Merge pull request #65045 from akien-mga/3.5-cherrypicks
This commit is contained in:
commit
a264b68f50
92 changed files with 683 additions and 415 deletions
|
@ -518,8 +518,6 @@ void _err_flush_stdout();
|
|||
} else \
|
||||
((void)0)
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This should be a 'free' assert for program flow and should not be needed in any releases,
|
||||
* only used in dev builds.
|
||||
|
@ -558,5 +556,6 @@ void _err_flush_stdout();
|
|||
((void)0)
|
||||
#else
|
||||
#define DEV_CHECK_ONCE(m_cond)
|
||||
#endif
|
||||
|
||||
#endif // ERROR_MACROS_H
|
||||
|
|
|
@ -724,6 +724,8 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
|
|||
// We also fall back in case of regional locales as done in TranslationServer::translate
|
||||
// (e.g. 'ru_RU' -> 'ru' if the former has no specific mapping).
|
||||
|
||||
// An extra remap may still be necessary afterwards due to the text -> binary converter on export.
|
||||
|
||||
String locale = TranslationServer::get_singleton()->get_locale();
|
||||
ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid.");
|
||||
String lang = TranslationServer::get_language_code(locale);
|
||||
|
@ -763,12 +765,10 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
|
|||
|
||||
if (path_remaps.has(new_path)) {
|
||||
new_path = path_remaps[new_path];
|
||||
}
|
||||
|
||||
if (new_path == p_path) { // Did not remap.
|
||||
} else {
|
||||
// Try file remap.
|
||||
Error err;
|
||||
FileAccess *f = FileAccess::open(p_path + ".remap", FileAccess::READ, &err);
|
||||
FileAccess *f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err);
|
||||
|
||||
if (f) {
|
||||
VariantParser::StreamFile stream;
|
||||
|
|
|
@ -358,6 +358,15 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) {
|
|||
* If nothing was found, error out.
|
||||
*/
|
||||
Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, bool p_upwards, bool p_ignore_override) {
|
||||
if (OS::get_singleton()->get_resource_dir() != "") {
|
||||
// OS will call ProjectSettings->get_resource_path which will be empty if not overridden!
|
||||
// If the OS would rather use a specific location, then it will not be empty.
|
||||
resource_path = OS::get_singleton()->get_resource_dir().replace("\\", "/");
|
||||
if (resource_path != "" && resource_path[resource_path.length() - 1] == '/') {
|
||||
resource_path = resource_path.substr(0, resource_path.length() - 1); // Chop end.
|
||||
}
|
||||
}
|
||||
|
||||
// If looking for files in a network client, use it directly
|
||||
|
||||
if (FileAccessNetworkClient::get_singleton()) {
|
||||
|
@ -439,13 +448,6 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
|
|||
// (Only Android -when reading from pck- and iOS use this.)
|
||||
|
||||
if (OS::get_singleton()->get_resource_dir() != "") {
|
||||
// OS will call ProjectSettings->get_resource_path which will be empty if not overridden!
|
||||
// If the OS would rather use a specific location, then it will not be empty.
|
||||
resource_path = OS::get_singleton()->get_resource_dir().replace("\\", "/");
|
||||
if (resource_path != "" && resource_path[resource_path.length() - 1] == '/') {
|
||||
resource_path = resource_path.substr(0, resource_path.length() - 1); // Chop end.
|
||||
}
|
||||
|
||||
Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
|
||||
if (err == OK && !p_ignore_override) {
|
||||
// Optional, we don't mind if it fails.
|
||||
|
|
|
@ -57,20 +57,20 @@
|
|||
<method name="get_caption" qualifiers="virtual">
|
||||
<return type="String" />
|
||||
<description>
|
||||
Gets the text caption for this node (used by some editors).
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to override the text caption for this node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_child_by_name" qualifiers="virtual">
|
||||
<return type="Object" />
|
||||
<argument index="0" name="name" type="String" />
|
||||
<description>
|
||||
Gets a child node by index (used by editors inheriting from [AnimationRootNode]).
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to return a child node by its [code]name[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_child_nodes" qualifiers="virtual">
|
||||
<return type="Dictionary" />
|
||||
<description>
|
||||
Gets all children nodes in order as a [code]name: node[/code] dictionary. Only useful when inheriting [AnimationRootNode].
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to return all children nodes in order as a [code]name: node[/code] dictionary.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_input_count" qualifiers="const">
|
||||
|
@ -97,19 +97,19 @@
|
|||
<return type="Variant" />
|
||||
<argument index="0" name="name" type="String" />
|
||||
<description>
|
||||
Gets the default value of a parameter. Parameters are custom local memory used for your nodes, given a resource can be reused in multiple trees.
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to return the default value of parameter "[code]name[/code]". Parameters are custom local memory used for your nodes, given a resource can be reused in multiple trees.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_parameter_list" qualifiers="virtual">
|
||||
<return type="Array" />
|
||||
<description>
|
||||
Gets the property information for parameter. Parameters are custom local memory used for your nodes, given a resource can be reused in multiple trees. Format is similar to [method Object.get_property_list].
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to return a list of the properties on this node. Parameters are custom local memory used for your nodes, given a resource can be reused in multiple trees. Format is similar to [method Object.get_property_list].
|
||||
</description>
|
||||
</method>
|
||||
<method name="has_filter" qualifiers="virtual">
|
||||
<return type="String" />
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] whether you want the blend tree editor to display filter editing on this node.
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to return whether the blend tree editor should display filter editing on this node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_path_filtered" qualifiers="const">
|
||||
|
@ -124,7 +124,7 @@
|
|||
<argument index="0" name="time" type="float" />
|
||||
<argument index="1" name="seek" type="bool" />
|
||||
<description>
|
||||
User-defined callback called when a custom node is processed. The [code]time[/code] parameter is a relative delta, unless [code]seek[/code] is [code]true[/code], in which case it is absolute.
|
||||
When inheriting from [AnimationRootNode], implement this virtual method to run some code when this node is processed. The [code]time[/code] parameter is a relative delta, unless [code]seek[/code] is [code]true[/code], in which case it is absolute.
|
||||
Here, call the [method blend_input], [method blend_node] or [method blend_animation] functions. You can also use [method get_parameter] and [method set_parameter] to modify local memory.
|
||||
This function should return the time left for the current animation to finish (if unsure, pass the value from the main blend being called).
|
||||
</description>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</methods>
|
||||
<members>
|
||||
<member name="advance_condition" type="String" setter="set_advance_condition" getter="get_advance_condition" default="""">
|
||||
Turn on auto advance when this condition is set. The provided name will become a boolean parameter on the [AnimationTree] that can be controlled from code (see [url=$DOCS_URL/tutorials/animation/animation_tree.html#controlling-from-code][/url]). For example, if [member AnimationTree.tree_root] is an [AnimationNodeStateMachine] and [member advance_condition] is set to [code]"idle"[/code]:
|
||||
Turn on auto advance when this condition is set. The provided name will become a boolean parameter on the [AnimationTree] that can be controlled from code (see [url=$DOCS_URL/tutorials/animation/animation_tree.html#controlling-from-code]Using AnimationTree[/url]). For example, if [member AnimationTree.tree_root] is an [AnimationNodeStateMachine] and [member advance_condition] is set to [code]"idle"[/code]:
|
||||
[codeblock]
|
||||
$animation_tree["parameters/conditions/idle"] = is_on_floor and (linear_velocity.x == 0)
|
||||
[/codeblock]
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
<return type="Animation" />
|
||||
<argument index="0" name="name" type="String" />
|
||||
<description>
|
||||
Returns the [Animation] with key [code]name[/code] or [code]null[/code] if not found.
|
||||
Returns the [Animation] with the key [code]name[/code]. If the animation does not exist, [code]null[/code] is returned and an error is logged.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_animation_list" qualifiers="const">
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
Plays positional sound in 2D space.
|
||||
</brief_description>
|
||||
<description>
|
||||
Plays audio that dampens with distance from screen center.
|
||||
Plays audio that dampens with distance from a given position.
|
||||
By default, audio is heard from the screen center. This can be changed by adding a [Listener2D] node to the scene and enabling it by calling [method Listener2D.make_current] on it.
|
||||
See also [AudioStreamPlayer] to play a sound non-positionally.
|
||||
[b]Note:[/b] Hiding an [AudioStreamPlayer2D] node does not disable its audio output. To temporarily disable an [AudioStreamPlayer2D]'s audio output, set [member volume_db] to a very low value like [code]-100[/code] (which isn't audible to human hearing).
|
||||
</description>
|
||||
|
|
|
@ -179,7 +179,7 @@
|
|||
The camera's projection mode. In [constant PROJECTION_PERSPECTIVE] mode, objects' Z distance from the camera's local space scales their perceived size.
|
||||
</member>
|
||||
<member name="size" type="float" setter="set_size" getter="get_size" default="1.0">
|
||||
The camera's size measured as 1/2 the width or height. Only applicable in orthogonal and frustum modes. Since [member keep_aspect] locks on axis, [code]size[/code] sets the other axis' size length.
|
||||
The camera's size in meters measured as the diameter of the width or height, depending on [member keep_aspect]. Only applicable in orthogonal and frustum modes.
|
||||
</member>
|
||||
<member name="v_offset" type="float" setter="set_v_offset" getter="get_v_offset" default="0.0">
|
||||
The vertical (Y) offset of the camera viewport.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</brief_description>
|
||||
<description>
|
||||
Base class of anything 2D. Canvas items are laid out in a tree; children inherit and extend their parent's transform. [CanvasItem] is extended by [Control] for anything GUI-related, and by [Node2D] for anything related to the 2D engine.
|
||||
Any [CanvasItem] can draw. For this, [method update] must be called, then [constant NOTIFICATION_DRAW] will be received on idle time to request redraw. Because of this, canvas items don't need to be redrawn on every frame, improving the performance significantly. Several functions for drawing on the [CanvasItem] are provided (see [code]draw_*[/code] functions). However, they can only be used inside the [method Object._notification], signal or [method _draw] virtual functions.
|
||||
Any [CanvasItem] can draw. For this, [method update] is called by the engine, then [constant NOTIFICATION_DRAW] will be received on idle time to request redraw. Because of this, canvas items don't need to be redrawn on every frame, improving the performance significantly. Several functions for drawing on the [CanvasItem] are provided (see [code]draw_*[/code] functions). However, they can only be used inside [method _draw], its corresponding [method Object._notification] or methods connected to the [signal draw] signal.
|
||||
Canvas items are drawn in tree order. By default, children are on top of their parents so a root [CanvasItem] will be drawn behind everything. This behavior can be changed on a per-item basis.
|
||||
A [CanvasItem] can also be hidden, which will also hide its children. It provides many ways to change parameters such as modulation (for itself and its children) and self modulation (only for itself), as well as its blend mode.
|
||||
Ultimately, a transform notification can be requested, which will notify the node that its global position changed in case the parent tree changed.
|
||||
|
@ -20,7 +20,8 @@
|
|||
<method name="_draw" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Overridable function called by the engine (if defined) to draw the canvas item.
|
||||
Called when [CanvasItem] has been requested to redraw (when [method update] is called, either manually or by the engine).
|
||||
Corresponds to the [constant NOTIFICATION_DRAW] notification in [method Object._notification].
|
||||
</description>
|
||||
</method>
|
||||
<method name="draw_arc">
|
||||
|
@ -366,7 +367,7 @@
|
|||
<method name="is_visible_in_tree" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its antecedents are also visible. If any antecedent is hidden, this node will not be visible in the scene tree.
|
||||
Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its antecedents are also visible. If any antecedent is hidden, this node will not be visible in the scene tree, and is consequently not drawn (see [method _draw]).
|
||||
</description>
|
||||
</method>
|
||||
<method name="make_canvas_position_local" qualifiers="const">
|
||||
|
@ -413,7 +414,7 @@
|
|||
<method name="update">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Queue the [CanvasItem] for update. [constant NOTIFICATION_DRAW] will be called on idle time to request redraw.
|
||||
Queues the [CanvasItem] to redraw. During idle time, if [CanvasItem] is visible, [constant NOTIFICATION_DRAW] is sent and [method _draw] is called. This only occurs [b]once[/b] per frame, even if this method has been called multiple times.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
|
@ -447,7 +448,8 @@
|
|||
<signals>
|
||||
<signal name="draw">
|
||||
<description>
|
||||
Emitted when the [CanvasItem] must redraw. This can only be connected realtime, as deferred will not allow drawing.
|
||||
Emitted when the [CanvasItem] must redraw, [i]after[/i] the related [constant NOTIFICATION_DRAW] notification, and [i]before[/i] [method _draw] is called.
|
||||
[b]Note:[/b] Deferred connections do not allow drawing through the [code]draw_*[/code] methods.
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="hide">
|
||||
|
@ -492,7 +494,7 @@
|
|||
The [CanvasItem]'s local transform has changed. This notification is only received if enabled by [method set_notify_local_transform].
|
||||
</constant>
|
||||
<constant name="NOTIFICATION_DRAW" value="30">
|
||||
The [CanvasItem] is requested to draw.
|
||||
The [CanvasItem] is requested to draw (see [method _draw]).
|
||||
</constant>
|
||||
<constant name="NOTIFICATION_VISIBILITY_CHANGED" value="31">
|
||||
The [CanvasItem]'s visibility has changed.
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<argument index="0" name="position" type="Vector2" />
|
||||
<argument index="1" name="in" type="Vector2" default="Vector2( 0, 0 )" />
|
||||
<argument index="2" name="out" type="Vector2" default="Vector2( 0, 0 )" />
|
||||
<argument index="3" name="at_position" type="int" default="-1" />
|
||||
<argument index="3" name="index" type="int" default="-1" />
|
||||
<description>
|
||||
Adds a point to a curve at [code]position[/code] relative to the [Curve2D]'s position, with control points [code]in[/code] and [code]out[/code].
|
||||
If [code]at_position[/code] is given, the point is inserted before the point number [code]at_position[/code], moving that point (and every point after) after the inserted point. If [code]at_position[/code] is not given, or is an illegal value ([code]at_position <0[/code] or [code]at_position >= [method get_point_count][/code]), the point will be appended at the end of the point list.
|
||||
Adds a point with the specified [code]position[/code] relative to the curve's own position, with control points [code]in[/code] and [code]out[/code]. Appends the new point at the end of the point list.
|
||||
If [code]index[/code] is given, the new point is inserted before the existing point identified by index [code]index[/code]. Every existing point starting from [code]index[/code] is shifted further down the list of points. The index must be greater than or equal to [code]0[/code] and must not exceed the number of existing points in the line. See [method get_point_count].
|
||||
</description>
|
||||
</method>
|
||||
<method name="clear_points">
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<argument index="0" name="position" type="Vector3" />
|
||||
<argument index="1" name="in" type="Vector3" default="Vector3( 0, 0, 0 )" />
|
||||
<argument index="2" name="out" type="Vector3" default="Vector3( 0, 0, 0 )" />
|
||||
<argument index="3" name="at_position" type="int" default="-1" />
|
||||
<argument index="3" name="index" type="int" default="-1" />
|
||||
<description>
|
||||
Adds a point to a curve at [code]position[/code] relative to the [Curve3D]'s position, with control points [code]in[/code] and [code]out[/code].
|
||||
If [code]at_position[/code] is given, the point is inserted before the point number [code]at_position[/code], moving that point (and every point after) after the inserted point. If [code]at_position[/code] is not given, or is an illegal value ([code]at_position <0[/code] or [code]at_position >= [method get_point_count][/code]), the point will be appended at the end of the point list.
|
||||
Adds a point with the specified [code]position[/code] relative to the curve's own position, with control points [code]in[/code] and [code]out[/code]. Appends the new point at the end of the point list.
|
||||
If [code]index[/code] is given, the new point is inserted before the existing point identified by index [code]index[/code]. Every existing point starting from [code]index[/code] is shifted further down the list of points. The index must be greater than or equal to [code]0[/code] and must not exceed the number of existing points in the line. See [method get_point_count].
|
||||
</description>
|
||||
</method>
|
||||
<method name="clear_points">
|
||||
|
|
|
@ -96,6 +96,8 @@
|
|||
<signal name="property_changed">
|
||||
<argument index="0" name="property" type="String" />
|
||||
<argument index="1" name="value" type="Variant" />
|
||||
<argument index="2" name="field" type="String" />
|
||||
<argument index="3" name="changing" type="bool" />
|
||||
<description>
|
||||
Do not emit this manually, use the [method emit_changed] method instead.
|
||||
</description>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<argument index="5" name="outline" type="bool" default="false" />
|
||||
<description>
|
||||
Draw character [code]char[/code] into a canvas item using the font at a given position, with [code]modulate[/code] color, and optionally kerning if [code]next[/code] is passed. clipping the width. [code]position[/code] specifies the baseline, not the top. To draw from the top, [i]ascent[/i] must be added to the Y axis. The width used by the character is returned, making this function useful for drawing strings character by character.
|
||||
If [code]outline[/code] is [code]true[/code], the outline of the character is drawn instead of the character itself.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ascent" qualifiers="const">
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
<argument index="0" name="replace" type="bool" default="false" />
|
||||
<argument index="1" name="custom_scene" type="PackedScene" default="null" />
|
||||
<description>
|
||||
Not thread-safe. Use [method Object.call_deferred] if calling from a thread.
|
||||
Call this method to actually load in the node. The created node will be placed as a sibling [i]above[/i] the [InstancePlaceholder] in the scene tree. The [Node]'s reference is also returned for convenience.
|
||||
[b]Note:[/b] [method create_instance] is not thread-safe. Use [method Object.call_deferred] if calling from a thread.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_instance_path" qualifiers="const">
|
||||
|
@ -28,6 +29,8 @@
|
|||
<return type="Dictionary" />
|
||||
<argument index="0" name="with_order" type="bool" default="false" />
|
||||
<description>
|
||||
Returns the list of properties that will be applied to the node when [method create_instance] is called.
|
||||
If [code]with_order[/code] is [code]true[/code], a key named [code].order[/code] (note the leading period) is added to the dictionary. This [code].order[/code] key is an [Array] of [String] property names specifying the order in which properties will be applied (with index 0 being the first).
|
||||
</description>
|
||||
</method>
|
||||
<method name="replace_by_instance">
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<method name="add_point">
|
||||
<return type="void" />
|
||||
<argument index="0" name="position" type="Vector2" />
|
||||
<argument index="1" name="at_position" type="int" default="-1" />
|
||||
<argument index="1" name="index" type="int" default="-1" />
|
||||
<description>
|
||||
Adds a point at the [code]position[/code]. Appends the point at the end of the line.
|
||||
If [code]at_position[/code] is given, the point is inserted before the point number [code]at_position[/code], moving that point (and every point after) after the inserted point. If [code]at_position[/code] is not given, or is an illegal value ([code]at_position < 0[/code] or [code]at_position >= [method get_point_count][/code]), the point will be appended at the end of the point list.
|
||||
Adds a point with the specified [code]position[/code] relative to the line's own position. Appends the new point at the end of the point list.
|
||||
If [code]index[/code] is given, the new point is inserted before the existing point identified by index [code]index[/code]. Every existing point starting from [code]index[/code] is shifted further down the list of points. The index must be greater than or equal to [code]0[/code] and must not exceed the number of existing points in the line. See [method get_point_count].
|
||||
</description>
|
||||
</method>
|
||||
<method name="clear_points">
|
||||
|
@ -30,29 +30,29 @@
|
|||
<method name="get_point_count" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the Line2D's amount of points.
|
||||
Returns the amount of points in the line.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_point_position" qualifiers="const">
|
||||
<return type="Vector2" />
|
||||
<argument index="0" name="i" type="int" />
|
||||
<argument index="0" name="index" type="int" />
|
||||
<description>
|
||||
Returns point [code]i[/code]'s position.
|
||||
Returns the position of the point at index [code]index[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="remove_point">
|
||||
<return type="void" />
|
||||
<argument index="0" name="i" type="int" />
|
||||
<argument index="0" name="index" type="int" />
|
||||
<description>
|
||||
Removes the point at index [code]i[/code] from the line.
|
||||
Removes the point at index [code]index[/code] from the line.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_point_position">
|
||||
<return type="void" />
|
||||
<argument index="0" name="i" type="int" />
|
||||
<argument index="0" name="index" type="int" />
|
||||
<argument index="1" name="position" type="Vector2" />
|
||||
<description>
|
||||
Overwrites the position in point [code]i[/code] with the supplied [code]position[/code].
|
||||
Overwrites the position of the point at index [code]index[/code] with the supplied [code]position[/code].
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
|
|
|
@ -213,7 +213,7 @@
|
|||
</method>
|
||||
<method name="map_is_active" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="nap" type="RID" />
|
||||
<argument index="0" name="map" type="RID" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the map is active.
|
||||
</description>
|
||||
|
|
|
@ -237,7 +237,7 @@
|
|||
</method>
|
||||
<method name="map_is_active" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="nap" type="RID" />
|
||||
<argument index="0" name="map" type="RID" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the map is active.
|
||||
</description>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
</brief_description>
|
||||
<description>
|
||||
Class for displaying popups with a panel background. In some cases it might be simpler to use than [Popup], since it provides a configurable background. If you are making windows, better check [WindowDialog].
|
||||
If any [Control] node is added as a child of this [PopupPanel], it will be stretched to fit the panel's size (similar to how [PanelContainer] works).
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
|
|
|
@ -1660,7 +1660,9 @@
|
|||
[b]Note:[/b] The two video drivers are not drop-in replacements for each other, so a game designed for GLES3 might not work properly when falling back to GLES2. In particular, some features of the GLES3 backend are not available in GLES2. Enabling this setting also means that both ETC and ETC2 VRAM-compressed textures will be exported on Android and iOS, increasing the data pack's size.
|
||||
</member>
|
||||
<member name="rendering/quality/filters/anisotropic_filter_level" type="int" setter="" getter="" default="4">
|
||||
Maximum anisotropic filter level used for textures with anisotropy enabled. Higher values will result in sharper textures when viewed from oblique angles, at the cost of performance. Only power-of-two values are valid (2, 4, 8, 16).
|
||||
Maximum anisotropic filter level used for textures with anisotropy enabled. Higher values will result in sharper textures when viewed from oblique angles, at the cost of performance. With the exception of [code]1[/code], only power-of-two values are valid ([code]2[/code], [code]4[/code], [code]8[/code], [code]16[/code]). A value of [code]1[/code] forcibly disables anisotropic filtering, even on textures where it is enabled.
|
||||
[b]Note:[/b] For performance reasons, anisotropic filtering [i]is not enabled by default[/i] on textures. For this setting to have an effect, anisotropic texture filtering can be enabled by selecting a texture in the FileSystem dock, going to the Import dock, checking the [b]Anisotropic[/b] checkbox then clicking [b]Reimport[/b]. However, anisotropic filtering is rarely useful in 2D, so only enable it for textures in 2D if it makes a meaningful visual difference.
|
||||
[b]Note:[/b] This property is only read when the project starts. There is currently no way to change this setting at run-time.
|
||||
</member>
|
||||
<member name="rendering/quality/filters/msaa" type="int" setter="" getter="" default="0">
|
||||
Sets the number of MSAA samples to use. MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware.
|
||||
|
|
|
@ -233,9 +233,11 @@
|
|||
</member>
|
||||
<member name="debug_collisions_hint" type="bool" setter="set_debug_collisions_hint" getter="is_debugging_collisions_hint" default="false">
|
||||
If [code]true[/code], collision shapes will be visible when running the game from the editor for debugging purposes.
|
||||
[b]Note:[/b] This property is not designed to be changed at run-time. Changing the value of [member debug_collisions_hint] while the project is running will not have the desired effect.
|
||||
</member>
|
||||
<member name="debug_navigation_hint" type="bool" setter="set_debug_navigation_hint" getter="is_debugging_navigation_hint" default="false">
|
||||
If [code]true[/code], navigation polygons will be visible when running the game from the editor for debugging purposes.
|
||||
[b]Note:[/b] This property is not designed to be changed at run-time. Changing the value of [member debug_navigation_hint] while the project is running will not have the desired effect.
|
||||
</member>
|
||||
<member name="edited_scene_root" type="Node" setter="set_edited_scene_root" getter="get_edited_scene_root">
|
||||
The root of the edited scene.
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
<member name="anisotropy_enabled" type="bool" setter="set_feature" getter="get_feature" default="false">
|
||||
If [code]true[/code], anisotropy is enabled. Anisotropy changes the shape of the specular blob and aligns it to tangent space. This is useful for brushed aluminium and hair reflections.
|
||||
[b]Note:[/b] Mesh tangents are needed for anisotropy to work. If the mesh does not contain tangents, the anisotropy effect will appear broken.
|
||||
[b]Note:[/b] Material anisotropy should not to be confused with anisotropic texture filtering. Anisotropic texture filtering can be enabled by selecting a texture in the FileSystem dock, going to the Import dock, checking the [b]Anisotropic[/b] checkbox then clicking [b]Reimport[/b].
|
||||
[b]Note:[/b] Material anisotropy should not to be confused with anisotropic texture filtering. Anisotropic texture filtering can be enabled by selecting a texture in the FileSystem dock, going to the Import dock, checking the [b]Anisotropic[/b] checkbox then clicking [b]Reimport[/b]. The anisotropic filtering level can be changed by adjusting [member ProjectSettings.rendering/quality/filters/anisotropic_filter_level].
|
||||
</member>
|
||||
<member name="anisotropy_flowmap" type="Texture" setter="set_texture" getter="get_texture">
|
||||
Texture that offsets the tangent map for anisotropy calculations and optionally controls the anisotropy effect (if an alpha channel is present). The flowmap texture is expected to be a derivative map, with the red channel representing distortion on the X axis and green channel representing distortion on the Y axis. Values below 0.5 will result in negative distortion, whereas values above 0.5 will result in positive distortion.
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
The region of the atlas texture to display. [member region_enabled] must be [code]true[/code].
|
||||
</member>
|
||||
<member name="texture" type="Texture" setter="set_texture" getter="get_texture">
|
||||
[Texture] object to draw. If [member GeometryInstance.material_override] is used, this will be overridden.
|
||||
[Texture] object to draw. If [member GeometryInstance.material_override] is used, this will be overridden. The size information is still used.
|
||||
</member>
|
||||
<member name="vframes" type="int" setter="set_vframes" getter="get_vframes" default="1">
|
||||
The number of rows in the sprite sheet.
|
||||
|
|
|
@ -42,6 +42,13 @@
|
|||
Returns [code]true[/code] if select with right mouse button is enabled.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_tab_button_icon" qualifiers="const">
|
||||
<return type="Texture" />
|
||||
<argument index="0" name="tab_idx" type="int" />
|
||||
<description>
|
||||
Returns the button icon from the tab at index [code]tab_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_tab_count" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
|
@ -110,6 +117,14 @@
|
|||
If [code]true[/code], enables selecting a tab with the right mouse button.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_tab_button_icon">
|
||||
<return type="void" />
|
||||
<argument index="0" name="tab_idx" type="int" />
|
||||
<argument index="1" name="icon" type="Texture" />
|
||||
<description>
|
||||
Sets the button icon from the tab at index [code]tab_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_tab_disabled">
|
||||
<return type="void" />
|
||||
<argument index="0" name="tab_idx" type="int" />
|
||||
|
@ -169,7 +184,7 @@
|
|||
<signal name="right_button_pressed">
|
||||
<argument index="0" name="tab" type="int" />
|
||||
<description>
|
||||
Emitted when a tab is right-clicked.
|
||||
Emitted when a tab's right button is pressed. See [method set_tab_button_icon].
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="tab_changed">
|
||||
|
|
|
@ -168,9 +168,8 @@ Error AudioDriverALSA::init() {
|
|||
return ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
active = false;
|
||||
thread_exited = false;
|
||||
exit_thread = false;
|
||||
active.clear();
|
||||
exit_thread.clear();
|
||||
|
||||
Error err = init_device();
|
||||
if (err == OK) {
|
||||
|
@ -183,11 +182,11 @@ Error AudioDriverALSA::init() {
|
|||
void AudioDriverALSA::thread_func(void *p_udata) {
|
||||
AudioDriverALSA *ad = (AudioDriverALSA *)p_udata;
|
||||
|
||||
while (!ad->exit_thread) {
|
||||
while (!ad->exit_thread.is_set()) {
|
||||
ad->lock();
|
||||
ad->start_counting_ticks();
|
||||
|
||||
if (!ad->active) {
|
||||
if (!ad->active.is_set()) {
|
||||
for (uint64_t i = 0; i < ad->period_size * ad->channels; i++) {
|
||||
ad->samples_out.write[i] = 0;
|
||||
}
|
||||
|
@ -203,7 +202,7 @@ void AudioDriverALSA::thread_func(void *p_udata) {
|
|||
int todo = ad->period_size;
|
||||
int total = 0;
|
||||
|
||||
while (todo && !ad->exit_thread) {
|
||||
while (todo && !ad->exit_thread.is_set()) {
|
||||
int16_t *src = (int16_t *)ad->samples_out.ptr();
|
||||
int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo);
|
||||
|
||||
|
@ -222,8 +221,8 @@ void AudioDriverALSA::thread_func(void *p_udata) {
|
|||
wrote = snd_pcm_recover(ad->pcm_handle, wrote, 0);
|
||||
if (wrote < 0) {
|
||||
ERR_PRINT("ALSA: Failed and can't recover: " + String(snd_strerror(wrote)));
|
||||
ad->active = false;
|
||||
ad->exit_thread = true;
|
||||
ad->active.clear();
|
||||
ad->exit_thread.set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,8 +240,8 @@ void AudioDriverALSA::thread_func(void *p_udata) {
|
|||
|
||||
err = ad->init_device();
|
||||
if (err != OK) {
|
||||
ad->active = false;
|
||||
ad->exit_thread = true;
|
||||
ad->active.clear();
|
||||
ad->exit_thread.set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -250,12 +249,10 @@ void AudioDriverALSA::thread_func(void *p_udata) {
|
|||
ad->stop_counting_ticks();
|
||||
ad->unlock();
|
||||
}
|
||||
|
||||
ad->thread_exited = true;
|
||||
}
|
||||
|
||||
void AudioDriverALSA::start() {
|
||||
active = true;
|
||||
active.set();
|
||||
}
|
||||
|
||||
int AudioDriverALSA::get_mix_rate() const {
|
||||
|
@ -327,7 +324,7 @@ void AudioDriverALSA::finish_device() {
|
|||
}
|
||||
|
||||
void AudioDriverALSA::finish() {
|
||||
exit_thread = true;
|
||||
exit_thread.set();
|
||||
thread.wait_to_finish();
|
||||
|
||||
finish_device();
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/safe_refcount.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#include "asound-so_wrap.h"
|
||||
|
@ -64,9 +65,8 @@ class AudioDriverALSA : public AudioDriver {
|
|||
snd_pcm_uframes_t period_size;
|
||||
int channels;
|
||||
|
||||
bool active;
|
||||
bool thread_exited;
|
||||
mutable bool exit_thread;
|
||||
SafeFlag active;
|
||||
SafeFlag exit_thread;
|
||||
|
||||
public:
|
||||
const char *get_name() const {
|
||||
|
|
|
@ -79,7 +79,7 @@ void MIDIDriverALSAMidi::thread_func(void *p_udata) {
|
|||
int expected_size = 255;
|
||||
int bytes = 0;
|
||||
|
||||
while (!md->exit_thread) {
|
||||
while (!md->exit_thread.is_set()) {
|
||||
int ret;
|
||||
|
||||
md->lock();
|
||||
|
@ -149,14 +149,14 @@ Error MIDIDriverALSAMidi::open() {
|
|||
}
|
||||
snd_device_name_free_hint(hints);
|
||||
|
||||
exit_thread = false;
|
||||
exit_thread.clear();
|
||||
thread.start(MIDIDriverALSAMidi::thread_func, this);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void MIDIDriverALSAMidi::close() {
|
||||
exit_thread = true;
|
||||
exit_thread.set();
|
||||
thread.wait_to_finish();
|
||||
|
||||
for (int i = 0; i < connected_inputs.size(); i++) {
|
||||
|
@ -193,7 +193,7 @@ PoolStringArray MIDIDriverALSAMidi::get_connected_inputs() {
|
|||
}
|
||||
|
||||
MIDIDriverALSAMidi::MIDIDriverALSAMidi() {
|
||||
exit_thread = false;
|
||||
exit_thread.clear();
|
||||
}
|
||||
|
||||
MIDIDriverALSAMidi::~MIDIDriverALSAMidi() {
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "core/os/midi_driver.h"
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/safe_refcount.h"
|
||||
#include "core/vector.h"
|
||||
|
||||
#include "../alsa/asound-so_wrap.h"
|
||||
|
@ -47,7 +48,7 @@ class MIDIDriverALSAMidi : public MIDIDriver {
|
|||
|
||||
Vector<snd_rawmidi_t *> connected_inputs;
|
||||
|
||||
bool exit_thread;
|
||||
SafeFlag exit_thread;
|
||||
|
||||
static void thread_func(void *p_udata);
|
||||
|
||||
|
|
|
@ -211,7 +211,7 @@ VERTEX_SHADER_CODE
|
|||
|
||||
xform = transpose(xform);
|
||||
|
||||
out_velocity_active.a = mix(0.0, 1.0, shader_active);
|
||||
out_velocity_active.a = float(shader_active);
|
||||
|
||||
out_xform_1 = xform[0];
|
||||
out_xform_2 = xform[1];
|
||||
|
|
|
@ -285,9 +285,8 @@ Error AudioDriverPulseAudio::init() {
|
|||
return ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
active = false;
|
||||
thread_exited = false;
|
||||
exit_thread = false;
|
||||
active.clear();
|
||||
exit_thread.clear();
|
||||
|
||||
mix_rate = GLOBAL_GET("audio/mix_rate");
|
||||
|
||||
|
@ -384,7 +383,7 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|||
size_t avail_bytes = 0;
|
||||
uint64_t default_device_msec = OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
while (!ad->exit_thread) {
|
||||
while (!ad->exit_thread.is_set()) {
|
||||
size_t read_bytes = 0;
|
||||
size_t written_bytes = 0;
|
||||
|
||||
|
@ -394,7 +393,7 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|||
|
||||
int16_t *out_ptr = ad->samples_out.ptrw();
|
||||
|
||||
if (!ad->active) {
|
||||
if (!ad->active.is_set()) {
|
||||
for (unsigned int i = 0; i < ad->pa_buffer_size; i++) {
|
||||
out_ptr[i] = 0;
|
||||
}
|
||||
|
@ -464,8 +463,8 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|||
|
||||
err = ad->init_device();
|
||||
if (err != OK) {
|
||||
ad->active = false;
|
||||
ad->exit_thread = true;
|
||||
ad->active.clear();
|
||||
ad->exit_thread.set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -503,8 +502,8 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|||
Error err = ad->init_device();
|
||||
if (err != OK) {
|
||||
ERR_PRINT("PulseAudio: init_device error");
|
||||
ad->active = false;
|
||||
ad->exit_thread = true;
|
||||
ad->active.clear();
|
||||
ad->exit_thread.set();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -557,8 +556,8 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|||
|
||||
err = ad->capture_init_device();
|
||||
if (err != OK) {
|
||||
ad->active = false;
|
||||
ad->exit_thread = true;
|
||||
ad->active.clear();
|
||||
ad->exit_thread.set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -573,12 +572,10 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|||
OS::get_singleton()->delay_usec(1000);
|
||||
}
|
||||
}
|
||||
|
||||
ad->thread_exited = true;
|
||||
}
|
||||
|
||||
void AudioDriverPulseAudio::start() {
|
||||
active = true;
|
||||
active.set();
|
||||
}
|
||||
|
||||
int AudioDriverPulseAudio::get_mix_rate() const {
|
||||
|
@ -663,7 +660,7 @@ void AudioDriverPulseAudio::finish() {
|
|||
return;
|
||||
}
|
||||
|
||||
exit_thread = true;
|
||||
exit_thread.set();
|
||||
thread.wait_to_finish();
|
||||
|
||||
finish_device();
|
||||
|
@ -840,9 +837,6 @@ AudioDriverPulseAudio::AudioDriverPulseAudio() :
|
|||
channels(0),
|
||||
pa_ready(0),
|
||||
pa_status(0),
|
||||
active(false),
|
||||
thread_exited(false),
|
||||
exit_thread(false),
|
||||
latency(0) {
|
||||
samples_in.clear();
|
||||
samples_out.clear();
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/safe_refcount.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#include "pulse-so_wrap.h"
|
||||
|
@ -70,9 +71,8 @@ class AudioDriverPulseAudio : public AudioDriver {
|
|||
Array pa_devices;
|
||||
Array pa_rec_devices;
|
||||
|
||||
bool active;
|
||||
bool thread_exited;
|
||||
mutable bool exit_thread;
|
||||
SafeFlag active;
|
||||
SafeFlag exit_thread;
|
||||
|
||||
float latency;
|
||||
|
||||
|
|
|
@ -365,12 +365,12 @@ Error AudioDriverWASAPI::init_capture_device(bool reinit) {
|
|||
}
|
||||
|
||||
Error AudioDriverWASAPI::audio_device_finish(AudioDeviceWASAPI *p_device) {
|
||||
if (p_device->active) {
|
||||
if (p_device->active.is_set()) {
|
||||
if (p_device->audio_client) {
|
||||
p_device->audio_client->Stop();
|
||||
}
|
||||
|
||||
p_device->active = false;
|
||||
p_device->active.clear();
|
||||
}
|
||||
|
||||
SAFE_RELEASE(p_device->audio_client)
|
||||
|
@ -396,8 +396,7 @@ Error AudioDriverWASAPI::init() {
|
|||
ERR_PRINT("WASAPI: init_render_device error");
|
||||
}
|
||||
|
||||
exit_thread = false;
|
||||
thread_exited = false;
|
||||
exit_thread.clear();
|
||||
|
||||
thread.start(thread_func, this);
|
||||
|
||||
|
@ -543,7 +542,7 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
|
|||
uint32_t avail_frames = 0;
|
||||
uint32_t write_ofs = 0;
|
||||
|
||||
while (!ad->exit_thread) {
|
||||
while (!ad->exit_thread.is_set()) {
|
||||
uint32_t read_frames = 0;
|
||||
uint32_t written_frames = 0;
|
||||
|
||||
|
@ -551,7 +550,7 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
|
|||
ad->lock();
|
||||
ad->start_counting_ticks();
|
||||
|
||||
if (ad->audio_output.active) {
|
||||
if (ad->audio_output.active.is_set()) {
|
||||
ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw());
|
||||
} else {
|
||||
for (int i = 0; i < ad->samples_in.size(); i++) {
|
||||
|
@ -617,7 +616,7 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
|
|||
}
|
||||
} else {
|
||||
ERR_PRINT("WASAPI: Get buffer error");
|
||||
ad->exit_thread = true;
|
||||
ad->exit_thread.set();
|
||||
}
|
||||
}
|
||||
} else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
|
||||
|
@ -666,7 +665,7 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
|
|||
write_ofs = 0;
|
||||
}
|
||||
|
||||
if (ad->audio_input.active) {
|
||||
if (ad->audio_input.active.is_set()) {
|
||||
UINT32 packet_length = 0;
|
||||
BYTE *data;
|
||||
UINT32 num_frames_available;
|
||||
|
@ -745,8 +744,6 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
|
|||
OS::get_singleton()->delay_usec(1000);
|
||||
}
|
||||
}
|
||||
|
||||
ad->thread_exited = true;
|
||||
}
|
||||
|
||||
void AudioDriverWASAPI::start() {
|
||||
|
@ -755,7 +752,7 @@ void AudioDriverWASAPI::start() {
|
|||
if (hr != S_OK) {
|
||||
ERR_PRINT("WASAPI: Start failed");
|
||||
} else {
|
||||
audio_output.active = true;
|
||||
audio_output.active.set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -769,7 +766,7 @@ void AudioDriverWASAPI::unlock() {
|
|||
}
|
||||
|
||||
void AudioDriverWASAPI::finish() {
|
||||
exit_thread = true;
|
||||
exit_thread.set();
|
||||
thread.wait_to_finish();
|
||||
|
||||
finish_capture_device();
|
||||
|
@ -783,19 +780,19 @@ Error AudioDriverWASAPI::capture_start() {
|
|||
return err;
|
||||
}
|
||||
|
||||
if (audio_input.active) {
|
||||
if (audio_input.active.is_set()) {
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
audio_input.audio_client->Start();
|
||||
audio_input.active = true;
|
||||
audio_input.active.set();
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error AudioDriverWASAPI::capture_stop() {
|
||||
if (audio_input.active) {
|
||||
if (audio_input.active.is_set()) {
|
||||
audio_input.audio_client->Stop();
|
||||
audio_input.active = false;
|
||||
audio_input.active.clear();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
@ -827,9 +824,6 @@ AudioDriverWASAPI::AudioDriverWASAPI() {
|
|||
channels = 0;
|
||||
mix_rate = 0;
|
||||
buffer_frames = 0;
|
||||
|
||||
thread_exited = false;
|
||||
exit_thread = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/safe_refcount.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#include <audioclient.h>
|
||||
|
@ -48,7 +49,7 @@ class AudioDriverWASAPI : public AudioDriver {
|
|||
IAudioClient *audio_client;
|
||||
IAudioRenderClient *render_client;
|
||||
IAudioCaptureClient *capture_client;
|
||||
bool active;
|
||||
SafeFlag active;
|
||||
|
||||
WORD format_tag;
|
||||
WORD bits_per_sample;
|
||||
|
@ -62,7 +63,6 @@ class AudioDriverWASAPI : public AudioDriver {
|
|||
audio_client(NULL),
|
||||
render_client(NULL),
|
||||
capture_client(NULL),
|
||||
active(false),
|
||||
format_tag(0),
|
||||
bits_per_sample(0),
|
||||
channels(0),
|
||||
|
@ -84,8 +84,7 @@ class AudioDriverWASAPI : public AudioDriver {
|
|||
int mix_rate;
|
||||
int buffer_frames;
|
||||
|
||||
bool thread_exited;
|
||||
mutable bool exit_thread;
|
||||
SafeFlag exit_thread;
|
||||
|
||||
static _FORCE_INLINE_ void write_sample(WORD format_tag, int bits_per_sample, BYTE *buffer, int i, int32_t sample);
|
||||
static _FORCE_INLINE_ int32_t read_sample(WORD format_tag, int bits_per_sample, BYTE *buffer, int i);
|
||||
|
|
|
@ -403,6 +403,8 @@ DirAccessWindows::DirAccessWindows() {
|
|||
}
|
||||
|
||||
DirAccessWindows::~DirAccessWindows() {
|
||||
list_dir_end();
|
||||
|
||||
memdelete(p);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,9 +38,8 @@ const char *AudioDriverXAudio2::get_name() const {
|
|||
}
|
||||
|
||||
Error AudioDriverXAudio2::init() {
|
||||
active = false;
|
||||
thread_exited = false;
|
||||
exit_thread = false;
|
||||
active.clear();
|
||||
exit_thread.clear();
|
||||
pcm_open = false;
|
||||
samples_in = NULL;
|
||||
|
||||
|
@ -86,17 +85,19 @@ Error AudioDriverXAudio2::init() {
|
|||
void AudioDriverXAudio2::thread_func(void *p_udata) {
|
||||
AudioDriverXAudio2 *ad = (AudioDriverXAudio2 *)p_udata;
|
||||
|
||||
while (!ad->exit_thread) {
|
||||
if (!ad->active) {
|
||||
while (!ad->exit_thread.is_set()) {
|
||||
if (!ad->active.is_set()) {
|
||||
for (int i = 0; i < AUDIO_BUFFERS; i++) {
|
||||
ad->xaudio_buffer[i].Flags = XAUDIO2_END_OF_STREAM;
|
||||
}
|
||||
|
||||
} else {
|
||||
ad->lock();
|
||||
ad->start_counting_ticks();
|
||||
|
||||
ad->audio_server_process(ad->buffer_size, ad->samples_in);
|
||||
|
||||
ad->stop_counting_ticks();
|
||||
ad->unlock();
|
||||
|
||||
for (unsigned int i = 0; i < ad->buffer_size * ad->channels; i++) {
|
||||
|
@ -117,12 +118,10 @@ void AudioDriverXAudio2::thread_func(void *p_udata) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
ad->thread_exited = true;
|
||||
}
|
||||
|
||||
void AudioDriverXAudio2::start() {
|
||||
active = true;
|
||||
active.set();
|
||||
HRESULT hr = source_voice->Start(0);
|
||||
ERR_FAIL_COND_MSG(hr != S_OK, "Error starting XAudio2 driver. Error code: " + itos(hr) + ".");
|
||||
}
|
||||
|
@ -156,7 +155,7 @@ void AudioDriverXAudio2::finish() {
|
|||
if (!thread.is_started())
|
||||
return;
|
||||
|
||||
exit_thread = true;
|
||||
exit_thread.set();
|
||||
thread.wait_to_finish();
|
||||
|
||||
if (source_voice) {
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/safe_refcount.h"
|
||||
#include "servers/audio_server.h"
|
||||
|
||||
#include <mmsystem.h>
|
||||
|
@ -77,9 +78,8 @@ class AudioDriverXAudio2 : public AudioDriver {
|
|||
|
||||
int channels;
|
||||
|
||||
bool active;
|
||||
bool thread_exited;
|
||||
mutable bool exit_thread;
|
||||
SafeFlag active;
|
||||
SafeFlag exit_thread;
|
||||
bool pcm_open;
|
||||
|
||||
WAVEFORMATEX wave_format;
|
||||
|
|
|
@ -569,7 +569,7 @@ void EditorProperty::_gui_input(const Ref<InputEvent> &p_event) {
|
|||
}
|
||||
|
||||
void EditorProperty::_unhandled_key_input(const Ref<InputEvent> &p_event) {
|
||||
if (!selected) {
|
||||
if (!selected || !is_visible_in_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -765,7 +765,7 @@ void EditorProperty::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked"), "set_checked", "is_checked");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_red"), "set_draw_red", "is_draw_red");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying"), "set_keying", "is_keying");
|
||||
ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
|
||||
ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::STRING, "field"), PropertyInfo(Variant::BOOL, "changing")));
|
||||
ADD_SIGNAL(MethodInfo("multiple_properties_changed", PropertyInfo(Variant::POOL_STRING_ARRAY, "properties"), PropertyInfo(Variant::ARRAY, "value")));
|
||||
ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property")));
|
||||
ADD_SIGNAL(MethodInfo("property_keyed_with_value", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
|
||||
|
|
|
@ -58,18 +58,23 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f
|
|||
}
|
||||
|
||||
void EditorLog::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||
//button->set_icon(get_icon("Console","EditorIcons"));
|
||||
log->add_font_override("normal_font", get_font("output_source", "EditorFonts"));
|
||||
log->add_color_override("selection_color", get_color("accent_color", "Editor") * Color(1, 1, 1, 0.4));
|
||||
} else if (p_what == NOTIFICATION_THEME_CHANGED) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (log != nullptr) {
|
||||
Ref<DynamicFont> df_output_code = get_font("output_source", "EditorFonts");
|
||||
if (df_output_code.is_valid()) {
|
||||
if (log != nullptr) {
|
||||
log->add_font_override("normal_font", get_font("output_source", "EditorFonts"));
|
||||
log->add_color_override("selection_color", get_color("accent_color", "Editor") * Color(1, 1, 1, 0.4));
|
||||
}
|
||||
}
|
||||
|
||||
theme_cache.error_color = get_color("error_color", "Editor");
|
||||
theme_cache.error_icon = get_icon("Error", "EditorIcons");
|
||||
theme_cache.warning_color = get_color("warning_color", "Editor");
|
||||
theme_cache.warning_icon = get_icon("Warning", "EditorIcons");
|
||||
theme_cache.message_color = get_color("font_color", "Editor") * Color(1, 1, 1, 0.6);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,22 +109,22 @@ void EditorLog::add_message(const String &p_msg, MessageType p_type) {
|
|||
case MSG_TYPE_STD: {
|
||||
} break;
|
||||
case MSG_TYPE_ERROR: {
|
||||
log->push_color(get_color("error_color", "Editor"));
|
||||
Ref<Texture> icon = get_icon("Error", "EditorIcons");
|
||||
log->push_color(theme_cache.error_color);
|
||||
Ref<Texture> icon = theme_cache.error_icon;
|
||||
log->add_image(icon);
|
||||
log->add_text(" ");
|
||||
tool_button->set_icon(icon);
|
||||
} break;
|
||||
case MSG_TYPE_WARNING: {
|
||||
log->push_color(get_color("warning_color", "Editor"));
|
||||
Ref<Texture> icon = get_icon("Warning", "EditorIcons");
|
||||
log->push_color(theme_cache.warning_color);
|
||||
Ref<Texture> icon = theme_cache.warning_icon;
|
||||
log->add_image(icon);
|
||||
log->add_text(" ");
|
||||
tool_button->set_icon(icon);
|
||||
} break;
|
||||
case MSG_TYPE_EDITOR: {
|
||||
// Distinguish editor messages from messages printed by the project
|
||||
log->push_color(get_color("font_color", "Editor") * Color(1, 1, 1, 0.6));
|
||||
log->push_color(theme_cache.message_color);
|
||||
} break;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,16 @@
|
|||
class EditorLog : public VBoxContainer {
|
||||
GDCLASS(EditorLog, VBoxContainer);
|
||||
|
||||
struct {
|
||||
Color error_color;
|
||||
Ref<Texture> error_icon;
|
||||
|
||||
Color warning_color;
|
||||
Ref<Texture> warning_icon;
|
||||
|
||||
Color message_color;
|
||||
} theme_cache;
|
||||
|
||||
Button *clearbutton;
|
||||
Button *copybutton;
|
||||
Label *title;
|
||||
|
|
|
@ -1129,24 +1129,63 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
|
|||
}
|
||||
} break;
|
||||
case TOOL_TOGGLE_SCENE_UNIQUE_NAME: {
|
||||
List<Node *> selection = editor_selection->get_selected_node_list();
|
||||
List<Node *>::Element *e = selection.front();
|
||||
if (e) {
|
||||
UndoRedo *undo_redo = &editor_data->get_undo_redo();
|
||||
Node *node = e->get();
|
||||
bool enabled = node->is_unique_name_in_owner();
|
||||
if (!enabled && get_tree()->get_edited_scene_root()->get_node_or_null(UNIQUE_NODE_PREFIX + String(node->get_name())) != nullptr) {
|
||||
accept->set_text(TTR("Another node already uses this unique name in the scene."));
|
||||
accept->popup_centered();
|
||||
// Enabling/disabling based on the same node based on which the checkbox in the menu is checked/unchecked.
|
||||
List<Node *>::Element *first_selected = editor_selection->get_selected_node_list().front();
|
||||
if (first_selected == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!enabled) {
|
||||
undo_redo->create_action(TTR("Enable Scene Unique Name"));
|
||||
} else {
|
||||
undo_redo->create_action(TTR("Disable Scene Unique Name"));
|
||||
bool enabling = !first_selected->get()->is_unique_name_in_owner();
|
||||
|
||||
List<Node *> full_selection = editor_selection->get_full_selected_node_list();
|
||||
UndoRedo *undo_redo = &editor_data->get_undo_redo();
|
||||
|
||||
if (enabling) {
|
||||
Vector<Node *> new_unique_nodes;
|
||||
Vector<StringName> new_unique_names;
|
||||
Vector<StringName> cant_be_set_unique_names;
|
||||
|
||||
for (List<Node *>::Element *e = full_selection.front(); e; e = e->next()) {
|
||||
Node *node = e->get();
|
||||
if (node->is_unique_name_in_owner()) {
|
||||
continue;
|
||||
}
|
||||
StringName name = node->get_name();
|
||||
if (new_unique_names.find(name) != -1 || get_tree()->get_edited_scene_root()->get_node_or_null(UNIQUE_NODE_PREFIX + String(name)) != nullptr) {
|
||||
cant_be_set_unique_names.push_back(name);
|
||||
} else {
|
||||
new_unique_nodes.push_back(node);
|
||||
new_unique_names.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_unique_nodes.size()) {
|
||||
undo_redo->create_action(TTR("Enable Scene Unique Name(s)"));
|
||||
for (int i = 0; i < new_unique_nodes.size(); i++) {
|
||||
undo_redo->add_do_method(new_unique_nodes[i], "set_unique_name_in_owner", true);
|
||||
undo_redo->add_undo_method(new_unique_nodes[i], "set_unique_name_in_owner", false);
|
||||
}
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
if (cant_be_set_unique_names.size()) {
|
||||
String popup_text = TTR("Unique names already used by another node in the scene:");
|
||||
popup_text += "\n";
|
||||
for (int i = 0; i < cant_be_set_unique_names.size(); i++) {
|
||||
popup_text += "\n" + String(cant_be_set_unique_names[i]);
|
||||
}
|
||||
accept->set_text(popup_text);
|
||||
accept->popup_centered();
|
||||
}
|
||||
} else { // Disabling.
|
||||
undo_redo->create_action(TTR("Disable Scene Unique Name(s)"));
|
||||
for (List<Node *>::Element *e = full_selection.front(); e; e = e->next()) {
|
||||
Node *node = e->get();
|
||||
if (!node->is_unique_name_in_owner()) {
|
||||
continue;
|
||||
}
|
||||
undo_redo->add_do_method(node, "set_unique_name_in_owner", false);
|
||||
undo_redo->add_undo_method(node, "set_unique_name_in_owner", true);
|
||||
}
|
||||
undo_redo->add_do_method(node, "set_unique_name_in_owner", !enabled);
|
||||
undo_redo->add_undo_method(node, "set_unique_name_in_owner", enabled);
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
} break;
|
||||
|
@ -2815,9 +2854,18 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
|
|||
}
|
||||
|
||||
if (profile_allow_editing) {
|
||||
if (selection[0]->get_owner() == EditorNode::get_singleton()->get_edited_scene()) {
|
||||
// Only for nodes owned by the edited scene root.
|
||||
// Allow multi-toggling scene unique names but only if all selected nodes are owned by the edited scene root.
|
||||
bool all_owned = true;
|
||||
for (List<Node *>::Element *e = full_selection.front(); e; e = e->next()) {
|
||||
Node *node = e->get();
|
||||
if (node->get_owner() != EditorNode::get_singleton()->get_edited_scene()) {
|
||||
all_owned = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_owned) {
|
||||
menu->add_icon_check_item(get_icon("SceneUniqueName", "EditorIcons"), TTR("Access as Scene Unique Name"), TOOL_TOGGLE_SCENE_UNIQUE_NAME);
|
||||
// Checked based on `selection[0]` because `full_selection` has undesired ordering.
|
||||
menu->set_item_checked(menu->get_item_index(TOOL_TOGGLE_SCENE_UNIQUE_NAME), selection[0]->is_unique_name_in_owner());
|
||||
menu->add_separator();
|
||||
}
|
||||
|
|
|
@ -841,11 +841,9 @@ void InputDefault::joy_axis(int p_device, int p_axis, float p_value) {
|
|||
}
|
||||
|
||||
bool pressed = map.value > 0.5;
|
||||
if (pressed == joy_buttons_pressed.has(_combine_device(map.index, p_device))) {
|
||||
// Button already pressed or released; so ignore.
|
||||
return;
|
||||
}
|
||||
if (pressed != joy_buttons_pressed.has(_combine_device(map.index, p_device))) {
|
||||
_button_event(p_device, map.index, pressed);
|
||||
}
|
||||
|
||||
// Ensure opposite D-Pad button is also released.
|
||||
switch (map.index) {
|
||||
|
@ -992,7 +990,7 @@ InputDefault::JoyEvent InputDefault::_get_mapped_axis_event(const JoyDeviceMappi
|
|||
value = -value;
|
||||
}
|
||||
if (binding.input.axis.range == FULL_AXIS ||
|
||||
(binding.input.axis.range == POSITIVE_HALF_AXIS && value > 0) ||
|
||||
(binding.input.axis.range == POSITIVE_HALF_AXIS && value >= 0) ||
|
||||
(binding.input.axis.range == NEGATIVE_HALF_AXIS && value < 0)) {
|
||||
event.type = binding.outputType;
|
||||
float shifted_positive_value = 0;
|
||||
|
|
|
@ -333,6 +333,7 @@ def use_windows_spawn_fix(self, platform=None):
|
|||
startupinfo=startupinfo,
|
||||
shell=False,
|
||||
env=env,
|
||||
text=True,
|
||||
)
|
||||
_, err = proc.communicate()
|
||||
rv = proc.wait()
|
||||
|
|
|
@ -904,10 +904,12 @@ void GridMapEditor::update_palette() {
|
|||
}
|
||||
|
||||
if (selected != -1 && mesh_library_palette->get_item_count() > 0) {
|
||||
mesh_library_palette->select(selected);
|
||||
// Make sure that this variable is set correctly.
|
||||
selected_palette = MIN(selected, mesh_library_palette->get_item_count() - 1);
|
||||
mesh_library_palette->select(selected_palette);
|
||||
}
|
||||
|
||||
last_mesh_library = mesh_library.operator->();
|
||||
last_mesh_library = *mesh_library;
|
||||
}
|
||||
|
||||
void GridMapEditor::edit(GridMap *p_gridmap) {
|
||||
|
|
|
@ -722,19 +722,24 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
|
|||
GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
|
||||
|
||||
String appname = ProjectSettings::get_singleton()->get("application/config/name");
|
||||
String assembly_name = ProjectSettings::get_singleton()->get_setting("mono/project/assembly_name");
|
||||
|
||||
if (assembly_name.empty()) {
|
||||
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
|
||||
if (appname_safe.empty()) {
|
||||
appname_safe = "UnnamedProject";
|
||||
}
|
||||
assembly_name = appname_safe;
|
||||
}
|
||||
|
||||
appname_safe += ".dll";
|
||||
assembly_name += ".dll";
|
||||
|
||||
if (proj_assembly) {
|
||||
String proj_asm_path = proj_assembly->get_path();
|
||||
|
||||
if (!FileAccess::exists(proj_asm_path)) {
|
||||
// Maybe it wasn't loaded from the default path, so check this as well
|
||||
proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe);
|
||||
proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(assembly_name);
|
||||
if (!FileAccess::exists(proj_asm_path))
|
||||
return false; // No assembly to load
|
||||
}
|
||||
|
@ -742,7 +747,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
|
|||
if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
|
||||
return false; // Already up to date
|
||||
} else {
|
||||
if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe)))
|
||||
if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(assembly_name)))
|
||||
return false; // No assembly to load
|
||||
}
|
||||
|
||||
|
|
|
@ -231,7 +231,7 @@ namespace GodotTools.Export
|
|||
RunLipo(new[] {CompileForArch("arm64"), CompileForArch("x86_64")}, libFilePath);
|
||||
}
|
||||
|
||||
string projectAssemblyName = GodotSharpEditor.ProjectAssemblyName;
|
||||
string projectAssemblyName = GodotSharpDirs.ProjectAssemblyName;
|
||||
string libAotName = $"lib-aot-{projectAssemblyName}";
|
||||
|
||||
string libAotXcFrameworkPath = Path.Combine(aotTempDir, $"{libAotName}.xcframework");
|
||||
|
|
|
@ -167,7 +167,7 @@ namespace GodotTools.Export
|
|||
|
||||
var assemblies = new Godot.Collections.Dictionary<string, string>();
|
||||
|
||||
string projectDllName = GodotSharpEditor.ProjectAssemblyName;
|
||||
string projectDllName = GodotSharpDirs.ProjectAssemblyName;
|
||||
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
|
||||
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
|
||||
|
||||
|
|
|
@ -39,18 +39,6 @@ namespace GodotTools
|
|||
|
||||
public bool SkipBuildBeforePlaying { get; set; } = false;
|
||||
|
||||
public static string ProjectAssemblyName
|
||||
{
|
||||
get
|
||||
{
|
||||
string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name");
|
||||
projectAssemblyName = projectAssemblyName.ToSafeDirName();
|
||||
if (string.IsNullOrEmpty(projectAssemblyName))
|
||||
projectAssemblyName = "UnnamedProject";
|
||||
return projectAssemblyName;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateProjectSolution()
|
||||
{
|
||||
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3))
|
||||
|
@ -60,7 +48,7 @@ namespace GodotTools
|
|||
string resourceDir = ProjectSettings.GlobalizePath("res://");
|
||||
|
||||
string path = resourceDir;
|
||||
string name = ProjectAssemblyName;
|
||||
string name = GodotSharpDirs.ProjectAssemblyName;
|
||||
|
||||
string guid = CsProjOperations.GenerateGameProject(path, name);
|
||||
|
||||
|
@ -375,7 +363,8 @@ namespace GodotTools
|
|||
[UsedImplicitly]
|
||||
public bool OverridesExternalEditor()
|
||||
{
|
||||
return (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None;
|
||||
return (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor") !=
|
||||
ExternalEditorId.None;
|
||||
}
|
||||
|
||||
public override bool Build()
|
||||
|
@ -396,7 +385,7 @@ namespace GodotTools
|
|||
// NOTE: The order in which changes are made to the project is important
|
||||
|
||||
// Migrate to MSBuild project Sdks style if using the old style
|
||||
ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName);
|
||||
ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, GodotSharpDirs.ProjectAssemblyName);
|
||||
|
||||
ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace GodotTools.Internals
|
|||
public static string MonoSolutionsDir => internal_MonoSolutionsDir();
|
||||
public static string BuildLogsDirs => internal_BuildLogsDirs();
|
||||
|
||||
public static string ProjectAssemblyName => internal_ProjectAssemblyName();
|
||||
public static string ProjectSlnPath => internal_ProjectSlnPath();
|
||||
public static string ProjectCsProjPath => internal_ProjectCsProjPath();
|
||||
|
||||
|
@ -74,6 +75,9 @@ namespace GodotTools.Internals
|
|||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern string internal_BuildLogsDirs();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern string internal_ProjectAssemblyName();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern string internal_ProjectSlnPath();
|
||||
|
||||
|
|
|
@ -107,6 +107,14 @@ MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() {
|
|||
#endif
|
||||
}
|
||||
|
||||
MonoString *godot_icall_GodotSharpDirs_ProjectAssemblyName() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_assembly_name());
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path());
|
||||
|
@ -388,6 +396,7 @@ void register_editor_internal_calls() {
|
|||
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", godot_icall_GodotSharpDirs_MonoLogsDir);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", godot_icall_GodotSharpDirs_MonoSolutionsDir);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", godot_icall_GodotSharpDirs_BuildLogsDirs);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectAssemblyName", godot_icall_GodotSharpDirs_ProjectAssemblyName);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", godot_icall_GodotSharpDirs_ProjectSlnPath);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", godot_icall_GodotSharpDirs_ProjectCsProjPath);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", godot_icall_GodotSharpDirs_DataEditorToolsDir);
|
||||
|
|
|
@ -102,6 +102,7 @@ public:
|
|||
String mono_solutions_dir;
|
||||
String build_logs_dir;
|
||||
|
||||
String project_assembly_name;
|
||||
String sln_filepath;
|
||||
String csproj_filepath;
|
||||
|
||||
|
@ -144,16 +145,35 @@ private:
|
|||
mono_solutions_dir = mono_user_dir.plus_file("solutions");
|
||||
build_logs_dir = mono_user_dir.plus_file("build_logs");
|
||||
|
||||
GLOBAL_DEF("mono/project/assembly_name", "");
|
||||
GLOBAL_DEF("mono/project/solution_directory", "");
|
||||
GLOBAL_DEF("mono/project/c#_project_directory", "");
|
||||
|
||||
String appname = ProjectSettings::get_singleton()->get("application/config/name");
|
||||
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
|
||||
if (appname_safe.empty()) {
|
||||
appname_safe = "UnnamedProject";
|
||||
}
|
||||
|
||||
String base_path = ProjectSettings::get_singleton()->globalize_path("res://");
|
||||
project_assembly_name = ProjectSettings::get_singleton()->get("mono/project/assembly_name");
|
||||
if (project_assembly_name.empty()) {
|
||||
project_assembly_name = appname_safe;
|
||||
ProjectSettings::get_singleton()->set("mono/project/assembly_name", project_assembly_name);
|
||||
}
|
||||
|
||||
sln_filepath = base_path.plus_file(appname_safe + ".sln");
|
||||
csproj_filepath = base_path.plus_file(appname_safe + ".csproj");
|
||||
String sln_parent_dir = ProjectSettings::get_singleton()->get("mono/project/solution_directory");
|
||||
if (sln_parent_dir.empty()) {
|
||||
sln_parent_dir = "res://";
|
||||
}
|
||||
|
||||
String csproj_parent_dir = ProjectSettings::get_singleton()->get("mono/project/c#_project_directory");
|
||||
if (csproj_parent_dir.empty()) {
|
||||
csproj_parent_dir = "res://";
|
||||
}
|
||||
|
||||
sln_filepath = ProjectSettings::get_singleton()->globalize_path(sln_parent_dir).plus_file(project_assembly_name + ".sln");
|
||||
|
||||
csproj_filepath = ProjectSettings::get_singleton()->globalize_path(csproj_parent_dir).plus_file(project_assembly_name + ".csproj");
|
||||
#endif
|
||||
|
||||
String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
|
||||
|
@ -288,6 +308,10 @@ String get_build_logs_dir() {
|
|||
return _GodotSharpDirs::get_singleton().build_logs_dir;
|
||||
}
|
||||
|
||||
String get_project_assembly_name() {
|
||||
return _GodotSharpDirs::get_singleton().project_assembly_name;
|
||||
}
|
||||
|
||||
String get_project_sln_path() {
|
||||
return _GodotSharpDirs::get_singleton().sln_filepath;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ String get_mono_logs_dir();
|
|||
String get_mono_solutions_dir();
|
||||
String get_build_logs_dir();
|
||||
|
||||
String get_project_assembly_name();
|
||||
String get_project_sln_path();
|
||||
String get_project_csproj_path();
|
||||
|
||||
|
|
|
@ -987,13 +987,15 @@ bool GDMono::_load_project_assembly() {
|
|||
if (project_assembly)
|
||||
return true;
|
||||
|
||||
String assembly_name = ProjectSettings::get_singleton()->get("mono/project/assembly_name");
|
||||
|
||||
if (assembly_name.empty()) {
|
||||
String appname = ProjectSettings::get_singleton()->get("application/config/name");
|
||||
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
|
||||
if (appname_safe.empty()) {
|
||||
appname_safe = "UnnamedProject";
|
||||
assembly_name = appname_safe;
|
||||
}
|
||||
|
||||
bool success = load_assembly(appname_safe, &project_assembly);
|
||||
bool success = load_assembly(assembly_name, &project_assembly);
|
||||
|
||||
if (success) {
|
||||
mono_assembly_set_main(project_assembly->get_assembly());
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="UPNP" inherits="Reference" version="3.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
UPNP network functions.
|
||||
Universal Plug and Play (UPnP) functions for network device discovery, querying and port forwarding.
|
||||
</brief_description>
|
||||
<description>
|
||||
Provides UPNP functionality to discover [UPNPDevice]s on the local network and execute commands on them, like managing port mappings (port forwarding) and querying the local and remote network IP address. Note that methods on this class are synchronous and block the calling thread.
|
||||
To forward a specific port:
|
||||
This class can be used to discover compatible [UPNPDevice]s on the local network and execute commands on them, like managing port mappings (for port forwarding/NAT traversal) and querying the local and remote network IP address. Note that methods on this class are synchronous and block the calling thread.
|
||||
To forward a specific port (here [code]7777[/code], note both [method discover] and [method add_port_mapping] can return errors that should be checked):
|
||||
[codeblock]
|
||||
const PORT = 7777
|
||||
var upnp = UPNP.new()
|
||||
upnp.discover(2000, 2, "InternetGatewayDevice")
|
||||
upnp.add_port_mapping(port)
|
||||
upnp.discover()
|
||||
upnp.add_port_mapping(7777)
|
||||
[/codeblock]
|
||||
To close a specific port (e.g. after you have finished using it):
|
||||
[codeblock]
|
||||
|
@ -21,7 +20,7 @@
|
|||
# Emitted when UPnP port mapping setup is completed (regardless of success or failure).
|
||||
signal upnp_completed(error)
|
||||
|
||||
# Replace this with your own server port number between 1025 and 65535.
|
||||
# Replace this with your own server port number between 1024 and 65535.
|
||||
const SERVER_PORT = 3928
|
||||
var thread = null
|
||||
|
||||
|
@ -48,6 +47,14 @@
|
|||
# Wait for thread finish here to handle game exit while the thread is running.
|
||||
thread.wait_to_finish()
|
||||
[/codeblock]
|
||||
[b]Terminology:[/b] In the context of UPnP networking, "gateway" (or "internet gateway device", short IGD) refers to network devices that allow computers in the local network to access the internet ("wide area network", WAN). These gateways are often also called "routers".
|
||||
[b]Pitfalls:[/b]
|
||||
- As explained above, these calls are blocking and shouldn't be run on the main thread, especially as they can block for multiple seconds at a time. Use threading!
|
||||
- Networking is physical and messy. Packets get lost in transit or get filtered, addresses, free ports and assigned mappings change, and devices may leave or join the network at any time. Be mindful of this, be diligent when checking and handling errors, and handle these gracefully if you can: add clear error UI, timeouts and re-try handling.
|
||||
- Port mappings may change (and be removed) at any time, and the remote/external IP address of the gateway can change likewise. You should consider re-querying the external IP and try to update/refresh the port mapping periodically (for example, every 5 minutes and on networking failures).
|
||||
- Not all devices support UPnP, and some users disable UPnP support. You need to handle this (e.g. documenting and requiring the user to manually forward ports, or adding alternative methods of NAT traversal, like a relay/mirror server, or NAT hole punching, STUN/TURN, etc.).
|
||||
- Consider what happens on mapping conflicts. Maybe multiple users on the same network would like to play your game at the same time, or maybe another application uses the same port. Make the port configurable, and optimally choose a port automatically (re-trying with a different port on failure).
|
||||
[b]Further reading:[/b] If you want to know more about UPnP (and the Internet Gateway Device (IGD) and Port Control Protocol (PCP) specifically), [url=https://en.wikipedia.org/wiki/Universal_Plug_and_Play]Wikipedia[/url] is a good first stop, the specification can be found at the [url=https://openconnectivity.org/developer/specifications/upnp-resources/upnp/]Open Connectivity Foundation[/url] and Godot's implementation is based on the [url=https://github.com/miniupnp/miniupnp]MiniUPnP client[/url].
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
|
@ -67,9 +74,11 @@
|
|||
<argument index="3" name="proto" type="String" default=""UDP"" />
|
||||
<argument index="4" name="duration" type="int" default="0" />
|
||||
<description>
|
||||
Adds a mapping to forward the external [code]port[/code] (between 1 and 65535) on the default gateway (see [method get_gateway]) to the [code]internal_port[/code] on the local machine for the given protocol [code]proto[/code] (either [code]TCP[/code] or [code]UDP[/code], with UDP being the default). If a port mapping for the given port and protocol combination already exists on that gateway device, this method tries to overwrite it. If that is not desired, you can retrieve the gateway manually with [method get_gateway] and call [method add_port_mapping] on it, if any.
|
||||
Adds a mapping to forward the external [code]port[/code] (between 1 and 65535, although recommended to use port 1024 or above) on the default gateway (see [method get_gateway]) to the [code]internal_port[/code] on the local machine for the given protocol [code]proto[/code] (either [code]TCP[/code] or [code]UDP[/code], with UDP being the default). If a port mapping for the given port and protocol combination already exists on that gateway device, this method tries to overwrite it. If that is not desired, you can retrieve the gateway manually with [method get_gateway] and call [method add_port_mapping] on it, if any. Note that forwarding a well-known port (below 1024) with UPnP may fail depending on the device.
|
||||
Depending on the gateway device, if a mapping for that port already exists, it will either be updated or it will refuse this command due to that conflict, especially if the existing mapping for that port wasn't created via UPnP or points to a different network address (or device) than this one.
|
||||
If [code]internal_port[/code] is [code]0[/code] (the default), the same port number is used for both the external and the internal port (the [code]port[/code] value).
|
||||
The description ([code]desc[/code]) is shown in some router UIs and can be used to point out which application added the mapping. The mapping's lease duration can be limited by specifying a [code]duration[/code] (in seconds). However, some routers are incompatible with one or both of these, so use with caution and add fallback logic in case of errors to retry without them if in doubt.
|
||||
The description ([code]desc[/code]) is shown in some routers management UIs and can be used to point out which application added the mapping.
|
||||
The mapping's lease [code]duration[/code] can be limited by specifying a duration in seconds. The default of [code]0[/code] means no duration, i.e. a permanent lease and notably some devices only support these permanent leases. Note that whether permanent or not, this is only a request and the gateway may still decide at any point to remove the mapping (which usually happens on a reboot of the gateway, when its external IP address changes, or on some models when it detects a port mapping has become inactive, i.e. had no traffic for multiple minutes). If not [code]0[/code] (permanent), the allowed range according to spec is between [code]120[/code] (2 minutes) and [code]86400[/code] seconds (24 hours).
|
||||
See [enum UPNPResult] for possible return values.
|
||||
</description>
|
||||
</method>
|
||||
|
@ -84,7 +93,7 @@
|
|||
<argument index="0" name="port" type="int" />
|
||||
<argument index="1" name="proto" type="String" default=""UDP"" />
|
||||
<description>
|
||||
Deletes the port mapping for the given port and protocol combination on the default gateway (see [method get_gateway]) if one exists. [code]port[/code] must be a valid port between 1 and 65535, [code]proto[/code] can be either [code]TCP[/code] or [code]UDP[/code]. See [enum UPNPResult] for possible return values.
|
||||
Deletes the port mapping for the given port and protocol combination on the default gateway (see [method get_gateway]) if one exists. [code]port[/code] must be a valid port between 1 and 65535, [code]proto[/code] can be either [code]TCP[/code] or [code]UDP[/code]. May be refused for mappings pointing to addresses other than this one, for well-known ports (below 1024), or for mappings not added via UPnP. See [enum UPNPResult] for possible return values.
|
||||
</description>
|
||||
</method>
|
||||
<method name="discover">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="UPNPDevice" inherits="Reference" version="3.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
UPNP device.
|
||||
Universal Plug and Play (UPnP) device.
|
||||
</brief_description>
|
||||
<description>
|
||||
UPNP device. See [UPNP] for UPNP discovery and utility functions. Provides low-level access to UPNP control commands. Allows to manage port mappings (port forwarding) and to query network information of the device (like local and external IP address and status). Note that methods on this class are synchronous and block the calling thread.
|
||||
Universal Plug and Play (UPnP) device. See [UPNP] for UPnP discovery and utility functions. Provides low-level access to UPNP control commands. Allows to manage port mappings (port forwarding) and to query network information of the device (like local and external IP address and status). Note that methods on this class are synchronous and block the calling thread.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
|
|
|
@ -518,10 +518,10 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons
|
|||
t = Variant::REAL;
|
||||
} break;
|
||||
case MATH_ABS: {
|
||||
t = Variant::NIL;
|
||||
t = Variant::REAL;
|
||||
} break;
|
||||
case MATH_SIGN: {
|
||||
t = Variant::NIL;
|
||||
t = Variant::REAL;
|
||||
} break;
|
||||
case MATH_POW:
|
||||
case MATH_LOG:
|
||||
|
@ -547,7 +547,6 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons
|
|||
case MATH_MOVE_TOWARD:
|
||||
case MATH_DECTIME: {
|
||||
t = Variant::REAL;
|
||||
|
||||
} break;
|
||||
case MATH_RANDOMIZE: {
|
||||
} break;
|
||||
|
@ -584,34 +583,29 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons
|
|||
case LOGIC_MAX:
|
||||
case LOGIC_MIN:
|
||||
case LOGIC_CLAMP: {
|
||||
t = Variant::REAL;
|
||||
} break;
|
||||
|
||||
case LOGIC_NEAREST_PO2: {
|
||||
t = Variant::NIL;
|
||||
t = Variant::INT;
|
||||
} break;
|
||||
case OBJ_WEAKREF: {
|
||||
t = Variant::OBJECT;
|
||||
|
||||
} break;
|
||||
case FUNC_FUNCREF: {
|
||||
t = Variant::OBJECT;
|
||||
|
||||
} break;
|
||||
case TYPE_CONVERT: {
|
||||
} break;
|
||||
case TEXT_ORD:
|
||||
case TYPE_OF: {
|
||||
t = Variant::INT;
|
||||
|
||||
} break;
|
||||
case TYPE_EXISTS: {
|
||||
t = Variant::BOOL;
|
||||
|
||||
} break;
|
||||
case TEXT_CHAR:
|
||||
case TEXT_STR: {
|
||||
t = Variant::STRING;
|
||||
|
||||
} break;
|
||||
case TEXT_PRINT: {
|
||||
} break;
|
||||
|
@ -630,7 +624,6 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons
|
|||
} else {
|
||||
t = Variant::BOOL;
|
||||
}
|
||||
|
||||
} break;
|
||||
case BYTES_TO_VAR: {
|
||||
if (p_idx == 1) {
|
||||
|
@ -1218,10 +1211,6 @@ public:
|
|||
|
||||
VisualScriptBuiltinFunc::BuiltinFunc func;
|
||||
|
||||
//virtual int get_working_memory_size() const { return 0; }
|
||||
//virtual bool is_output_port_unsequenced(int p_idx) const { return false; }
|
||||
//virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; }
|
||||
|
||||
virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Variant::CallError &r_error, String &r_error_str) {
|
||||
VisualScriptBuiltinFunc::exec_func(func, p_inputs, p_outputs[0], r_error, r_error_str);
|
||||
return 0;
|
||||
|
|
|
@ -134,6 +134,20 @@ String DirAccessJAndroid::get_drive(int p_drive) {
|
|||
}
|
||||
}
|
||||
|
||||
String DirAccessJAndroid::get_current_dir() {
|
||||
String base = _get_root_path();
|
||||
String bd = current_dir;
|
||||
if (base != "") {
|
||||
bd = current_dir.replace_first(base, "");
|
||||
}
|
||||
|
||||
if (bd.begins_with("/")) {
|
||||
return _get_root_string() + bd.substr(1, bd.length());
|
||||
} else {
|
||||
return _get_root_string() + bd;
|
||||
}
|
||||
}
|
||||
|
||||
Error DirAccessJAndroid::change_dir(String p_dir) {
|
||||
String new_dir = get_absolute_path(p_dir);
|
||||
if (new_dir == current_dir) {
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
virtual String get_drive(int p_drive) override;
|
||||
|
||||
virtual Error change_dir(String p_dir) override; ///< can be relative or absolute, return false on success
|
||||
virtual String get_current_dir() override; ///< return current dir location
|
||||
|
||||
virtual bool file_exists(String p_file) override;
|
||||
virtual bool dir_exists(String p_dir) override;
|
||||
|
|
|
@ -228,6 +228,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal
|
|||
static const int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
|
||||
static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
|
||||
EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
|
||||
|
||||
|
@ -259,7 +260,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
// Check for devices updates
|
||||
String adb = get_adb_path();
|
||||
if (FileAccess::exists(adb)) {
|
||||
|
@ -373,7 +373,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
|
|||
|
||||
ea->device_lock.unlock();
|
||||
}
|
||||
#endif
|
||||
|
||||
uint64_t sleep = 300'000;
|
||||
uint64_t wait = 3'000'000;
|
||||
|
@ -386,7 +385,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) {
|
||||
String adb = get_adb_path();
|
||||
if (!FileAccess::exists(adb)) {
|
||||
|
@ -397,8 +395,8 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
|
|||
args.push_back("kill-server");
|
||||
OS::get_singleton()->execute(adb, args, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
String EditorExportPlatformAndroid::get_project_name(const String &p_name) const {
|
||||
String aname;
|
||||
|
@ -3438,10 +3436,14 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
|
|||
|
||||
devices_changed.set();
|
||||
plugins_changed.set();
|
||||
#ifndef ANDROID_ENABLED
|
||||
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
|
||||
#ifndef ANDROID_ENABLED
|
||||
quit_request.set();
|
||||
check_for_changes_thread.wait_to_finish();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -98,10 +98,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
|
|||
Vector<Device> devices;
|
||||
SafeFlag devices_changed;
|
||||
Mutex device_lock;
|
||||
#ifndef ANDROID_ENABLED
|
||||
Thread check_for_changes_thread;
|
||||
SafeFlag quit_request;
|
||||
|
||||
static void _check_for_changes_poll_thread(void *ud);
|
||||
#endif
|
||||
|
||||
String get_project_name(const String &p_name) const;
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_seek_end = nullptr;
|
|||
jmethodID FileAccessFilesystemJAndroid::_file_read = nullptr;
|
||||
jmethodID FileAccessFilesystemJAndroid::_file_tell = nullptr;
|
||||
jmethodID FileAccessFilesystemJAndroid::_file_eof = nullptr;
|
||||
jmethodID FileAccessFilesystemJAndroid::_file_set_eof = nullptr;
|
||||
jmethodID FileAccessFilesystemJAndroid::_file_close = nullptr;
|
||||
jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr;
|
||||
jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr;
|
||||
|
@ -161,6 +162,16 @@ bool FileAccessFilesystemJAndroid::eof_reached() const {
|
|||
}
|
||||
}
|
||||
|
||||
void FileAccessFilesystemJAndroid::_set_eof(bool eof) {
|
||||
if (_file_set_eof) {
|
||||
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
|
||||
|
||||
JNIEnv *env = get_jni_env();
|
||||
ERR_FAIL_COND(env == nullptr);
|
||||
env->CallVoidMethod(file_access_handler, _file_set_eof, id, eof);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t FileAccessFilesystemJAndroid::get_8() const {
|
||||
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
|
||||
uint8_t byte;
|
||||
|
@ -183,6 +194,7 @@ String FileAccessFilesystemJAndroid::get_line() const {
|
|||
while (true) {
|
||||
size_t line_buffer_size = MIN(buffer_size_limit, file_size - get_position());
|
||||
if (line_buffer_size <= 0) {
|
||||
const_cast<FileAccessFilesystemJAndroid *>(this)->_set_eof(true);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -309,6 +321,7 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) {
|
|||
_file_get_size = env->GetMethodID(cls, "fileGetSize", "(I)J");
|
||||
_file_tell = env->GetMethodID(cls, "fileGetPosition", "(I)J");
|
||||
_file_eof = env->GetMethodID(cls, "isFileEof", "(I)Z");
|
||||
_file_set_eof = env->GetMethodID(cls, "setFileEof", "(IZ)V");
|
||||
_file_seek = env->GetMethodID(cls, "fileSeek", "(IJ)V");
|
||||
_file_seek_end = env->GetMethodID(cls, "fileSeekFromEnd", "(IJ)V");
|
||||
_file_read = env->GetMethodID(cls, "fileRead", "(ILjava/nio/ByteBuffer;)I");
|
||||
|
|
|
@ -44,6 +44,7 @@ class FileAccessFilesystemJAndroid : public FileAccess {
|
|||
static jmethodID _file_seek_end;
|
||||
static jmethodID _file_tell;
|
||||
static jmethodID _file_eof;
|
||||
static jmethodID _file_set_eof;
|
||||
static jmethodID _file_read;
|
||||
static jmethodID _file_write;
|
||||
static jmethodID _file_flush;
|
||||
|
@ -55,6 +56,8 @@ class FileAccessFilesystemJAndroid : public FileAccess {
|
|||
String absolute_path;
|
||||
String path_src;
|
||||
|
||||
void _set_eof(bool eof);
|
||||
|
||||
public:
|
||||
virtual Error _open(const String &p_path, int p_mode_flags) override; ///< open a file
|
||||
virtual void close() override; ///< close a file
|
||||
|
|
|
@ -104,7 +104,6 @@ internal abstract class DataAccess(private val filePath: String) {
|
|||
|
||||
protected abstract val fileChannel: FileChannel
|
||||
internal var endOfFile = false
|
||||
private set
|
||||
|
||||
fun close() {
|
||||
try {
|
||||
|
@ -125,9 +124,7 @@ internal abstract class DataAccess(private val filePath: String) {
|
|||
fun seek(position: Long) {
|
||||
try {
|
||||
fileChannel.position(position)
|
||||
if (position <= size()) {
|
||||
endOfFile = false
|
||||
}
|
||||
endOfFile = position >= fileChannel.size()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Exception when seeking file $filePath.", e)
|
||||
}
|
||||
|
@ -161,8 +158,7 @@ internal abstract class DataAccess(private val filePath: String) {
|
|||
fun read(buffer: ByteBuffer): Int {
|
||||
return try {
|
||||
val readBytes = fileChannel.read(buffer)
|
||||
endOfFile = readBytes == -1
|
||||
|| (fileChannel.position() >= fileChannel.size() && fileChannel.size() > 0)
|
||||
endOfFile = readBytes == -1 || (fileChannel.position() >= fileChannel.size())
|
||||
if (readBytes == -1) {
|
||||
0
|
||||
} else {
|
||||
|
|
|
@ -194,6 +194,11 @@ class FileAccessHandler(val context: Context) {
|
|||
return files[fileId].endOfFile
|
||||
}
|
||||
|
||||
fun setFileEof(fileId: Int, eof: Boolean) {
|
||||
val file = files[fileId] ?: return
|
||||
file.endOfFile = eof
|
||||
}
|
||||
|
||||
fun fileClose(fileId: Int) {
|
||||
if (hasFileId(fileId)) {
|
||||
files[fileId].close()
|
||||
|
|
|
@ -57,8 +57,10 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
|
|||
|
||||
// Plugins
|
||||
SafeFlag plugins_changed;
|
||||
#ifndef ANDROID_ENABLED
|
||||
Thread check_for_changes_thread;
|
||||
SafeFlag quit_request;
|
||||
#endif
|
||||
Mutex plugins_lock;
|
||||
Vector<PluginConfigIOS> plugins;
|
||||
|
||||
|
@ -141,6 +143,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
static void _check_for_changes_poll_thread(void *ud) {
|
||||
EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud;
|
||||
|
||||
|
@ -176,6 +179,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
|
||||
|
@ -409,27 +413,27 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
|
|||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), ""));
|
||||
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with Retina display
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with Retina HD display
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPhone/iPod Touch with Retina display
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPhone with Retina HD display
|
||||
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with Retina display
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPad
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPad with Retina display
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPad Pro
|
||||
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // App Store
|
||||
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with Retina display
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Spotlight
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Spotlight on devices with Retina display
|
||||
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color()));
|
||||
|
||||
for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) {
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -660,26 +664,37 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
|
|||
String locale_files;
|
||||
Vector<String> translations = ProjectSettings::get_singleton()->get("locale/translations");
|
||||
if (translations.size() > 0) {
|
||||
Set<String> languages;
|
||||
for (int j = 0; j < translations.size(); j++) {
|
||||
Ref<Translation> tr = ResourceLoader::load(translations[j]);
|
||||
if (tr.is_valid()) {
|
||||
String lang = tr->get_locale();
|
||||
locale_files += "D0BCFE4518AEBDA2004A" + itos(j).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"<group>\"; };";
|
||||
if (tr.is_valid() && tr->get_locale() != "en") {
|
||||
languages.insert(tr->get_locale());
|
||||
}
|
||||
}
|
||||
int index = 0;
|
||||
for (const Set<String>::Element *E = languages.front(); E; E = E->next()) {
|
||||
const String &lang = E->get();
|
||||
locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"<group>\"; };\n";
|
||||
index++;
|
||||
}
|
||||
}
|
||||
strnew += lines[i].replace("$pbx_locale_file_reference", locale_files);
|
||||
} else if (lines[i].find("$pbx_locale_build_reference") != -1) {
|
||||
String locale_files;
|
||||
Vector<String> translations = ProjectSettings::get_singleton()->get("locale/translations");
|
||||
if (translations.size() > 0) {
|
||||
Set<String> languages;
|
||||
for (int j = 0; j < translations.size(); j++) {
|
||||
Ref<Translation> tr = ResourceLoader::load(translations[j]);
|
||||
if (tr.is_valid()) {
|
||||
String lang = tr->get_locale();
|
||||
locale_files += "D0BCFE4518AEBDA2004A" + itos(j).pad_zeros(4) + " /* " + lang + " */,";
|
||||
if (tr.is_valid() && tr->get_locale() != "en") {
|
||||
languages.insert(tr->get_locale());
|
||||
}
|
||||
}
|
||||
int index = 0;
|
||||
for (const Set<String>::Element *E = languages.front(); E; E = E->next()) {
|
||||
locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + E->get() + " */,\n";
|
||||
index++;
|
||||
}
|
||||
}
|
||||
strnew += lines[i].replace("$pbx_locale_build_reference", locale_files);
|
||||
} else {
|
||||
|
@ -777,26 +792,27 @@ struct IconInfo {
|
|||
const char *actual_size_side;
|
||||
const char *scale;
|
||||
const char *unscaled_size;
|
||||
const bool force_opaque;
|
||||
};
|
||||
|
||||
static const IconInfo icon_infos[] = {
|
||||
// Home screen on iPhone
|
||||
{ "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60" },
|
||||
{ "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40" },
|
||||
{ "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60" },
|
||||
{ "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60", false },
|
||||
{ "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40", false },
|
||||
{ "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60", false },
|
||||
|
||||
// Home screen on iPad
|
||||
{ "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76" },
|
||||
{ "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76" },
|
||||
{ "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5" },
|
||||
{ "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76", false },
|
||||
{ "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76", false },
|
||||
{ "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5", false },
|
||||
|
||||
// App Store
|
||||
{ "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024" },
|
||||
{ "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", true },
|
||||
|
||||
// Spotlight
|
||||
{ "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40" },
|
||||
{ "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40" },
|
||||
{ "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40" }
|
||||
{ "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40", false },
|
||||
{ "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40", false },
|
||||
{ "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40", false }
|
||||
};
|
||||
|
||||
Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) {
|
||||
|
@ -816,14 +832,20 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
|
|||
Ref<Image> img = memnew(Image);
|
||||
Error err = ImageLoader::load_image(icon_path, img);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
|
||||
memdelete(da);
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
|
||||
return ERR_UNCONFIGURED;
|
||||
}
|
||||
if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
|
||||
memdelete(da);
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
|
||||
return ERR_UNCONFIGURED;
|
||||
}
|
||||
img->resize(side_size, side_size);
|
||||
err = img->save_png(p_iconset_dir + info.export_name);
|
||||
if (err) {
|
||||
String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
|
||||
ERR_PRINT(err_str.utf8().get_data());
|
||||
memdelete(da);
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
|
@ -831,11 +853,17 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
|
|||
Ref<Image> img = memnew(Image);
|
||||
Error err = ImageLoader::load_image(icon_path, img);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
|
||||
memdelete(da);
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
|
||||
return ERR_UNCONFIGURED;
|
||||
}
|
||||
if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
|
||||
memdelete(da);
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
|
||||
return ERR_UNCONFIGURED;
|
||||
}
|
||||
if (img->get_width() != side_size || img->get_height() != side_size) {
|
||||
WARN_PRINT("Icon (" + String(info.preset_key) + "): '" + icon_path + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(side_size) + "x" + String::num_int64(side_size) + ".");
|
||||
add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2(side_size, side_size)));
|
||||
img->resize(side_size, side_size);
|
||||
err = img->save_png(p_iconset_dir + info.export_name);
|
||||
} else {
|
||||
|
@ -844,8 +872,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
|
|||
|
||||
if (err) {
|
||||
memdelete(da);
|
||||
String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
|
||||
ERR_PRINT(err_str.utf8().get_data());
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
@ -1905,19 +1932,23 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
|
|||
f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";");
|
||||
}
|
||||
|
||||
for (int i = 0; i < translations.size(); i++) {
|
||||
Ref<Translation> tr = ResourceLoader::load(translations[i]);
|
||||
if (tr.is_valid()) {
|
||||
String fname = dest_dir + binary_name + "/" + tr->get_locale() + ".lproj";
|
||||
Set<String> languages;
|
||||
for (int j = 0; j < translations.size(); j++) {
|
||||
Ref<Translation> tr = ResourceLoader::load(translations[j]);
|
||||
if (tr.is_valid() && tr->get_locale() != "en") {
|
||||
languages.insert(tr->get_locale());
|
||||
}
|
||||
}
|
||||
for (const Set<String>::Element *E = languages.front(); E; E = E->next()) {
|
||||
String fname = dest_dir + binary_name + "/" + E->get() + ".lproj";
|
||||
tmp_app_path->make_dir_recursive(fname);
|
||||
FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
|
||||
String prop = "application/config/name_" + tr->get_locale();
|
||||
String prop = "application/config/name_" + E->get();
|
||||
if (ProjectSettings::get_singleton()->has_setting(prop)) {
|
||||
f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get(prop).operator String() + "\";");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy project static libs to the project
|
||||
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
||||
|
@ -2124,12 +2155,16 @@ EditorExportPlatformIOS::EditorExportPlatformIOS() {
|
|||
|
||||
plugins_changed.set();
|
||||
|
||||
#ifndef ANDROID_ENABLED
|
||||
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
EditorExportPlatformIOS::~EditorExportPlatformIOS() {
|
||||
#ifndef ANDROID_ENABLED
|
||||
quit_request.set();
|
||||
check_for_changes_thread.wait_to_finish();
|
||||
#endif
|
||||
}
|
||||
|
||||
void register_iphone_exporter() {
|
||||
|
|
|
@ -33,8 +33,6 @@
|
|||
#define GLES2_INCLUDE_H <ES2/gl.h>
|
||||
#define GLES3_INCLUDE_H <ES3/gl.h>
|
||||
|
||||
#define PLATFORM_REFCOUNT
|
||||
|
||||
#define PTHREAD_RENAME_SELF
|
||||
|
||||
#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
|
||||
|
|
|
@ -896,19 +896,23 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
|
|||
f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";");
|
||||
}
|
||||
|
||||
for (int i = 0; i < translations.size(); i++) {
|
||||
Ref<Translation> tr = ResourceLoader::load(translations[i]);
|
||||
if (tr.is_valid()) {
|
||||
String fname = tmp_app_path_name + "/Contents/Resources/" + tr->get_locale() + ".lproj";
|
||||
Set<String> languages;
|
||||
for (int j = 0; j < translations.size(); j++) {
|
||||
Ref<Translation> tr = ResourceLoader::load(translations[j]);
|
||||
if (tr.is_valid() && tr->get_locale() != "en") {
|
||||
languages.insert(tr->get_locale());
|
||||
}
|
||||
}
|
||||
for (const Set<String>::Element *E = languages.front(); E; E = E->next()) {
|
||||
String fname = tmp_app_path_name + "/Contents/Resources/" + E->get() + ".lproj";
|
||||
tmp_app_dir->make_dir_recursive(fname);
|
||||
FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
|
||||
String prop = "application/config/name_" + tr->get_locale();
|
||||
String prop = "application/config/name_" + E->get();
|
||||
if (ProjectSettings::get_singleton()->has_setting(prop)) {
|
||||
f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get(prop).operator String() + "\";");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now process our template.
|
||||
bool found_binary = false;
|
||||
|
|
|
@ -405,7 +405,9 @@ def configure(env):
|
|||
import subprocess
|
||||
import re
|
||||
|
||||
linker_version_str = subprocess.check_output([env.subst(env["LINK"]), "-Wl,--version"]).decode("utf-8")
|
||||
linker_version_str = subprocess.check_output(
|
||||
[env.subst(env["LINK"]), "-Wl,--version"] + env.subst(env["LINKFLAGS"])
|
||||
).decode("utf-8")
|
||||
gnu_ld_version = re.search("^GNU ld [^$]*(\d+\.\d+)$", linker_version_str, re.MULTILINE)
|
||||
if not gnu_ld_version:
|
||||
print(
|
||||
|
|
|
@ -355,13 +355,13 @@ void Line2D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_points", "points"), &Line2D::set_points);
|
||||
ClassDB::bind_method(D_METHOD("get_points"), &Line2D::get_points);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_point_position", "i", "position"), &Line2D::set_point_position);
|
||||
ClassDB::bind_method(D_METHOD("get_point_position", "i"), &Line2D::get_point_position);
|
||||
ClassDB::bind_method(D_METHOD("set_point_position", "index", "position"), &Line2D::set_point_position);
|
||||
ClassDB::bind_method(D_METHOD("get_point_position", "index"), &Line2D::get_point_position);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_point_count"), &Line2D::get_point_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("add_point", "position", "at_position"), &Line2D::add_point, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("remove_point", "i"), &Line2D::remove_point);
|
||||
ClassDB::bind_method(D_METHOD("add_point", "position", "index"), &Line2D::add_point, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("remove_point", "index"), &Line2D::remove_point);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("clear_points"), &Line2D::clear_points);
|
||||
|
||||
|
|
|
@ -139,7 +139,12 @@ void NavigationObstacle2D::set_navigation(Navigation2D *p_nav) {
|
|||
}
|
||||
|
||||
navigation = p_nav;
|
||||
Navigation2DServer::get_singleton()->agent_set_map(agent, navigation == nullptr ? RID() : navigation->get_rid());
|
||||
|
||||
if (navigation != nullptr) {
|
||||
Navigation2DServer::get_singleton()->agent_set_map(agent, navigation->get_rid());
|
||||
} else if (parent_node2d && parent_node2d->is_inside_tree()) {
|
||||
Navigation2DServer::get_singleton()->agent_set_map(agent, parent_node2d->get_world_2d()->get_navigation_map());
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_navigation_node(Node *p_nav) {
|
||||
|
|
|
@ -419,5 +419,7 @@ Particles2D::Particles2D() {
|
|||
}
|
||||
|
||||
Particles2D::~Particles2D() {
|
||||
if (particles.is_valid()) {
|
||||
VS::get_singleton()->free(particles);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,7 +179,9 @@ GIProbeData::GIProbeData() {
|
|||
}
|
||||
|
||||
GIProbeData::~GIProbeData() {
|
||||
if (probe.is_valid()) {
|
||||
VS::get_singleton()->free(probe);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
|
@ -527,5 +529,7 @@ GIProbe::GIProbe() {
|
|||
}
|
||||
|
||||
GIProbe::~GIProbe() {
|
||||
if (gi_probe.is_valid()) {
|
||||
VS::get_singleton()->free(gi_probe);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,7 +145,12 @@ void NavigationObstacle::set_navigation(Navigation *p_nav) {
|
|||
}
|
||||
|
||||
navigation = p_nav;
|
||||
NavigationServer::get_singleton()->agent_set_map(agent, navigation == nullptr ? RID() : navigation->get_rid());
|
||||
|
||||
if (navigation != nullptr) {
|
||||
NavigationServer::get_singleton()->agent_set_map(agent, navigation->get_rid());
|
||||
} else if (parent_spatial && parent_spatial->is_inside_tree()) {
|
||||
NavigationServer::get_singleton()->agent_set_map(agent, parent_spatial->get_world()->get_navigation_map());
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationObstacle::set_navigation_node(Node *p_nav) {
|
||||
|
|
|
@ -416,5 +416,7 @@ Particles::Particles() {
|
|||
}
|
||||
|
||||
Particles::~Particles() {
|
||||
if (particles.is_valid()) {
|
||||
VS::get_singleton()->free(particles);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -962,8 +962,13 @@ Ref<KinematicCollision> KinematicBody::_move(const Vector3 &p_motion, bool p_inf
|
|||
|
||||
bool collided = move_and_collide(p_motion, p_infinite_inertia, col, p_exclude_raycast_shapes, p_test_only);
|
||||
|
||||
// Ugly hack as a hot fix, 65b3200 fix an issue but cause a problem with Bullet that broke games using Bullet.
|
||||
// The bug is something internal to Bullet, seems to be related to the Bullet’s margin. As not proper fix was found yet,
|
||||
// this temporary solution solves the issue for games using Bullet.
|
||||
bool is_bullet = PhysicsServerManager::current_server_id != 0;
|
||||
|
||||
// Don't report collision when the whole motion is done.
|
||||
if (collided && col.collision_safe_fraction < 1) {
|
||||
if (collided && (col.collision_safe_fraction < 1 || is_bullet)) {
|
||||
// Create a new instance when the cached reference is invalid or still in use in script.
|
||||
if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) {
|
||||
motion_cache.instance();
|
||||
|
|
|
@ -1052,13 +1052,15 @@ void AnimationPlayer::rename_animation(const StringName &p_name, const StringNam
|
|||
bool AnimationPlayer::has_animation(const StringName &p_name) const {
|
||||
return animation_set.has(p_name);
|
||||
}
|
||||
|
||||
Ref<Animation> AnimationPlayer::get_animation(const StringName &p_name) const {
|
||||
ERR_FAIL_COND_V(!animation_set.has(p_name), Ref<Animation>());
|
||||
ERR_FAIL_COND_V_MSG(!animation_set.has(p_name), Ref<Animation>(), vformat("Animation not found: \"%s\".", p_name));
|
||||
|
||||
const AnimationData &data = animation_set[p_name];
|
||||
|
||||
return data.animation;
|
||||
}
|
||||
|
||||
void AnimationPlayer::get_animation_list(List<StringName> *p_animations) const {
|
||||
List<String> anims;
|
||||
|
||||
|
|
|
@ -357,6 +357,9 @@ bool AnimationNode::is_path_filtered(const NodePath &p_path) const {
|
|||
}
|
||||
|
||||
bool AnimationNode::has_filter() const {
|
||||
if (get_script_instance()) {
|
||||
return get_script_instance()->call("has_filter");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -427,7 +430,7 @@ void AnimationNode::_bind_methods() {
|
|||
}
|
||||
BIND_VMETHOD(MethodInfo("process", PropertyInfo(Variant::REAL, "time"), PropertyInfo(Variant::BOOL, "seek")));
|
||||
BIND_VMETHOD(MethodInfo(Variant::STRING, "get_caption"));
|
||||
BIND_VMETHOD(MethodInfo(Variant::STRING, "has_filter"));
|
||||
BIND_VMETHOD(MethodInfo(Variant::BOOL, "has_filter"));
|
||||
|
||||
ADD_SIGNAL(MethodInfo("removed_from_graph"));
|
||||
|
||||
|
|
|
@ -754,11 +754,7 @@ void ItemList::_notification(int p_what) {
|
|||
scroll_bar->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, -bg->get_margin(MARGIN_BOTTOM));
|
||||
|
||||
Size2 size = get_size();
|
||||
|
||||
int width = size.width - bg->get_minimum_size().width;
|
||||
if (scroll_bar->is_visible()) {
|
||||
width -= mw;
|
||||
}
|
||||
|
||||
draw_style_box(bg, Rect2(Point2(), size));
|
||||
|
||||
|
@ -917,6 +913,10 @@ void ItemList::_notification(int p_what) {
|
|||
shape_changed = false;
|
||||
}
|
||||
|
||||
if (scroll_bar->is_visible()) {
|
||||
width -= mw;
|
||||
}
|
||||
|
||||
//ensure_selected_visible needs to be checked before we draw the list.
|
||||
if (ensure_selected_visible && current >= 0 && current < items.size()) {
|
||||
Rect2 r = items[current].rect_cache;
|
||||
|
|
|
@ -223,6 +223,7 @@ void PopupMenu::_scroll(float p_factor, const Point2 &p_over) {
|
|||
void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND(p_event.is_null());
|
||||
|
||||
if (!items.empty()) {
|
||||
if (p_event->is_action("ui_down") && p_event->is_pressed()) {
|
||||
int search_from = mouse_over + 1;
|
||||
if (search_from >= items.size()) {
|
||||
|
@ -312,6 +313,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
|
|||
accept_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> b = p_event;
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
|
|||
|
||||
if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
|
||||
if (rb_hover != -1) {
|
||||
// Right mouse button pressed.
|
||||
// Right tab button pressed.
|
||||
emit_signal("right_button_pressed", rb_hover);
|
||||
}
|
||||
|
||||
|
@ -955,6 +955,8 @@ void Tabs::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &Tabs::get_tab_title);
|
||||
ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &Tabs::set_tab_icon);
|
||||
ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &Tabs::get_tab_icon);
|
||||
ClassDB::bind_method(D_METHOD("set_tab_button_icon", "tab_idx", "icon"), &Tabs::set_tab_right_button);
|
||||
ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &Tabs::get_tab_right_button);
|
||||
ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &Tabs::set_tab_disabled);
|
||||
ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &Tabs::get_tab_disabled);
|
||||
ClassDB::bind_method(D_METHOD("remove_tab", "tab_idx"), &Tabs::remove_tab);
|
||||
|
|
|
@ -6341,7 +6341,8 @@ void TextEdit::fold_line(int p_line) {
|
|||
int last_line = start_indent;
|
||||
for (int i = p_line + 1; i < text.size(); i++) {
|
||||
if (text[i].strip_edges().size() != 0) {
|
||||
if (is_line_comment(i)) {
|
||||
if (is_line_comment(i) && get_indent_level(i) <= start_indent) {
|
||||
// Checked indent to make sure indented comments that finish a code block are folded.
|
||||
continue;
|
||||
} else if (get_indent_level(i) > start_indent) {
|
||||
last_line = i;
|
||||
|
|
|
@ -1177,7 +1177,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
|
|||
}
|
||||
p_item->set_meta("__focus_rect", Rect2(r.position, r.size));
|
||||
|
||||
if (p_item->cells[i].selected) {
|
||||
if (select_mode != SELECT_ROW && p_item->cells[i].selected) {
|
||||
if (has_focus()) {
|
||||
cache.selected_focus->draw(ci, r);
|
||||
} else {
|
||||
|
|
|
@ -3031,7 +3031,7 @@ String Viewport::get_configuration_warning() const {
|
|||
warning += TTR("The Viewport size must be greater than or equal to 2 pixels on both dimensions to render anything.");
|
||||
}
|
||||
|
||||
if (hdr && (usage == USAGE_2D || usage == USAGE_2D_NO_SAMPLING)) {
|
||||
if (!VisualServer::get_singleton()->is_low_end() && hdr && (usage == USAGE_2D || usage == USAGE_2D_NO_SAMPLING)) {
|
||||
if (warning != String()) {
|
||||
warning += "\n\n";
|
||||
}
|
||||
|
|
|
@ -962,7 +962,7 @@ PoolVector2Array Curve2D::tessellate(int p_max_stages, float p_tolerance) const
|
|||
|
||||
void Curve2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_point_count"), &Curve2D::get_point_count);
|
||||
ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "at_position"), &Curve2D::add_point, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "index"), &Curve2D::add_point, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve2D::set_point_position);
|
||||
ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve2D::get_point_position);
|
||||
ClassDB::bind_method(D_METHOD("set_point_in", "idx", "position"), &Curve2D::set_point_in);
|
||||
|
@ -1626,7 +1626,7 @@ PoolVector3Array Curve3D::tessellate(int p_max_stages, float p_tolerance) const
|
|||
|
||||
void Curve3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_point_count"), &Curve3D::get_point_count);
|
||||
ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "at_position"), &Curve3D::add_point, DEFVAL(Vector3()), DEFVAL(Vector3()), DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "index"), &Curve3D::add_point, DEFVAL(Vector3()), DEFVAL(Vector3()), DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve3D::set_point_position);
|
||||
ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve3D::get_point_position);
|
||||
ClassDB::bind_method(D_METHOD("set_point_tilt", "idx", "tilt"), &Curve3D::set_point_tilt);
|
||||
|
|
|
@ -523,7 +523,7 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharT
|
|||
cpos.y -= font->get_ascent();
|
||||
cpos.y += ch->v_align;
|
||||
Color modulate = p_modulate;
|
||||
if (FT_HAS_COLOR(font->face)) {
|
||||
if (font->textures[ch->texture_idx].texture->get_format() == Image::FORMAT_RGBA8) {
|
||||
modulate.r = modulate.g = modulate.b = 1.0;
|
||||
}
|
||||
RID texture = font->textures[ch->texture_idx].texture->get_rid();
|
||||
|
|
|
@ -34,9 +34,8 @@
|
|||
#include "core/project_settings.h"
|
||||
|
||||
Error AudioDriverDummy::init() {
|
||||
active = false;
|
||||
thread_exited = false;
|
||||
exit_thread = false;
|
||||
active.clear();
|
||||
exit_thread.clear();
|
||||
samples_in = nullptr;
|
||||
|
||||
mix_rate = GLOBAL_GET("audio/mix_rate");
|
||||
|
@ -58,23 +57,23 @@ void AudioDriverDummy::thread_func(void *p_udata) {
|
|||
|
||||
uint64_t usdelay = (ad->buffer_frames / float(ad->mix_rate)) * 1000000;
|
||||
|
||||
while (!ad->exit_thread) {
|
||||
if (ad->active) {
|
||||
while (!ad->exit_thread.is_set()) {
|
||||
if (ad->active.is_set()) {
|
||||
ad->lock();
|
||||
ad->start_counting_ticks();
|
||||
|
||||
ad->audio_server_process(ad->buffer_frames, ad->samples_in);
|
||||
|
||||
ad->stop_counting_ticks();
|
||||
ad->unlock();
|
||||
};
|
||||
|
||||
OS::get_singleton()->delay_usec(usdelay);
|
||||
};
|
||||
|
||||
ad->thread_exited = true;
|
||||
};
|
||||
|
||||
void AudioDriverDummy::start() {
|
||||
active = true;
|
||||
active.set();
|
||||
};
|
||||
|
||||
int AudioDriverDummy::get_mix_rate() const {
|
||||
|
@ -94,7 +93,7 @@ void AudioDriverDummy::unlock() {
|
|||
};
|
||||
|
||||
void AudioDriverDummy::finish() {
|
||||
exit_thread = true;
|
||||
exit_thread.set();
|
||||
thread.wait_to_finish();
|
||||
|
||||
if (samples_in) {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/safe_refcount.h"
|
||||
|
||||
class AudioDriverDummy : public AudioDriver {
|
||||
Thread thread;
|
||||
|
@ -50,9 +51,8 @@ class AudioDriverDummy : public AudioDriver {
|
|||
|
||||
int channels;
|
||||
|
||||
bool active;
|
||||
bool thread_exited;
|
||||
mutable bool exit_thread;
|
||||
SafeFlag active;
|
||||
SafeFlag exit_thread;
|
||||
|
||||
public:
|
||||
const char *get_name() const {
|
||||
|
|
|
@ -175,7 +175,7 @@ void Navigation2DServer::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("map_create"), &Navigation2DServer::map_create);
|
||||
ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &Navigation2DServer::map_set_active);
|
||||
ClassDB::bind_method(D_METHOD("map_is_active", "nap"), &Navigation2DServer::map_is_active);
|
||||
ClassDB::bind_method(D_METHOD("map_is_active", "map"), &Navigation2DServer::map_is_active);
|
||||
ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &Navigation2DServer::map_set_cell_size);
|
||||
ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &Navigation2DServer::map_get_cell_size);
|
||||
ClassDB::bind_method(D_METHOD("map_set_cell_height", "map", "cell_height"), &Navigation2DServer::map_set_cell_size);
|
||||
|
|
|
@ -37,7 +37,7 @@ void NavigationServer::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer::map_create);
|
||||
ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer::map_set_active);
|
||||
ClassDB::bind_method(D_METHOD("map_is_active", "nap"), &NavigationServer::map_is_active);
|
||||
ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer::map_is_active);
|
||||
ClassDB::bind_method(D_METHOD("map_set_up", "map", "up"), &NavigationServer::map_set_up);
|
||||
ClassDB::bind_method(D_METHOD("map_get_up", "map"), &NavigationServer::map_get_up);
|
||||
ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &NavigationServer::map_set_cell_size);
|
||||
|
|
|
@ -810,6 +810,7 @@ PhysicsServer::~PhysicsServer() {
|
|||
|
||||
Vector<PhysicsServerManager::ClassInfo> PhysicsServerManager::physics_servers;
|
||||
int PhysicsServerManager::default_server_id = -1;
|
||||
int PhysicsServerManager::current_server_id = -1;
|
||||
int PhysicsServerManager::default_server_priority = -1;
|
||||
const String PhysicsServerManager::setting_property_name(PNAME("physics/3d/physics_engine"));
|
||||
|
||||
|
@ -857,6 +858,7 @@ String PhysicsServerManager::get_server_name(int p_id) {
|
|||
|
||||
PhysicsServer *PhysicsServerManager::new_default_server() {
|
||||
ERR_FAIL_COND_V(default_server_id == -1, nullptr);
|
||||
current_server_id = default_server_id;
|
||||
return physics_servers[default_server_id].create_callback();
|
||||
}
|
||||
|
||||
|
@ -865,6 +867,7 @@ PhysicsServer *PhysicsServerManager::new_server(const String &p_name) {
|
|||
if (id == -1) {
|
||||
return nullptr;
|
||||
} else {
|
||||
current_server_id = id;
|
||||
return physics_servers[id].create_callback();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -807,6 +807,7 @@ class PhysicsServerManager {
|
|||
|
||||
public:
|
||||
static const String setting_property_name;
|
||||
static int current_server_id;
|
||||
|
||||
private:
|
||||
static void on_servers_changed();
|
||||
|
|
2
thirdparty/README.md
vendored
2
thirdparty/README.md
vendored
|
@ -399,7 +399,7 @@ Collection of single-file libraries used in Godot components.
|
|||
## nanosvg
|
||||
|
||||
- Upstream: https://github.com/memononen/nanosvg
|
||||
- Version: git (4c8f0139b62c6e7faa3b67ce1fbe6e63590ed148, 2022)
|
||||
- Version: git (bd16c4e6b2842e1f0286dc374d21f85c659862e5, 2022)
|
||||
- License: zlib
|
||||
|
||||
Files extracted from the upstream source:
|
||||
|
|
67
thirdparty/nanosvg/nanosvg.h
vendored
67
thirdparty/nanosvg/nanosvg.h
vendored
|
@ -181,8 +181,6 @@ void nsvgDelete(NSVGimage* image);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#endif // NANOSVG_H
|
||||
|
||||
#ifdef NANOSVG_IMPLEMENTATION
|
||||
|
||||
#include <string.h>
|
||||
|
@ -1224,15 +1222,58 @@ static unsigned int nsvg__parseColorHex(const char* str)
|
|||
return NSVG_RGB(128, 128, 128);
|
||||
}
|
||||
|
||||
// Parse rgb color. The pointer 'str' must point at "rgb(" (4+ characters).
|
||||
// This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors
|
||||
// for backwards compatibility. Note: other image viewers return black instead.
|
||||
|
||||
static unsigned int nsvg__parseColorRGB(const char* str)
|
||||
{
|
||||
unsigned int r=0, g=0, b=0;
|
||||
float rf=0, gf=0, bf=0;
|
||||
if (sscanf(str, "rgb(%u, %u, %u)", &r, &g, &b) == 3) // decimal integers
|
||||
return NSVG_RGB(r, g, b);
|
||||
if (sscanf(str, "rgb(%f%%, %f%%, %f%%)", &rf, &gf, &bf) == 3) // decimal integer percentage
|
||||
return NSVG_RGB(roundf(rf*2.55f), roundf(gf*2.55f), roundf(bf*2.55f)); // (255 / 100.0f)
|
||||
return NSVG_RGB(128, 128, 128);
|
||||
int i;
|
||||
unsigned int rgbi[3];
|
||||
float rgbf[3];
|
||||
// try decimal integers first
|
||||
if (sscanf(str, "rgb(%u, %u, %u)", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) {
|
||||
// integers failed, try percent values (float, locale independent)
|
||||
const char delimiter[3] = {',', ',', ')'};
|
||||
str += 4; // skip "rgb("
|
||||
for (i = 0; i < 3; i++) {
|
||||
while (*str && (nsvg__isspace(*str))) str++; // skip leading spaces
|
||||
if (*str == '+') str++; // skip '+' (don't allow '-')
|
||||
if (!*str) break;
|
||||
rgbf[i] = nsvg__atof(str);
|
||||
|
||||
// Note 1: it would be great if nsvg__atof() returned how many
|
||||
// bytes it consumed but it doesn't. We need to skip the number,
|
||||
// the '%' character, spaces, and the delimiter ',' or ')'.
|
||||
|
||||
// Note 2: The following code does not allow values like "33.%",
|
||||
// i.e. a decimal point w/o fractional part, but this is consistent
|
||||
// with other image viewers, e.g. firefox, chrome, eog, gimp.
|
||||
|
||||
while (*str && nsvg__isdigit(*str)) str++; // skip integer part
|
||||
if (*str == '.') {
|
||||
str++;
|
||||
if (!nsvg__isdigit(*str)) break; // error: no digit after '.'
|
||||
while (*str && nsvg__isdigit(*str)) str++; // skip fractional part
|
||||
}
|
||||
if (*str == '%') str++; else break;
|
||||
while (nsvg__isspace(*str)) str++;
|
||||
if (*str == delimiter[i]) str++;
|
||||
else break;
|
||||
}
|
||||
if (i == 3) {
|
||||
rgbi[0] = roundf(rgbf[0] * 2.55f);
|
||||
rgbi[1] = roundf(rgbf[1] * 2.55f);
|
||||
rgbi[2] = roundf(rgbf[2] * 2.55f);
|
||||
} else {
|
||||
rgbi[0] = rgbi[1] = rgbi[2] = 128;
|
||||
}
|
||||
}
|
||||
// clip values as the CSS spec requires
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (rgbi[i] > 255) rgbi[i] = 255;
|
||||
}
|
||||
return NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]);
|
||||
}
|
||||
|
||||
typedef struct NSVGNamedColor {
|
||||
|
@ -1640,9 +1681,9 @@ static void nsvg__parseUrl(char* id, const char* str)
|
|||
{
|
||||
int i = 0;
|
||||
str += 4; // "url(";
|
||||
if (*str == '#')
|
||||
if (*str && *str == '#')
|
||||
str++;
|
||||
while (i < 63 && *str != ')') {
|
||||
while (i < 63 && *str && *str != ')') {
|
||||
id[i] = *str++;
|
||||
i++;
|
||||
}
|
||||
|
@ -3007,4 +3048,6 @@ void nsvgDelete(NSVGimage* image)
|
|||
free(image);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // NANOSVG_IMPLEMENTATION
|
||||
|
||||
#endif // NANOSVG_H
|
||||
|
|
8
thirdparty/nanosvg/nanosvgrast.h
vendored
8
thirdparty/nanosvg/nanosvgrast.h
vendored
|
@ -25,6 +25,8 @@
|
|||
#ifndef NANOSVGRAST_H
|
||||
#define NANOSVGRAST_H
|
||||
|
||||
#include "nanosvg.h"
|
||||
|
||||
#ifndef NANOSVGRAST_CPLUSPLUS
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
@ -72,8 +74,6 @@ void nsvgDeleteRasterizer(NSVGrasterizer*);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#endif // NANOSVGRAST_H
|
||||
|
||||
#ifdef NANOSVGRAST_IMPLEMENTATION
|
||||
|
||||
#include <math.h>
|
||||
|
@ -1453,4 +1453,6 @@ void nsvgRasterize(NSVGrasterizer* r,
|
|||
r->stride = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // NANOSVGRAST_IMPLEMENTATION
|
||||
|
||||
#endif // NANOSVGRAST_H
|
||||
|
|
Loading…
Reference in a new issue