summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct1
-rw-r--r--core/math/audio_frame.h110
-rw-r--r--doc/classes/AudioServer.xml2
-rw-r--r--doc/classes/AudioStream.xml12
-rw-r--r--doc/classes/AudioStreamPlayback.xml9
-rw-r--r--doc/classes/DisplayServer.xml46
-rw-r--r--doc/classes/NodePath.xml148
-rw-r--r--doc/classes/SpriteBase3D.xml4
-rw-r--r--doc/classes/StatusIndicator.xml31
-rw-r--r--doc/classes/TextEdit.xml14
-rw-r--r--doc/classes/Tree.xml2
-rw-r--r--editor/audio_stream_preview.cpp8
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/icons/StatusIndicator.svg1
-rw-r--r--editor/import/3d/resource_importer_scene.cpp71
-rw-r--r--editor/import/3d/resource_importer_scene.h1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp2
-rw-r--r--editor/plugins/editor_preview_plugins.cpp8
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp8
-rw-r--r--editor/plugins/script_text_editor.cpp3
-rw-r--r--editor/plugins/text_editor.cpp3
-rw-r--r--editor/plugins/text_shader_editor.cpp4
-rw-r--r--editor/plugins/theme_editor_plugin.cpp25
-rw-r--r--editor/plugins/theme_editor_plugin.h2
-rw-r--r--main/main.cpp27
-rw-r--r--misc/dist/macos/editor_debug.entitlements22
-rw-r--r--misc/dist/macos/editor_info_plist.template199
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp8
-rw-r--r--platform/android/detect.py1
-rw-r--r--platform/ios/SCsub61
-rw-r--r--platform/ios/detect.py2
-rw-r--r--platform/ios/display_server_ios.mm2
-rw-r--r--platform/ios/export/export_plugin.cpp10
-rw-r--r--platform/ios/rendering_context_driver_vulkan_ios.h4
-rw-r--r--platform/ios/rendering_context_driver_vulkan_ios.mm12
-rw-r--r--platform/macos/SCsub99
-rw-r--r--platform/macos/detect.py78
-rw-r--r--platform/macos/display_server_macos.h14
-rw-r--r--platform/macos/display_server_macos.mm127
-rw-r--r--platform/macos/export/export_plugin.cpp184
-rw-r--r--platform/macos/godot_content_view.h2
-rw-r--r--platform/macos/godot_content_view.mm49
-rw-r--r--platform/macos/godot_open_save_delegate.mm74
-rw-r--r--platform/macos/godot_status_item.h51
-rw-r--r--platform/macos/godot_status_item.mm101
-rw-r--r--platform/macos/godot_window_delegate.mm1
-rw-r--r--platform/macos/rendering_context_driver_vulkan_macos.h4
-rw-r--r--platform/macos/rendering_context_driver_vulkan_macos.mm12
-rw-r--r--platform/windows/SCsub2
-rw-r--r--platform/windows/display_server_windows.cpp284
-rw-r--r--platform/windows/display_server_windows.h13
-rw-r--r--platform/windows/platform_windows_builders.py28
-rw-r--r--platform_methods.py105
-rw-r--r--scene/3d/audio_stream_player_3d.cpp32
-rw-r--r--scene/3d/path_3d.cpp56
-rw-r--r--scene/3d/path_3d.h14
-rw-r--r--scene/gui/code_edit.cpp3
-rw-r--r--scene/gui/item_list.cpp26
-rw-r--r--scene/gui/item_list.h10
-rw-r--r--scene/gui/line_edit.cpp7
-rw-r--r--scene/gui/menu_bar.cpp46
-rw-r--r--scene/gui/menu_bar.h1
-rw-r--r--scene/gui/popup_menu.cpp132
-rw-r--r--scene/gui/popup_menu.h16
-rw-r--r--scene/gui/text_edit.cpp136
-rw-r--r--scene/gui/text_edit.h6
-rw-r--r--scene/gui/tree.cpp34
-rw-r--r--scene/main/status_indicator.cpp135
-rw-r--r--scene/main/status_indicator.h62
-rw-r--r--scene/property_list_helper.cpp55
-rw-r--r--scene/property_list_helper.h28
-rw-r--r--scene/register_scene_types.cpp3
-rw-r--r--scene/resources/audio_stream_wav.cpp4
-rw-r--r--scene/theme/theme_db.cpp5
-rw-r--r--scene/theme/theme_db.h2
-rw-r--r--servers/audio/effects/audio_effect_capture.cpp2
-rw-r--r--servers/audio/effects/audio_effect_chorus.cpp4
-rw-r--r--servers/audio/effects/audio_effect_compressor.cpp6
-rw-r--r--servers/audio/effects/audio_effect_delay.cpp8
-rw-r--r--servers/audio/effects/audio_effect_eq.cpp8
-rw-r--r--servers/audio/effects/audio_effect_filter.cpp8
-rw-r--r--servers/audio/effects/audio_effect_limiter.cpp8
-rw-r--r--servers/audio/effects/audio_effect_panner.cpp4
-rw-r--r--servers/audio/effects/audio_effect_phaser.cpp12
-rw-r--r--servers/audio/effects/audio_effect_record.cpp4
-rw-r--r--servers/audio/effects/audio_effect_reverb.cpp8
-rw-r--r--servers/audio/effects/audio_effect_spectrum_analyzer.cpp12
-rw-r--r--servers/audio/effects/audio_effect_stereo_enhance.cpp8
-rw-r--r--servers/audio_server.cpp30
-rw-r--r--servers/display_server.cpp29
-rw-r--r--servers/display_server.h11
91 files changed, 2342 insertions, 728 deletions
diff --git a/SConstruct b/SConstruct
index 04564ad149..47a039fb14 100644
--- a/SConstruct
+++ b/SConstruct
@@ -182,7 +182,6 @@ opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", False))
opts.Add(BoolVariable("separate_debug_symbols", "Extract debugging symbols to a separate file", False))
opts.Add(EnumVariable("lto", "Link-time optimization (production builds)", "none", ("none", "auto", "thin", "full")))
opts.Add(BoolVariable("production", "Set defaults to build Godot for use in production", False))
-opts.Add(BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False))
opts.Add(BoolVariable("threads", "Enable threading support", True))
# Components
diff --git a/core/math/audio_frame.h b/core/math/audio_frame.h
index d26336e9a2..e205126cdf 100644
--- a/core/math/audio_frame.h
+++ b/core/math/audio_frame.h
@@ -51,105 +51,123 @@ static const float AUDIO_PEAK_OFFSET = 0.0000000001f;
static const float AUDIO_MIN_PEAK_DB = -200.0f; // linear_to_db(AUDIO_PEAK_OFFSET)
struct AudioFrame {
- //left and right samples
- float l = 0.f, r = 0.f;
-
- _ALWAYS_INLINE_ const float &operator[](int idx) const { return idx == 0 ? l : r; }
- _ALWAYS_INLINE_ float &operator[](int idx) { return idx == 0 ? l : r; }
+ // Left and right samples.
+ union {
+ struct {
+ float left;
+ float right;
+ };
+#ifndef DISABLE_DEPRECATED
+ struct {
+ float l;
+ float r;
+ };
+#endif
+ float levels[2] = { 0.0 };
+ };
+
+ _ALWAYS_INLINE_ const float &operator[](int p_idx) const {
+ DEV_ASSERT((unsigned int)p_idx < 2);
+ return levels[p_idx];
+ }
+ _ALWAYS_INLINE_ float &operator[](int p_idx) {
+ DEV_ASSERT((unsigned int)p_idx < 2);
+ return levels[p_idx];
+ }
- _ALWAYS_INLINE_ AudioFrame operator+(const AudioFrame &p_frame) const { return AudioFrame(l + p_frame.l, r + p_frame.r); }
- _ALWAYS_INLINE_ AudioFrame operator-(const AudioFrame &p_frame) const { return AudioFrame(l - p_frame.l, r - p_frame.r); }
- _ALWAYS_INLINE_ AudioFrame operator*(const AudioFrame &p_frame) const { return AudioFrame(l * p_frame.l, r * p_frame.r); }
- _ALWAYS_INLINE_ AudioFrame operator/(const AudioFrame &p_frame) const { return AudioFrame(l / p_frame.l, r / p_frame.r); }
+ _ALWAYS_INLINE_ AudioFrame operator+(const AudioFrame &p_frame) const { return AudioFrame(left + p_frame.left, right + p_frame.right); }
+ _ALWAYS_INLINE_ AudioFrame operator-(const AudioFrame &p_frame) const { return AudioFrame(left - p_frame.left, right - p_frame.right); }
+ _ALWAYS_INLINE_ AudioFrame operator*(const AudioFrame &p_frame) const { return AudioFrame(left * p_frame.left, right * p_frame.right); }
+ _ALWAYS_INLINE_ AudioFrame operator/(const AudioFrame &p_frame) const { return AudioFrame(left / p_frame.left, right / p_frame.right); }
- _ALWAYS_INLINE_ AudioFrame operator+(float p_sample) const { return AudioFrame(l + p_sample, r + p_sample); }
- _ALWAYS_INLINE_ AudioFrame operator-(float p_sample) const { return AudioFrame(l - p_sample, r - p_sample); }
- _ALWAYS_INLINE_ AudioFrame operator*(float p_sample) const { return AudioFrame(l * p_sample, r * p_sample); }
- _ALWAYS_INLINE_ AudioFrame operator/(float p_sample) const { return AudioFrame(l / p_sample, r / p_sample); }
+ _ALWAYS_INLINE_ AudioFrame operator+(float p_sample) const { return AudioFrame(left + p_sample, right + p_sample); }
+ _ALWAYS_INLINE_ AudioFrame operator-(float p_sample) const { return AudioFrame(left - p_sample, right - p_sample); }
+ _ALWAYS_INLINE_ AudioFrame operator*(float p_sample) const { return AudioFrame(left * p_sample, right * p_sample); }
+ _ALWAYS_INLINE_ AudioFrame operator/(float p_sample) const { return AudioFrame(left / p_sample, right / p_sample); }
_ALWAYS_INLINE_ void operator+=(const AudioFrame &p_frame) {
- l += p_frame.l;
- r += p_frame.r;
+ left += p_frame.left;
+ right += p_frame.right;
}
_ALWAYS_INLINE_ void operator-=(const AudioFrame &p_frame) {
- l -= p_frame.l;
- r -= p_frame.r;
+ left -= p_frame.left;
+ right -= p_frame.right;
}
_ALWAYS_INLINE_ void operator*=(const AudioFrame &p_frame) {
- l *= p_frame.l;
- r *= p_frame.r;
+ left *= p_frame.left;
+ right *= p_frame.right;
}
_ALWAYS_INLINE_ void operator/=(const AudioFrame &p_frame) {
- l /= p_frame.l;
- r /= p_frame.r;
+ left /= p_frame.left;
+ right /= p_frame.right;
}
_ALWAYS_INLINE_ void operator+=(float p_sample) {
- l += p_sample;
- r += p_sample;
+ left += p_sample;
+ right += p_sample;
}
_ALWAYS_INLINE_ void operator-=(float p_sample) {
- l -= p_sample;
- r -= p_sample;
+ left -= p_sample;
+ right -= p_sample;
}
_ALWAYS_INLINE_ void operator*=(float p_sample) {
- l *= p_sample;
- r *= p_sample;
+ left *= p_sample;
+ right *= p_sample;
}
_ALWAYS_INLINE_ void operator/=(float p_sample) {
- l /= p_sample;
- r /= p_sample;
+ left /= p_sample;
+ right /= p_sample;
}
_ALWAYS_INLINE_ void undenormalize() {
- l = ::undenormalize(l);
- r = ::undenormalize(r);
+ left = ::undenormalize(left);
+ right = ::undenormalize(right);
}
_FORCE_INLINE_ AudioFrame lerp(const AudioFrame &p_b, float p_t) const {
AudioFrame res = *this;
- res.l += (p_t * (p_b.l - l));
- res.r += (p_t * (p_b.r - r));
+ res.left += (p_t * (p_b.left - left));
+ res.right += (p_t * (p_b.right - right));
return res;
}
- _ALWAYS_INLINE_ AudioFrame(float p_l, float p_r) {
- l = p_l;
- r = p_r;
+ _ALWAYS_INLINE_ AudioFrame(float p_left, float p_right) {
+ left = p_left;
+ right = p_right;
}
_ALWAYS_INLINE_ AudioFrame(const AudioFrame &p_frame) {
- l = p_frame.l;
- r = p_frame.r;
+ left = p_frame.left;
+ right = p_frame.right;
}
_ALWAYS_INLINE_ void operator=(const AudioFrame &p_frame) {
- l = p_frame.l;
- r = p_frame.r;
+ left = p_frame.left;
+ right = p_frame.right;
}
_ALWAYS_INLINE_ operator Vector2() const {
- return Vector2(l, r);
+ return Vector2(left, right);
}
_ALWAYS_INLINE_ AudioFrame(const Vector2 &p_v2) {
- l = p_v2.x;
- r = p_v2.y;
+ left = p_v2.x;
+ right = p_v2.y;
}
_ALWAYS_INLINE_ AudioFrame() {}
};
_ALWAYS_INLINE_ AudioFrame operator*(float p_scalar, const AudioFrame &p_frame) {
- return AudioFrame(p_frame.l * p_scalar, p_frame.r * p_scalar);
+ return AudioFrame(p_frame.left * p_scalar, p_frame.right * p_scalar);
}
_ALWAYS_INLINE_ AudioFrame operator*(int32_t p_scalar, const AudioFrame &p_frame) {
- return AudioFrame(p_frame.l * p_scalar, p_frame.r * p_scalar);
+ return AudioFrame(p_frame.left * p_scalar, p_frame.right * p_scalar);
}
_ALWAYS_INLINE_ AudioFrame operator*(int64_t p_scalar, const AudioFrame &p_frame) {
- return AudioFrame(p_frame.l * p_scalar, p_frame.r * p_scalar);
+ return AudioFrame(p_frame.left * p_scalar, p_frame.right * p_scalar);
}
#endif // AUDIO_FRAME_H
diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml
index 5b3fcd67d4..993aa581dc 100644
--- a/doc/classes/AudioServer.xml
+++ b/doc/classes/AudioServer.xml
@@ -281,6 +281,8 @@
<return type="void" />
<param index="0" name="enable" type="bool" />
<description>
+ If set to [code]true[/code], all instances of [AudioStreamPlayback] will call [method AudioStreamPlayback._tag_used_streams] every mix step.
+ [b]Note:[/b] This is enabled by default in the editor, as it is used by editor plugins for the audio stream previews.
</description>
</method>
<method name="swap_bus_effects">
diff --git a/doc/classes/AudioStream.xml b/doc/classes/AudioStream.xml
index 6e30775fee..9813c2f251 100644
--- a/doc/classes/AudioStream.xml
+++ b/doc/classes/AudioStream.xml
@@ -16,16 +16,21 @@
<method name="_get_beat_count" qualifiers="virtual const">
<return type="int" />
<description>
+ Overridable method. Should return the total number of beats of this audio stream. Used by the engine to determine the position of every beat.
+ Ideally, the returned value should be based off the stream's sample rate ([member AudioStreamWAV.mix_rate], for example).
</description>
</method>
<method name="_get_bpm" qualifiers="virtual const">
<return type="float" />
<description>
+ Overridable method. Should return the tempo of this audio stream, in beats per minute (BPM). Used by the engine to determine the position of every beat.
+ Ideally, the returned value should be based off the stream's sample rate ([member AudioStreamWAV.mix_rate], for example).
</description>
</method>
<method name="_get_length" qualifiers="virtual const">
<return type="float" />
<description>
+ Override this method to customize the returned value of [method get_length]. Should return the length of this audio stream, in seconds.
</description>
</method>
<method name="_get_parameter_list" qualifiers="virtual const">
@@ -37,16 +42,19 @@
<method name="_get_stream_name" qualifiers="virtual const">
<return type="String" />
<description>
+ Override this method to customize the name assigned to this audio stream. Unused by the engine.
</description>
</method>
<method name="_instantiate_playback" qualifiers="virtual const">
<return type="AudioStreamPlayback" />
<description>
+ Override this method to customize the returned value of [method instantiate_playback]. Should returned a new [AudioStreamPlayback] created when the stream is played (such as by an [AudioStreamPlayer])..
</description>
</method>
<method name="_is_monophonic" qualifiers="virtual const">
<return type="bool" />
<description>
+ Override this method to customize the returned value of [method is_monophonic]. Should return [code]true[/code] if this audio stream only supports one channel.
</description>
</method>
<method name="get_length" qualifiers="const">
@@ -58,13 +66,13 @@
<method name="instantiate_playback">
<return type="AudioStreamPlayback" />
<description>
- Returns an AudioStreamPlayback. Useful for when you want to extend [method _instantiate_playback] but call [method instantiate_playback] from an internally held AudioStream subresource. An example of this can be found in the source files for [code]AudioStreamRandomPitch::instantiate_playback[/code].
+ Returns a newly created [AudioStreamPlayback] intended to play this audio stream. Useful for when you want to extend [method _instantiate_playback] but call [method instantiate_playback] from an internally held AudioStream subresource. An example of this can be found in the source code for [code]AudioStreamRandomPitch::instantiate_playback[/code].
</description>
</method>
<method name="is_monophonic" qualifiers="const">
<return type="bool" />
<description>
- Returns true if this audio stream only supports monophonic playback, or false if the audio stream supports polyphony.
+ Returns [code]true[/code] if this audio stream only supports one channel ([i]monophony[/i]), or [code]false[/code] if the audio stream supports two or more channels ([i]polyphony[/i]).
</description>
</method>
</methods>
diff --git a/doc/classes/AudioStreamPlayback.xml b/doc/classes/AudioStreamPlayback.xml
index a090989194..460a7050c8 100644
--- a/doc/classes/AudioStreamPlayback.xml
+++ b/doc/classes/AudioStreamPlayback.xml
@@ -13,6 +13,7 @@
<method name="_get_loop_count" qualifiers="virtual const">
<return type="int" />
<description>
+ Overridable method. Should return how many times this audio stream has looped. Most built-in playbacks always return [code]0[/code].
</description>
</method>
<method name="_get_parameter" qualifiers="virtual const">
@@ -25,11 +26,13 @@
<method name="_get_playback_position" qualifiers="virtual const">
<return type="float" />
<description>
+ Overridable method. Should return the current progress along the audio stream, in seconds.
</description>
</method>
<method name="_is_playing" qualifiers="virtual const">
<return type="bool" />
<description>
+ Overridable method. Should return [code]true[/code] if this playback is active and playing its audio stream.
</description>
</method>
<method name="_mix" qualifiers="virtual">
@@ -38,12 +41,15 @@
<param index="1" name="rate_scale" type="float" />
<param index="2" name="frames" type="int" />
<description>
+ Override this method to customize how the audio stream is mixed. This method is called even if the playback is not active.
+ [b]Note:[/b] It is not useful to override this method in GDScript or C#. Only GDExtension can take advantage of it.
</description>
</method>
<method name="_seek" qualifiers="virtual">
<return type="void" />
<param index="0" name="position" type="float" />
<description>
+ Override this method to customize what happens when seeking this audio stream at the given [param position], such as by calling [method AudioStreamPlayer.seek].
</description>
</method>
<method name="_set_parameter" qualifiers="virtual">
@@ -58,16 +64,19 @@
<return type="void" />
<param index="0" name="from_pos" type="float" />
<description>
+ Override this method to customize what happens when the playback starts at the given position, such as by calling [method AudioStreamPlayer.play].
</description>
</method>
<method name="_stop" qualifiers="virtual">
<return type="void" />
<description>
+ Override this method to customize what happens when the playback is stopped, such as by calling [method AudioStreamPlayer.stop].
</description>
</method>
<method name="_tag_used_streams" qualifiers="virtual">
<return type="void" />
<description>
+ Overridable method. Called whenever the audio stream is mixed if the playback is active and [method AudioServer.set_enable_tagging_used_audio_streams] has been set to [code]true[/code]. Editor plugins may use this method to "tag" the current position along the audio stream and display it in a preview.
</description>
</method>
</methods>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 3be64e2f62..09bb20af79 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -56,6 +56,15 @@
[b]Note:[/b] This method is only implemented on Linux (X11/Wayland).
</description>
</method>
+ <method name="create_status_indicator">
+ <return type="int" />
+ <param index="0" name="icon" type="Image" />
+ <param index="1" name="tooltip" type="String" />
+ <param index="2" name="callback" type="Callable" />
+ <description>
+ Creates a new application status indicator with the specified icon, tooltip, and activation callback.
+ </description>
+ </method>
<method name="cursor_get_shape" qualifiers="const">
<return type="int" enum="DisplayServer.CursorShape" />
<description>
@@ -78,6 +87,13 @@
Sets the default mouse cursor shape. The cursor's appearance will vary depending on the user's operating system and mouse cursor theme. See also [method cursor_get_shape] and [method cursor_set_custom_image].
</description>
</method>
+ <method name="delete_status_indicator">
+ <return type="void" />
+ <param index="0" name="id" type="int" />
+ <description>
+ Removes the application status indicator.
+ </description>
+ </method>
<method name="dialog_input_text">
<return type="int" enum="Error" />
<param index="0" name="title" type="String" />
@@ -1120,6 +1136,30 @@
Sets the window icon (usually displayed in the top-left corner) in the operating system's [i]native[/i] format. The file at [param filename] must be in [code].ico[/code] format on Windows or [code].icns[/code] on macOS. By using specially crafted [code].ico[/code] or [code].icns[/code] icons, [method set_native_icon] allows specifying different icons depending on the size the icon is displayed at. This size is determined by the operating system and user preferences (including the display scale factor). To use icons in other formats, use [method set_icon] instead.
</description>
</method>
+ <method name="status_indicator_set_callback">
+ <return type="void" />
+ <param index="0" name="id" type="int" />
+ <param index="1" name="callback" type="Callable" />
+ <description>
+ Sets the application status indicator activation callback.
+ </description>
+ </method>
+ <method name="status_indicator_set_icon">
+ <return type="void" />
+ <param index="0" name="id" type="int" />
+ <param index="1" name="icon" type="Image" />
+ <description>
+ Sets the application status indicator icon.
+ </description>
+ </method>
+ <method name="status_indicator_set_tooltip">
+ <return type="void" />
+ <param index="0" name="id" type="int" />
+ <param index="1" name="tooltip" type="String" />
+ <description>
+ Sets the application status indicator tooltip.
+ </description>
+ </method>
<method name="tablet_get_current_driver" qualifiers="const">
<return type="String" />
<description>
@@ -1748,6 +1788,9 @@
<constant name="FEATURE_SCREEN_CAPTURE" value="21" enum="Feature">
Display server supports reading screen pixels. See [method screen_get_pixel].
</constant>
+ <constant name="FEATURE_STATUS_INDICATOR" value="22" enum="Feature">
+ Display server supports application status indicators.
+ </constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
@@ -1786,6 +1829,9 @@
<constant name="INVALID_WINDOW_ID" value="-1">
The ID that refers to a nonexistent window. This is returned by some [DisplayServer] methods if no window matches the requested result.
</constant>
+ <constant name="INVALID_INDICATOR_ID" value="-1">
+ The ID that refers to a nonexistent application status indicator.
+ </constant>
<constant name="SCREEN_LANDSCAPE" value="0" enum="ScreenOrientation">
Default landscape orientation.
</constant>
diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml
index 328e572c13..fa6f158b6e 100644
--- a/doc/classes/NodePath.xml
+++ b/doc/classes/NodePath.xml
@@ -4,25 +4,33 @@
A pre-parsed scene tree path.
</brief_description>
<description>
- A pre-parsed relative or absolute path in a scene tree, for use with [method Node.get_node] and similar functions. It can reference a node, a resource within a node, or a property of a node or resource. For example, [code]"Path2D/PathFollow2D/Sprite2D:texture:size"[/code] would refer to the [code]size[/code] property of the [code]texture[/code] resource on the node named [code]"Sprite2D"[/code], which is a child of the other named nodes in the path.
- You will usually just pass a string to [method Node.get_node] and it will be automatically converted, but you may occasionally want to parse a path ahead of time with [NodePath] or the literal syntax [code]^"path"[/code]. Exporting a [NodePath] variable will give you a node selection widget in the properties panel of the editor, which can often be useful.
- A [NodePath] is composed of a list of slash-separated node names (like a filesystem path) and an optional colon-separated list of "subnames" which can be resources or properties.
- Some examples of NodePaths include the following:
+ The [NodePath] built-in [Variant] type represents a path to a node or property in a hierarchy of nodes. It is designed to be efficiently passed into many built-in methods (such as [method Node.get_node], [method Object.set_indexed], [method Tween.tween_property], etc.) without a hard dependence on the node or property they point to.
+ A node path is represented as a [String] composed of slash-separated ([code]/[/code]) node names and colon-separated ([code]:[/code]) property names (also called "subnames"). Similar to a filesystem path, [code]".."[/code] and [code]"."[/code] are special node names. They refer to the parent node and the current node, respectively.
+ The following examples are paths relative to the current node:
[codeblock]
- # No leading slash means it is relative to the current node.
- ^"A" # Immediate child A
- ^"A/B" # A's child B
- ^"." # The current node.
- ^".." # The parent node.
- ^"../C" # A sibling node C.
- ^"../.." # The grandparent node.
- # A leading slash means it is absolute from the SceneTree.
- ^"/root" # Equivalent to get_tree().get_root().
- ^"/root/Main" # If your main scene's root node were named "Main".
- ^"/root/MyAutoload" # If you have an autoloaded node or scene.
+ ^"A" # Points to the direct child A.
+ ^"A/B" # Points to A's child B.
+ ^"." # Points to the current node.
+ ^".." # Points to the parent node.
+ ^"../C" # Points to the sibling node C.
+ ^"../.." # Points to the grandparent node.
[/codeblock]
- See also [StringName], which is a similar concept for general-purpose string interning.
- [b]Note:[/b] In the editor, [NodePath] properties are automatically updated when moving, renaming or deleting a node in the scene tree, but they are never updated at runtime.
+ A leading slash means the path is absolute, and begins from the [SceneTree]:
+ [codeblock]
+ ^"/root" # Points to the SceneTree's root Window.
+ ^"/root/Title" # May point to the main scene's root node named "Title".
+ ^"/root/Global" # May point to an autoloaded node or scene named "Global".
+ [/codeblock]
+ Despite their name, node paths may also point to a property:
+ [codeblock]
+ ^"position" # Points to this object's position.
+ ^"position:x" # Points to this object's position in the x axis.
+ ^"Camera3D:rotation:y" # Points to the child Camera3D and its y rotation.
+ ^"/root:size:x" # Points to the root Window and its width.
+ [/codeblock]
+ Node paths cannot check whether they are valid and may point to nodes or properties that do not exist. Their meaning depends entirely on the context in which they're used.
+ You usually do not have to worry about the [NodePath] type, as strings are automatically converted to the type when necessary. There are still times when defining node paths is useful. For example, exported [NodePath] properties allow you to easily select any node within the currently edited scene. They are also automatically updated when moving, renaming or deleting nodes in the scene tree editor. See also [annotation @GDScript.@export_node_path].
+ See also [StringName], which is a similar type designed for optimised strings.
[b]Note:[/b] In a boolean context, a [NodePath] will evaluate to [code]false[/code] if it is empty ([code]NodePath("")[/code]). Otherwise, a [NodePath] will always evaluate to [code]true[/code].
</description>
<tutorials>
@@ -39,30 +47,35 @@
<return type="NodePath" />
<param index="0" name="from" type="NodePath" />
<description>
- Constructs a [NodePath] as a copy of the given [NodePath]. [code]NodePath("example")[/code] is equivalent to [code]^"example"[/code].
+ Constructs a [NodePath] as a copy of the given [NodePath].
</description>
</constructor>
<constructor name="NodePath">
<return type="NodePath" />
<param index="0" name="from" type="String" />
<description>
- Creates a NodePath from a string, e.g. [code]"Path2D/PathFollow2D/Sprite2D:texture:size"[/code]. A path is absolute if it starts with a slash. Absolute paths are only valid in the global scene tree, not within individual scenes. In a relative path, [code]"."[/code] and [code]".."[/code] indicate the current node and its parent.
- The "subnames" optionally included after the path to the target node can point to resources or properties, and can also be nested.
- Examples of valid NodePaths (assuming that those nodes exist and have the referenced resources or properties):
+ Constructs a [NodePath] from a [String]. The created path is absolute if prefixed with a slash (see [method is_absolute]).
+ The "subnames" optionally included after the path to the target node can point to properties, and can also be nested.
+ Examples of strings that could be node paths:
[codeblock]
# Points to the Sprite2D node.
- "Path2D/PathFollow2D/Sprite2D"
+ "Level/RigidBody2D/Sprite2D"
+
# Points to the Sprite2D node and its "texture" resource.
- # get_node() would retrieve "Sprite2D", while get_node_and_resource()
+ # get_node() would retrieve the Sprite2D, while get_node_and_resource()
# would retrieve both the Sprite2D node and the "texture" resource.
- "Path2D/PathFollow2D/Sprite2D:texture"
+ "Level/RigidBody2D/Sprite2D:texture"
+
# Points to the Sprite2D node and its "position" property.
- "Path2D/PathFollow2D/Sprite2D:position"
+ "Level/RigidBody2D/Sprite2D:position"
+
# Points to the Sprite2D node and the "x" component of its "position" property.
- "Path2D/PathFollow2D/Sprite2D:position:x"
- # Absolute path (from "root")
- "/root/Level/Path2D"
+ "Level/RigidBody2D/Sprite2D:position:x"
+
+ # Points to the RigidBody2D node as an absolute path beginning from the SceneTree.
+ "/root/Level/RigidBody2D"
[/codeblock]
+ [b]Note:[/b] In GDScript, it's also possible to convert a constant string into a node path by prefixing it with [code]^[/code]. [code]^"path/to/node"[/code] is equivalent to [code]NodePath("path/to/node")[/code].
</description>
</constructor>
</constructors>
@@ -70,21 +83,23 @@
<method name="get_as_property_path" qualifiers="const">
<return type="NodePath" />
<description>
- Returns a node path with a colon character ([code]:[/code]) prepended, transforming it to a pure property path with no node name (defaults to resolving from the current node).
+ Returns a copy of this node path with a colon character ([code]:[/code]) prefixed, transforming it to a pure property path with no node names (relative to the current node).
[codeblocks]
[gdscript]
- # This will be parsed as a node path to the "x" property in the "position" node.
- var node_path = NodePath("position:x")
- # This will be parsed as a node path to the "x" component of the "position" property in the current node.
+ # node_path points to the "x" property of the child node named "position".
+ var node_path = ^"position:x"
+
+ # property_path points to the "position" in the "x" axis of this node.
var property_path = node_path.get_as_property_path()
- print(property_path) # :position:x
+ print(property_path) # Prints ":position:x"
[/gdscript]
[csharp]
- // This will be parsed as a node path to the "x" property in the "position" node.
+ // nodePath points to the "x" property of the child node named "position".
var nodePath = new NodePath("position:x");
- // This will be parsed as a node path to the "x" component of the "position" property in the current node.
+
+ // propertyPath points to the "position" in the "x" axis of this node.
NodePath propertyPath = nodePath.GetAsPropertyPath();
- GD.Print(propertyPath); // :position:x
+ GD.Print(propertyPath); // Prints ":position:x".
[/csharp]
[/codeblocks]
</description>
@@ -92,21 +107,21 @@
<method name="get_concatenated_names" qualifiers="const">
<return type="StringName" />
<description>
- Returns all paths concatenated with a slash character ([code]/[/code]) as separator without subnames.
+ Returns all node names concatenated with a slash character ([code]/[/code]) as a single [StringName].
</description>
</method>
<method name="get_concatenated_subnames" qualifiers="const">
<return type="StringName" />
<description>
- Returns all subnames concatenated with a colon character ([code]:[/code]) as separator, i.e. the right side of the first colon in a node path.
+ Returns all property subnames concatenated with a colon character ([code]:[/code]) as a single [StringName].
[codeblocks]
[gdscript]
- var node_path = NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path")
- print(node_path.get_concatenated_subnames()) # texture:load_path
+ var node_path = ^"Sprite2D:texture:resource_name"
+ print(node_path.get_concatenated_subnames()) # Prints "texture:resource_name".
[/gdscript]
[csharp]
- var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
- GD.Print(nodePath.GetConcatenatedSubnames()); // texture:load_path
+ var nodePath = new NodePath("Sprite2D:texture:resource_name");
+ GD.Print(nodePath.GetConcatenatedSubnames()); // Prints "texture:resource_name".
[/csharp]
[/codeblocks]
</description>
@@ -115,19 +130,19 @@
<return type="StringName" />
<param index="0" name="idx" type="int" />
<description>
- Gets the node name indicated by [param idx] (0 to [method get_name_count] - 1).
+ Returns the node name indicated by [param idx], starting from 0. If [param idx] is out of bounds, an error is generated. See also [method get_subname_count] and [method get_name_count].
[codeblocks]
[gdscript]
- var node_path = NodePath("Path2D/PathFollow2D/Sprite2D")
- print(node_path.get_name(0)) # Path2D
- print(node_path.get_name(1)) # PathFollow2D
- print(node_path.get_name(2)) # Sprite2D
+ var sprite_path = NodePath("../RigidBody2D/Sprite2D")
+ print(sprite_path.get_name(0)) # Prints "..".
+ print(sprite_path.get_name(1)) # Prints "RigidBody2D".
+ print(sprite_path.get_name(2)) # Prints "Sprite".
[/gdscript]
[csharp]
- var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D");
- GD.Print(nodePath.GetName(0)); // Path2D
- GD.Print(nodePath.GetName(1)); // PathFollow2D
- GD.Print(nodePath.GetName(2)); // Sprite2D
+ var spritePath = new NodePath("../RigidBody2D/Sprite2D");
+ GD.Print(spritePath.GetName(0)); // Prints "..".
+ GD.Print(spritePath.GetName(1)); // Prints "PathFollow2D".
+ GD.Print(spritePath.GetName(2)); // Prints "Sprite".
[/csharp]
[/codeblocks]
</description>
@@ -135,25 +150,25 @@
<method name="get_name_count" qualifiers="const">
<return type="int" />
<description>
- Gets the number of node names which make up the path. Subnames (see [method get_subname_count]) are not included.
- For example, [code]"Path2D/PathFollow2D/Sprite2D"[/code] has 3 names.
+ Returns the number of node names in the path. Property subnames are not included.
+ For example, [code]"../RigidBody2D/Sprite2D:texture"[/code] contains 3 node names.
</description>
</method>
<method name="get_subname" qualifiers="const">
<return type="StringName" />
<param index="0" name="idx" type="int" />
<description>
- Gets the resource or property name indicated by [param idx] (0 to [method get_subname_count] - 1).
+ Returns the property name indicated by [param idx], starting from 0. If [param idx] is out of bounds, an error is generated. See also [method get_subname_count].
[codeblocks]
[gdscript]
- var node_path = NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path")
- print(node_path.get_subname(0)) # texture
- print(node_path.get_subname(1)) # load_path
+ var path_to_name = NodePath("Sprite2D:texture:resource_name")
+ print(path_to_name.get_subname(0)) # Prints "texture".
+ print(path_to_name.get_subname(1)) # Prints "resource_name".
[/gdscript]
[csharp]
- var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
- GD.Print(nodePath.GetSubname(0)); // texture
- GD.Print(nodePath.GetSubname(1)); // load_path
+ var pathToName = new NodePath("Sprite2D:texture:resource_name");
+ GD.Print(pathToName.GetSubname(0)); // Prints "texture".
+ GD.Print(pathToName.GetSubname(1)); // Prints "resource_name".
[/csharp]
[/codeblocks]
</description>
@@ -161,26 +176,27 @@
<method name="get_subname_count" qualifiers="const">
<return type="int" />
<description>
- Gets the number of resource or property names ("subnames") in the path. Each subname is listed after a colon character ([code]:[/code]) in the node path.
- For example, [code]"Path2D/PathFollow2D/Sprite2D:texture:load_path"[/code] has 2 subnames.
+ Returns the number of property names ("subnames") in the path. Each subname in the node path is listed after a colon character ([code]:[/code]).
+ For example, [code]"Level/RigidBody2D/Sprite2D:texture:resource_name"[/code] contains 2 subnames.
</description>
</method>
<method name="hash" qualifiers="const">
<return type="int" />
<description>
- Returns the 32-bit hash value representing the [NodePath]'s contents.
+ Returns the 32-bit hash value representing the node path's contents.
+ [b]Note:[/b] Node paths with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. Node paths with different hash values are guaranteed to be different.
</description>
</method>
<method name="is_absolute" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the node path is absolute (as opposed to relative), which means that it starts with a slash character ([code]/[/code]). Absolute node paths can be used to access the root node ([code]"/root"[/code]) or autoloads (e.g. [code]"/global"[/code] if a "global" autoload was registered).
+ Returns [code]true[/code] if the node path is absolute. Unlike a relative path, an absolute path is represented by a leading slash character ([code]/[/code]) and always begins from the [SceneTree]. It can be used to reliably access nodes from the root node (e.g. [code]"/root/Global"[/code] if an autoload named "Global" exists).
</description>
</method>
<method name="is_empty" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the node path is empty.
+ Returns [code]true[/code] if the node path has been constructed from an empty [String] ([code]""[/code]).
</description>
</method>
</methods>
@@ -196,7 +212,7 @@
<return type="bool" />
<param index="0" name="right" type="NodePath" />
<description>
- Returns [code]true[/code] if two node paths are equal, i.e. all node names in the path are the same and in the same order.
+ Returns [code]true[/code] if two node paths are equal, that is, they are composed of the same node names and subnames in the same order.
</description>
</operator>
</operators>
diff --git a/doc/classes/SpriteBase3D.xml b/doc/classes/SpriteBase3D.xml
index 601340ca91..4488351212 100644
--- a/doc/classes/SpriteBase3D.xml
+++ b/doc/classes/SpriteBase3D.xml
@@ -76,7 +76,8 @@
If [code]true[/code], texture is flipped vertically.
</member>
<member name="modulate" type="Color" setter="set_modulate" getter="get_modulate" default="Color(1, 1, 1, 1)">
- A color value used to [i]multiply[/i] the texture's colors. Can be used for mood-coloring or to simulate the color of light.
+ A color value used to [i]multiply[/i] the texture's colors. Can be used for mood-coloring or to simulate the color of ambient light.
+ [b]Note:[/b] Unlike [member CanvasItem.modulate] for 2D, colors with values above [code]1.0[/code] (overbright) are not supported.
[b]Note:[/b] If a [member GeometryInstance3D.material_override] is defined on the [SpriteBase3D], the material override must be configured to take vertex colors into account for albedo. Otherwise, the color defined in [member modulate] will be ignored. For a [BaseMaterial3D], [member BaseMaterial3D.vertex_color_use_as_albedo] must be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function.
</member>
<member name="no_depth_test" type="bool" setter="set_draw_flag" getter="get_draw_flag" default="false">
@@ -98,6 +99,7 @@
</member>
<member name="texture_filter" type="int" setter="set_texture_filter" getter="get_texture_filter" enum="BaseMaterial3D.TextureFilter" default="3">
Filter flags for the texture. See [enum BaseMaterial3D.TextureFilter] for options.
+ [b]Note:[/b] Linear filtering may cause artifacts around the edges, which are especially noticeable on opaque textures. To prevent this, use textures with transparent or identical colors around the edges.
</member>
<member name="transparent" type="bool" setter="set_draw_flag" getter="get_draw_flag" default="true">
If [code]true[/code], the texture's transparency and the opacity are used to make those parts of the sprite invisible.
diff --git a/doc/classes/StatusIndicator.xml b/doc/classes/StatusIndicator.xml
new file mode 100644
index 0000000000..b92018e172
--- /dev/null
+++ b/doc/classes/StatusIndicator.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="StatusIndicator" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Application status indicator (aka notification area icon).
+ [b]Note:[/b] Status indicator is implemented on macOS and Windows.
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="icon" type="Image" setter="set_icon" getter="get_icon">
+ Status indicator icon.
+ </member>
+ <member name="tooltip" type="String" setter="set_tooltip" getter="get_tooltip" default="&quot;&quot;">
+ Status indicator tooltip.
+ </member>
+ <member name="visible" type="bool" setter="set_visible" getter="is_visible" default="true">
+ If [code]true[/code], the status indicator is visible.
+ </member>
+ </members>
+ <signals>
+ <signal name="pressed">
+ <param index="0" name="mouse_button" type="int" />
+ <param index="1" name="position" type="Vector2i" />
+ <description>
+ Emitted when the status indicator is pressed.
+ </description>
+ </signal>
+ </signals>
+</class>
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index ea8185fff4..6a1879baa4 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -101,6 +101,12 @@
Adjust the viewport so the caret is visible.
</description>
</method>
+ <method name="apply_ime">
+ <return type="void" />
+ <description>
+ Applies text from the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME) to each caret and closes the IME if it is open.
+ </description>
+ </method>
<method name="backspace">
<return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
@@ -114,6 +120,12 @@
Starts a multipart edit. All edits will be treated as one action until [method end_complex_operation] is called.
</description>
</method>
+ <method name="cancel_ime">
+ <return type="void" />
+ <description>
+ Closes the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME) if it is open. Any text in the IME will be lost.
+ </description>
+ </method>
<method name="center_viewport_to_caret">
<return type="void" />
<param index="0" name="caret_index" type="int" default="0" />
@@ -611,7 +623,7 @@
<method name="has_ime_text" qualifiers="const">
<return type="bool" />
<description>
- Returns if the user has IME text.
+ Returns [code]true[/code] if the user has text in the [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url] (IME).
</description>
</method>
<method name="has_redo" qualifiers="const">
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index bf5a504aba..88cda5ae10 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -353,7 +353,7 @@
This controls the drop sections, i.e. the decision and drawing of possible drop locations based on the mouse position.
</member>
<member name="enable_recursive_folding" type="bool" setter="set_enable_recursive_folding" getter="is_recursive_folding_enabled" default="true">
- If [code]true[/code], recursive folding is enabled for this [Tree]. Holding down Shift while clicking the fold arrow collapses or uncollapses the [TreeItem] and all its descendants.
+ If [code]true[/code], recursive folding is enabled for this [Tree]. Holding down [kbd]Shift[/kbd] while clicking the fold arrow or using [code]ui_right[/code]/[code]ui_left[/code] shortcuts collapses or uncollapses the [TreeItem] and all its descendants.
</member>
<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="2" />
<member name="hide_folding" type="bool" setter="set_hide_folding" getter="is_folding_hidden" default="false">
diff --git a/editor/audio_stream_preview.cpp b/editor/audio_stream_preview.cpp
index 0b9289ff06..8dd4820c4e 100644
--- a/editor/audio_stream_preview.cpp
+++ b/editor/audio_stream_preview.cpp
@@ -143,11 +143,11 @@ void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) {
}
for (int j = from; j < to; j++) {
- max = MAX(max, mix_chunk[j].l);
- max = MAX(max, mix_chunk[j].r);
+ max = MAX(max, mix_chunk[j].left);
+ max = MAX(max, mix_chunk[j].right);
- min = MIN(min, mix_chunk[j].l);
- min = MIN(min, mix_chunk[j].r);
+ min = MIN(min, mix_chunk[j].left);
+ min = MIN(min, mix_chunk[j].right);
}
uint8_t pfrom = CLAMP((min * 0.5 + 0.5) * 255, 0, 255);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 6ec656d588..89ee57e190 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -6795,8 +6795,8 @@ EditorNode::EditorNode() {
export_as_menu->connect("index_pressed", callable_mp(this, &EditorNode::_export_as_menu_option));
file_menu->add_separator();
- file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true, true);
- file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true, true);
+ file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, false, true);
+ file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, false, true);
file_menu->add_separator();
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE);
diff --git a/editor/icons/StatusIndicator.svg b/editor/icons/StatusIndicator.svg
new file mode 100644
index 0000000000..09673b3354
--- /dev/null
+++ b/editor/icons/StatusIndicator.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1 5.184 3H2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-3.184ZM3 5h2.584L8 3.4 10.416 5H13v8H3Z" fill="#e0e0e0"/></svg>
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index dfb14c6741..f7fd7d0589 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -31,6 +31,7 @@
#include "resource_importer_scene.h"
#include "core/error/error_macros.h"
+#include "core/io/dir_access.h"
#include "core/io/resource_saver.h"
#include "core/object/script_language.h"
#include "editor/editor_node.h"
@@ -2390,6 +2391,21 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file, const HashM
return scene;
}
+Error ResourceImporterScene::_check_resource_save_paths(const Dictionary &p_data) {
+ Array keys = p_data.keys();
+ for (int i = 0; i < keys.size(); i++) {
+ const Dictionary &settings = p_data[keys[i]];
+
+ if (bool(settings.get("save_to_file/enabled", false)) && settings.has("save_to_file/path")) {
+ const String &save_path = settings["save_to_file/path"];
+
+ ERR_FAIL_COND_V(!save_path.is_empty() && !DirAccess::exists(save_path.get_base_dir()), ERR_FILE_BAD_PATH);
+ }
+ }
+
+ return OK;
+}
+
Error ResourceImporterScene::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
const String &src_path = p_source_file;
@@ -2442,7 +2458,42 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
import_flags |= EditorSceneFormatImporter::IMPORT_FORCE_DISABLE_MESH_COMPRESSION;
}
+ Dictionary subresources = p_options["_subresources"];
+
+ Dictionary node_data;
+ if (subresources.has("nodes")) {
+ node_data = subresources["nodes"];
+ }
+
+ Dictionary material_data;
+ if (subresources.has("materials")) {
+ material_data = subresources["materials"];
+ }
+
+ Dictionary animation_data;
+ if (subresources.has("animations")) {
+ animation_data = subresources["animations"];
+ }
+
+ Dictionary mesh_data;
+ if (subresources.has("meshes")) {
+ mesh_data = subresources["meshes"];
+ }
+
Error err = OK;
+
+ // Check whether any of the meshes or animations have nonexistent save paths
+ // and if they do, fail the import immediately.
+ err = _check_resource_save_paths(mesh_data);
+ if (err != OK) {
+ return err;
+ }
+
+ err = _check_resource_save_paths(animation_data);
+ if (err != OK) {
+ return err;
+ }
+
List<String> missing_deps; // for now, not much will be done with this
Node *scene = importer->import_scene(src_path, import_flags, p_options, &missing_deps, &err);
if (!scene || err != OK) {
@@ -2466,22 +2517,6 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
scene_3d->scale(scale);
}
}
- Dictionary subresources = p_options["_subresources"];
-
- Dictionary node_data;
- if (subresources.has("nodes")) {
- node_data = subresources["nodes"];
- }
-
- Dictionary material_data;
- if (subresources.has("materials")) {
- material_data = subresources["materials"];
- }
-
- Dictionary animation_data;
- if (subresources.has("animations")) {
- animation_data = subresources["animations"];
- }
HashSet<Ref<ImporterMesh>> scanned_meshes;
HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> collision_map;
@@ -2561,10 +2596,6 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
}
}
- Dictionary mesh_data;
- if (subresources.has("meshes")) {
- mesh_data = subresources["meshes"];
- }
scene = _generate_meshes(scene, mesh_data, gen_lods, create_shadow_meshes, LightBakeMode(light_bake_mode), lightmap_texel_size, src_lightmap_cache, mesh_lightmap_caches);
if (mesh_lightmap_caches.size()) {
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index 6ea4d1af7d..f82a5aa9e9 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -214,6 +214,7 @@ class ResourceImporterScene : public ResourceImporter {
SHAPE_TYPE_CAPSULE,
};
+ static Error _check_resource_save_paths(const Dictionary &p_data);
Array _get_skinned_pose_transforms(ImporterMeshInstance3D *p_src_mesh_node);
void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner);
Node *_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 3ea42a48ae..987a28adc9 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -5783,7 +5783,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
target_position = canvas_item_editor->snap_point(target_position);
CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent);
- Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().xform_inv(target_position) : target_position;
+ Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;
undo_redo->add_do_method(child, "set_position", local_target_pos);
}
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 0019922f9c..fbec9ca68f 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -666,11 +666,11 @@ Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_f
}
for (int j = from; j < to; j++) {
- max = MAX(max, frames[j].l);
- max = MAX(max, frames[j].r);
+ max = MAX(max, frames[j].left);
+ max = MAX(max, frames[j].right);
- min = MIN(min, frames[j].l);
- min = MIN(min, frames[j].r);
+ min = MIN(min, frames[j].left);
+ min = MIN(min, frames[j].right);
}
int pfrom = CLAMP((min * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 83b5a136fd..31166075a7 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -3298,7 +3298,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
}
Node3D *parent = sp->get_parent_node_3d();
- Transform3D local_xform = parent ? parent->get_global_transform().inverse_xform(xform) : xform;
+ Transform3D local_xform = parent ? parent->get_global_transform().affine_inverse() * xform : xform;
undo_redo->add_do_method(sp, "set_transform", local_xform);
undo_redo->add_undo_method(sp, "set_transform", sp->get_local_gizmo_transform());
}
@@ -4371,7 +4371,7 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
}
Transform3D new_tf = node3d->get_transform();
- new_tf.origin = parent_tf.xform_inv(preview_node_pos);
+ new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos);
undo_redo->add_do_method(instantiated_scene, "set_transform", new_tf);
}
@@ -6166,7 +6166,7 @@ void Node3DEditor::_xform_dialog_action() {
}
Node3D *parent = sp->get_parent_node_3d();
- Transform3D local_tr = parent ? parent->get_global_transform().inverse_xform(tr) : tr;
+ Transform3D local_tr = parent ? parent->get_global_transform().affine_inverse() * tr : tr;
undo_redo->add_do_method(sp, "set_transform", local_tr);
undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());
}
@@ -7517,7 +7517,7 @@ void Node3DEditor::_snap_selected_nodes_to_floor() {
new_transform.origin = new_transform.origin - position_offset;
Node3D *parent = sp->get_parent_node_3d();
- Transform3D new_local_xform = parent ? parent->get_global_transform().inverse_xform(new_transform) : new_transform;
+ Transform3D new_local_xform = parent ? parent->get_global_transform().affine_inverse() * new_transform : new_transform;
undo_redo->add_do_method(sp, "set_transform", new_local_xform);
undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());
}
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 9baf0c60dd..06aedecd0e 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1272,6 +1272,7 @@ void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) {
void ScriptTextEditor::_edit_option(int p_op) {
CodeEdit *tx = code_editor->get_text_editor();
+ tx->apply_ime();
switch (p_op) {
case EDIT_UNDO: {
@@ -1962,6 +1963,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
}
if (create_menu) {
+ tx->apply_ime();
+
Point2i pos = tx->get_line_column_at_pos(local_pos);
int row = pos.y;
int col = pos.x;
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index a2364278b6..c9a0cbd2de 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -348,6 +348,7 @@ void TextEditor::set_find_replace_bar(FindReplaceBar *p_bar) {
void TextEditor::_edit_option(int p_op) {
CodeEdit *tx = code_editor->get_text_editor();
+ tx->apply_ime();
switch (p_op) {
case EDIT_UNDO: {
@@ -502,6 +503,8 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
if (mb->get_button_index() == MouseButton::RIGHT) {
CodeEdit *tx = code_editor->get_text_editor();
+ tx->apply_ime();
+
Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
int row = pos.y;
int col = pos.x;
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index fe8fb79c56..ea200419f6 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -628,6 +628,8 @@ ShaderTextEditor::ShaderTextEditor() {
/*** SCRIPT EDITOR ******/
void TextShaderEditor::_menu_option(int p_option) {
+ shader_editor->get_text_editor()->apply_ime();
+
switch (p_option) {
case EDIT_UNDO: {
shader_editor->get_text_editor()->undo();
@@ -978,6 +980,8 @@ void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
CodeEdit *tx = shader_editor->get_text_editor();
+ tx->apply_ime();
+
Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
int row = pos.y;
int col = pos.x;
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 8c3fe82f36..53bdf79d87 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -2373,18 +2373,23 @@ void ThemeTypeEditor::_update_type_list_debounced() {
update_debounce_timer->start();
}
-HashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(const StringName &, List<StringName> *) const, bool include_default) {
+HashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, Theme::DataType p_type, bool p_include_default) {
HashMap<StringName, bool> items;
List<StringName> names;
- if (include_default) {
+ if (p_include_default) {
names.clear();
String default_type = p_type_name;
if (edited_theme->get_type_variation_base(p_type_name) != StringName()) {
default_type = edited_theme->get_type_variation_base(p_type_name);
}
- (ThemeDB::get_singleton()->get_default_theme().operator->()->*get_list_func)(default_type, &names);
+ List<ThemeDB::ThemeItemBind> theme_binds;
+ ThemeDB::get_singleton()->get_class_items(default_type, &theme_binds, true, p_type);
+ for (const ThemeDB::ThemeItemBind &E : theme_binds) {
+ names.push_back(E.item_name);
+ }
+
names.sort_custom<StringName::AlphCompare>();
for (const StringName &E : names) {
items[E] = false;
@@ -2393,7 +2398,7 @@ HashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, v
{
names.clear();
- (edited_theme.operator->()->*get_list_func)(p_type_name, &names);
+ edited_theme->get_theme_item_list(p_type, p_type_name, &names);
names.sort_custom<StringName::AlphCompare>();
for (const StringName &E : names) {
items[E] = true;
@@ -2499,7 +2504,7 @@ void ThemeTypeEditor::_update_type_items() {
color_items_list->remove_child(node);
}
- HashMap<StringName, bool> color_items = _get_type_items(edited_type, &Theme::get_color_list, show_default);
+ HashMap<StringName, bool> color_items = _get_type_items(edited_type, Theme::DATA_TYPE_COLOR, show_default);
for (const KeyValue<StringName, bool> &E : color_items) {
HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_COLOR, E.key, E.value);
ColorPickerButton *item_editor = memnew(ColorPickerButton);
@@ -2528,7 +2533,7 @@ void ThemeTypeEditor::_update_type_items() {
constant_items_list->remove_child(node);
}
- HashMap<StringName, bool> constant_items = _get_type_items(edited_type, &Theme::get_constant_list, show_default);
+ HashMap<StringName, bool> constant_items = _get_type_items(edited_type, Theme::DATA_TYPE_CONSTANT, show_default);
for (const KeyValue<StringName, bool> &E : constant_items) {
HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_CONSTANT, E.key, E.value);
SpinBox *item_editor = memnew(SpinBox);
@@ -2561,7 +2566,7 @@ void ThemeTypeEditor::_update_type_items() {
font_items_list->remove_child(node);
}
- HashMap<StringName, bool> font_items = _get_type_items(edited_type, &Theme::get_font_list, show_default);
+ HashMap<StringName, bool> font_items = _get_type_items(edited_type, Theme::DATA_TYPE_FONT, show_default);
for (const KeyValue<StringName, bool> &E : font_items) {
HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT, E.key, E.value);
EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
@@ -2599,7 +2604,7 @@ void ThemeTypeEditor::_update_type_items() {
font_size_items_list->remove_child(node);
}
- HashMap<StringName, bool> font_size_items = _get_type_items(edited_type, &Theme::get_font_size_list, show_default);
+ HashMap<StringName, bool> font_size_items = _get_type_items(edited_type, Theme::DATA_TYPE_FONT_SIZE, show_default);
for (const KeyValue<StringName, bool> &E : font_size_items) {
HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT_SIZE, E.key, E.value);
SpinBox *item_editor = memnew(SpinBox);
@@ -2632,7 +2637,7 @@ void ThemeTypeEditor::_update_type_items() {
icon_items_list->remove_child(node);
}
- HashMap<StringName, bool> icon_items = _get_type_items(edited_type, &Theme::get_icon_list, show_default);
+ HashMap<StringName, bool> icon_items = _get_type_items(edited_type, Theme::DATA_TYPE_ICON, show_default);
for (const KeyValue<StringName, bool> &E : icon_items) {
HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_ICON, E.key, E.value);
EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
@@ -2700,7 +2705,7 @@ void ThemeTypeEditor::_update_type_items() {
stylebox_items_list->add_child(memnew(HSeparator));
}
- HashMap<StringName, bool> stylebox_items = _get_type_items(edited_type, &Theme::get_stylebox_list, show_default);
+ HashMap<StringName, bool> stylebox_items = _get_type_items(edited_type, Theme::DATA_TYPE_STYLEBOX, show_default);
for (const KeyValue<StringName, bool> &E : stylebox_items) {
if (leading_stylebox.pinned && leading_stylebox.item_name == E.key) {
continue;
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index 8ad262da55..ba3446807e 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -373,7 +373,7 @@ class ThemeTypeEditor : public MarginContainer {
VBoxContainer *_create_item_list(Theme::DataType p_data_type);
void _update_type_list();
void _update_type_list_debounced();
- HashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(const StringName &, List<StringName> *) const, bool include_default);
+ HashMap<StringName, bool> _get_type_items(String p_type_name, Theme::DataType p_type, bool p_include_default);
HBoxContainer *_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable);
void _add_focusable(Control *p_control);
void _update_type_items();
diff --git a/main/main.cpp b/main/main.cpp
index 7d64eccbbf..dd0ebdd154 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1949,6 +1949,33 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
BLOCK_DEVICE("ATI", "Radeon (TM) R9 M3");
BLOCK_DEVICE("AMD", "Radeon (TM) R9 M3");
+ // Intel GPUs.
+ BLOCK_DEVICE("0x8086", "0x0042"); // HD Graphics, Gen5, Clarkdale
+ BLOCK_DEVICE("0x8086", "0x0046"); // HD Graphics, Gen5, Arrandale
+ BLOCK_DEVICE("0x8086", "0x010A"); // HD Graphics, Gen6, Sandy Bridge
+ BLOCK_DEVICE("Intel", "Intel HD Graphics 2000");
+ BLOCK_DEVICE("Intel", "Intel(R) HD Graphics 2000");
+ BLOCK_DEVICE("0x8086", "0x0102"); // HD Graphics 2000, Gen6, Sandy Bridge
+ BLOCK_DEVICE("0x8086", "0x0116"); // HD Graphics 3000, Gen6, Sandy Bridge
+ BLOCK_DEVICE("Intel", "Intel HD Graphics 3000");
+ BLOCK_DEVICE("Intel", "Intel(R) HD Graphics 3000");
+ BLOCK_DEVICE("0x8086", "0x0126"); // HD Graphics 3000, Gen6, Sandy Bridge
+ BLOCK_DEVICE("Intel", "Intel HD Graphics P3000");
+ BLOCK_DEVICE("Intel", "Intel(R) HD Graphics P3000");
+ BLOCK_DEVICE("0x8086", "0x0112"); // HD Graphics P3000, Gen6, Sandy Bridge
+ BLOCK_DEVICE("0x8086", "0x0122"); // HD Graphics P3000, Gen6, Sandy Bridge
+ BLOCK_DEVICE("0x8086", "0x015A"); // HD Graphics, Gen7, Ivy Bridge
+ BLOCK_DEVICE("Intel", "Intel HD Graphics 2500");
+ BLOCK_DEVICE("Intel", "Intel(R) HD Graphics 2500");
+ BLOCK_DEVICE("0x8086", "0x0152"); // HD Graphics 2500, Gen7, Ivy Bridge
+ BLOCK_DEVICE("Intel", "Intel HD Graphics 4000");
+ BLOCK_DEVICE("Intel", "Intel(R) HD Graphics 4000");
+ BLOCK_DEVICE("0x8086", "0x0162"); // HD Graphics 4000, Gen7, Ivy Bridge
+ BLOCK_DEVICE("0x8086", "0x0166"); // HD Graphics 4000, Gen7, Ivy Bridge
+ BLOCK_DEVICE("Intel", "Intel HD Graphics P4000");
+ BLOCK_DEVICE("Intel", "Intel(R) HD Graphics P4000");
+ BLOCK_DEVICE("0x8086", "0x016A"); // HD Graphics P4000, Gen7, Ivy Bridge
+
#undef BLOCK_DEVICE
GLOBAL_DEF_RST_NOVAL(PropertyInfo(Variant::ARRAY, "rendering/gl_compatibility/force_angle_on_devices", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::DICTIONARY, PROPERTY_HINT_NONE, String())), device_blocklist);
diff --git a/misc/dist/macos/editor_debug.entitlements b/misc/dist/macos/editor_debug.entitlements
new file mode 100644
index 0000000000..ff3f589121
--- /dev/null
+++ b/misc/dist/macos/editor_debug.entitlements
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.cs.allow-dyld-environment-variables</key>
+ <true/>
+ <key>com.apple.security.cs.allow-jit</key>
+ <true/>
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
+ <true/>
+ <key>com.apple.security.cs.disable-executable-page-protection</key>
+ <true/>
+ <key>com.apple.security.cs.disable-library-validation</key>
+ <true/>
+ <key>com.apple.security.device.audio-input</key>
+ <true/>
+ <key>com.apple.security.device.camera</key>
+ <true/>
+ <key>com.apple.security.get-task-allow</key>
+ <true/>
+</dict>
+</plist>
diff --git a/misc/dist/macos/editor_info_plist.template b/misc/dist/macos/editor_info_plist.template
new file mode 100644
index 0000000000..4b5399c345
--- /dev/null
+++ b/misc/dist/macos/editor_info_plist.template
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>Godot</string>
+ <key>CFBundleName</key>
+ <string>Godot</string>
+ <key>CFBundleIconFile</key>
+ <string>Godot.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.godotengine.godot</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>$short_version</string>
+ <key>CFBundleSignature</key>
+ <string>godot</string>
+ <key>CFBundleVersion</key>
+ <string>$version</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Microphone access is required to capture audio.</string>
+ <key>NSCameraUsageDescription</key>
+ <string>Camera access is required to capture video.</string>
+ <key>NSRequiresAquaSystemAppearance</key>
+ <false/>
+ <key>NSHumanReadableCopyright</key>
+ <string>© 2007-present Juan Linietsky, Ariel Manzur &amp; Godot Engine contributors</string>
+ <key>CFBundleSupportedPlatforms</key>
+ <array>
+ <string>MacOSX</string>
+ </array>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSApplicationCategoryType</key>
+ <string>public.app-category.developer-tools</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.12</string>
+ <key>LSMinimumSystemVersionByArchitecture</key>
+ <dict>
+ <key>x86_64</key>
+ <string>10.12</string>
+ </dict>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.tscn</string>
+ </array>
+ <key>NSExportableTypes</key>
+ <array>
+ <string>public.tscn</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.godot</string>
+ </array>
+ <key>NSExportableTypes</key>
+ <array>
+ <string>public.godot</string>
+ </array>
+ </dict>
+ </array>
+ <key>UTExportedTypeDeclarations</key>
+ <array>
+ <dict>
+ <key>UTTypeIdentifier</key>
+ <string>public.tscn</string>
+ <key>UTTypeReferenceURL</key>
+ <string></string>
+ <key>UTTypeDescription</key>
+ <string>Godot Engine scene</string>
+ <key>UTTypeIconFile</key>
+ <string>Scene.icns</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.data</string>
+ </array>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>scn</string>
+ <string>tscn</string>
+ <string>escn</string>
+ </array>
+ <key>public.mime-type</key>
+ <string>application/x-godot-scene</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeIdentifier</key>
+ <string>public.gd</string>
+ <key>UTTypeReferenceURL</key>
+ <string></string>
+ <key>UTTypeDescription</key>
+ <string>GDScript script</string>
+ <key>UTTypeIconFile</key>
+ <string>GDScript.icns</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.script</string>
+ </array>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>gd</string>
+ </array>
+ <key>public.mime-type</key>
+ <string>application/x-gdscript</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeIdentifier</key>
+ <string>public.res</string>
+ <key>UTTypeReferenceURL</key>
+ <string></string>
+ <key>UTTypeDescription</key>
+ <string>Godot Engine resource</string>
+ <key>UTTypeIconFile</key>
+ <string>Resource.icns</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.data</string>
+ </array>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>res</string>
+ <string>tres</string>
+ </array>
+ <key>public.mime-type</key>
+ <string>application/x-godot-resource</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeIdentifier</key>
+ <string>public.gdshader</string>
+ <key>UTTypeReferenceURL</key>
+ <string></string>
+ <key>UTTypeDescription</key>
+ <string>Godot Engine shader</string>
+ <key>UTTypeIconFile</key>
+ <string>Shader.icns</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.script</string>
+ </array>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>gdshader</string>
+ </array>
+ <key>public.mime-type</key>
+ <string>application/x-godot-shader</string>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeIdentifier</key>
+ <string>public.godot</string>
+ <key>UTTypeReferenceURL</key>
+ <string></string>
+ <key>UTTypeDescription</key>
+ <string>Godot Engine project</string>
+ <key>UTTypeIconFile</key>
+ <string>Project.icns</string>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.data</string>
+ </array>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>godot</string>
+ </array>
+ <key>public.mime-type</key>
+ <string>application/x-godot-project</string>
+ </dict>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index e6003f35df..b235b6f96c 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -177,13 +177,13 @@ int AudioStreamPlaybackOggVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p
if (info.channels > 1) {
for (int frame = 0; frame < frames; frame++) {
- p_buffer[frame].l = pcm[0][frame];
- p_buffer[frame].r = pcm[1][frame];
+ p_buffer[frame].left = pcm[0][frame];
+ p_buffer[frame].right = pcm[1][frame];
}
} else {
for (int frame = 0; frame < frames; frame++) {
- p_buffer[frame].l = pcm[0][frame];
- p_buffer[frame].r = pcm[0][frame];
+ p_buffer[frame].left = pcm[0][frame];
+ p_buffer[frame].right = pcm[0][frame];
}
}
vorbis_synthesis_read(&dsp_state, frames);
diff --git a/platform/android/detect.py b/platform/android/detect.py
index b396e5eb2d..8976e218b3 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -28,6 +28,7 @@ def get_opts():
"android-" + str(get_min_target_api()),
),
BoolVariable("store_release", "Editor build for Google Play Store (for official builds only)", False),
+ BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False),
]
diff --git a/platform/ios/SCsub b/platform/ios/SCsub
index d7c950967c..5a57f3840b 100644
--- a/platform/ios/SCsub
+++ b/platform/ios/SCsub
@@ -2,6 +2,62 @@
Import("env")
+import os, json
+from platform_methods import run_in_subprocess, architectures, lipo, get_build_version, detect_mvk
+import subprocess
+import shutil
+
+
+def generate_bundle(target, source, env):
+ bin_dir = Dir("#bin").abspath
+
+ # Template bundle.
+ app_prefix = "godot." + env["platform"]
+ rel_prefix = "libgodot." + env["platform"] + "." + "template_release"
+ dbg_prefix = "libgodot." + env["platform"] + "." + "template_debug"
+ if env.dev_build:
+ app_prefix += ".dev"
+ rel_prefix += ".dev"
+ dbg_prefix += ".dev"
+ if env["precision"] == "double":
+ app_prefix += ".double"
+ rel_prefix += ".double"
+ dbg_prefix += ".double"
+
+ # Lipo template libraries.
+ rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix + ".a")
+ dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix + ".a")
+ rel_target_bin_sim = lipo(bin_dir + "/" + rel_prefix, ".simulator" + env.extra_suffix + ".a")
+ dbg_target_bin_sim = lipo(bin_dir + "/" + dbg_prefix, ".simulator" + env.extra_suffix + ".a")
+
+ # Assemble Xcode project bundle.
+ app_dir = Dir("#bin/ios_xcode").abspath
+ templ = Dir("#misc/dist/ios_xcode").abspath
+ if os.path.exists(app_dir):
+ shutil.rmtree(app_dir)
+ shutil.copytree(templ, app_dir)
+ if rel_target_bin != "":
+ shutil.copy(rel_target_bin, app_dir + "/libgodot.ios.release.xcframework/ios-arm64/libgodot.a")
+ if dbg_target_bin != "":
+ shutil.copy(dbg_target_bin, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64/libgodot.a")
+ if rel_target_bin_sim != "":
+ shutil.copy(
+ rel_target_bin_sim, app_dir + "/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/libgodot.a"
+ )
+ if dbg_target_bin_sim != "":
+ shutil.copy(
+ dbg_target_bin_sim, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/libgodot.a"
+ )
+ mvk_path = detect_mvk(env, "ios-arm64")
+ if mvk_path != "":
+ shutil.copytree(mvk_path, app_dir + "/MoltenVK.xcframework")
+
+ # ZIP Xcode project bundle.
+ zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath
+ shutil.make_archive(zip_dir, "zip", root_dir=app_dir)
+ shutil.rmtree(app_dir)
+
+
ios_lib = [
"godot_ios.mm",
"os_ios.mm",
@@ -42,3 +98,8 @@ def combine_libs(target=None, source=None, env=None):
combine_command = env_ios.Command("#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], combine_libs)
+
+if env["generate_bundle"]:
+ generate_bundle_command = env.Command("generate_bundle", [], generate_bundle)
+ command = env.AlwaysBuild(generate_bundle_command)
+ env.Depends(command, [combine_command])
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 23f688501b..f8468e3d9e 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -23,6 +23,7 @@ def get_opts():
from SCons.Variables import BoolVariable
return [
+ ("vulkan_sdk_path", "Path to the Vulkan SDK", ""),
(
"IOS_TOOLCHAIN_PATH",
"Path to iOS toolchain",
@@ -31,6 +32,7 @@ def get_opts():
("IOS_SDK_PATH", "Path to the iOS SDK", ""),
BoolVariable("ios_simulator", "Build for iOS Simulator", False),
("ios_triple", "Triple for ios toolchain", ""),
+ BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False),
]
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index c371f2777c..2895dffdfa 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -80,7 +80,7 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
if (!layer) {
ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer.");
}
- wpd.vulkan.layer_ptr = &layer;
+ wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
rendering_context = memnew(RenderingContextDriverVulkanIOS);
}
#endif
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index d35819c34d..f518d7607b 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -1007,6 +1007,12 @@ Error EditorExportPlatformIOS::_convert_to_framework(const String &p_source, con
// Creating Info.plist
{
+ String lib_clean_name = file_name;
+ for (int i = 0; i < lib_clean_name.length(); i++) {
+ if (!is_ascii_alphanumeric_char(lib_clean_name[i]) && lib_clean_name[i] != '.' && lib_clean_name[i] != '-') {
+ lib_clean_name[i] = '-';
+ }
+ }
String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
@@ -1014,7 +1020,7 @@ Error EditorExportPlatformIOS::_convert_to_framework(const String &p_source, con
" <key>CFBundleShortVersionString</key>\n"
" <string>1.0</string>\n"
" <key>CFBundleIdentifier</key>\n"
- " <string>$id.framework.$name</string>\n"
+ " <string>$id.framework.$cl_name</string>\n"
" <key>CFBundleName</key>\n"
" <string>$name</string>\n"
" <key>CFBundleExecutable</key>\n"
@@ -1032,7 +1038,7 @@ Error EditorExportPlatformIOS::_convert_to_framework(const String &p_source, con
" </dict>\n"
"</plist>";
- String info_plist = info_plist_format.replace("$id", p_id).replace("$name", file_name);
+ String info_plist = info_plist_format.replace("$id", p_id).replace("$name", file_name).replace("$cl_name", lib_clean_name);
Ref<FileAccess> f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE);
if (f.is_valid()) {
diff --git a/platform/ios/rendering_context_driver_vulkan_ios.h b/platform/ios/rendering_context_driver_vulkan_ios.h
index 0778993a05..dc85ff738d 100644
--- a/platform/ios/rendering_context_driver_vulkan_ios.h
+++ b/platform/ios/rendering_context_driver_vulkan_ios.h
@@ -35,7 +35,7 @@
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
-#import <UIKit/UIKit.h>
+#import <QuartzCore/CAMetalLayer.h>
class RenderingContextDriverVulkanIOS : public RenderingContextDriverVulkan {
private:
@@ -46,7 +46,7 @@ protected:
public:
struct WindowPlatformData {
- CALayer *const *layer_ptr;
+ CAMetalLayer *const *layer_ptr;
};
RenderingContextDriverVulkanIOS();
diff --git a/platform/ios/rendering_context_driver_vulkan_ios.mm b/platform/ios/rendering_context_driver_vulkan_ios.mm
index 7e9c3e0e44..6a6af1bc41 100644
--- a/platform/ios/rendering_context_driver_vulkan_ios.mm
+++ b/platform/ios/rendering_context_driver_vulkan_ios.mm
@@ -35,22 +35,22 @@
#ifdef USE_VOLK
#include <volk.h>
#else
-#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_metal.h>
#endif
const char *RenderingContextDriverVulkanIOS::_get_platform_surface_extension() const {
- return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
+ return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
}
RenderingContextDriver::SurfaceID RenderingContextDriverVulkanIOS::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
- VkIOSSurfaceCreateInfoMVK create_info = {};
- create_info.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
- create_info.pView = (__bridge const void *)(*wpd->layer_ptr);
+ VkMetalSurfaceCreateInfoEXT create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
+ create_info.pLayer = *wpd->layer_ptr;
VkSurfaceKHR vk_surface = VK_NULL_HANDLE;
- VkResult err = vkCreateIOSSurfaceMVK(instance_get(), &create_info, nullptr, &vk_surface);
+ VkResult err = vkCreateMetalSurfaceEXT(instance_get(), &create_info, nullptr, &vk_surface);
ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID());
Surface *surface = memnew(Surface);
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index 5a93c3a09f..08783ee14a 100644
--- a/platform/macos/SCsub
+++ b/platform/macos/SCsub
@@ -2,8 +2,99 @@
Import("env")
-from platform_methods import run_in_subprocess
+import os, json
+from platform_methods import run_in_subprocess, architectures, lipo, get_build_version
import platform_macos_builders
+import subprocess
+import shutil
+
+
+def generate_bundle(target, source, env):
+ bin_dir = Dir("#bin").abspath
+
+ if env.editor_build:
+ # Editor bundle.
+ prefix = "godot." + env["platform"] + "." + env["target"]
+ if env.dev_build:
+ prefix += ".dev"
+ if env["precision"] == "double":
+ prefix += ".double"
+
+ # Lipo editor executable.
+ target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix)
+
+ # Assemble .app bundle and update version info.
+ app_dir = Dir("#bin/" + (prefix + env.extra_suffix).replace(".", "_") + ".app").abspath
+ templ = Dir("#misc/dist/macos_tools.app").abspath
+ if os.path.exists(app_dir):
+ shutil.rmtree(app_dir)
+ shutil.copytree(templ, app_dir, ignore=shutil.ignore_patterns("Contents/Info.plist"))
+ if not os.path.isdir(app_dir + "/Contents/MacOS"):
+ os.mkdir(app_dir + "/Contents/MacOS")
+ if target_bin != "":
+ shutil.copy(target_bin, app_dir + "/Contents/MacOS/Godot")
+ version = get_build_version(False)
+ short_version = get_build_version(True)
+ with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt") as fin:
+ with open(app_dir + "/Contents/Info.plist", "wt") as fout:
+ for line in fin:
+ line = line.replace("$version", version)
+ line = line.replace("$short_version", short_version)
+ fout.write(line)
+
+ # Sign .app bundle.
+ if env["bundle_sign_identity"] != "":
+ sign_command = [
+ "codesign",
+ "-s",
+ env["bundle_sign_identity"],
+ "--deep",
+ "--force",
+ "--options=runtime",
+ "--entitlements",
+ ]
+ if env.dev_build:
+ sign_command += [Dir("#misc/dist/macos").abspath + "/editor_debug.entitlements"]
+ else:
+ sign_command += [Dir("#misc/dist/macos").abspath + "/editor.entitlements"]
+ sign_command += [app_dir]
+ subprocess.run(sign_command)
+ else:
+ # Template bundle.
+ app_prefix = "godot." + env["platform"]
+ rel_prefix = "godot." + env["platform"] + "." + "template_release"
+ dbg_prefix = "godot." + env["platform"] + "." + "template_debug"
+ if env.dev_build:
+ app_prefix += ".dev"
+ rel_prefix += ".dev"
+ dbg_prefix += ".dev"
+ if env["precision"] == "double":
+ app_prefix += ".double"
+ rel_prefix += ".double"
+ dbg_prefix += ".double"
+
+ # Lipo template executables.
+ rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix)
+ dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix)
+
+ # Assemble .app bundle.
+ app_dir = Dir("#bin/macos_template.app").abspath
+ templ = Dir("#misc/dist/macos_template.app").abspath
+ if os.path.exists(app_dir):
+ shutil.rmtree(app_dir)
+ shutil.copytree(templ, app_dir)
+ if not os.path.isdir(app_dir + "/Contents/MacOS"):
+ os.mkdir(app_dir + "/Contents/MacOS")
+ if rel_target_bin != "":
+ shutil.copy(rel_target_bin, app_dir + "/Contents/MacOS/godot_macos_release.universal")
+ if dbg_target_bin != "":
+ shutil.copy(dbg_target_bin, app_dir + "/Contents/MacOS/godot_macos_debug.universal")
+
+ # ZIP .app bundle.
+ zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath
+ shutil.make_archive(zip_dir, "zip", root_dir=bin_dir, base_dir="macos_template.app")
+ shutil.rmtree(app_dir)
+
files = [
"os_macos.mm",
@@ -14,6 +105,7 @@ files = [
"display_server_macos.mm",
"godot_button_view.mm",
"godot_content_view.mm",
+ "godot_status_item.mm",
"godot_window_delegate.mm",
"godot_window.mm",
"key_mapping_macos.mm",
@@ -33,3 +125,8 @@ prog = env.add_program("#bin/godot", files)
if env["debug_symbols"] and env["separate_debug_symbols"]:
env.AddPostAction(prog, run_in_subprocess(platform_macos_builders.make_debug_macos))
+
+if env["generate_bundle"]:
+ generate_bundle_command = env.Command("generate_bundle", [], generate_bundle)
+ command = env.AlwaysBuild(generate_bundle_command)
+ env.Depends(command, [prog])
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index 0d1e40fb3d..54eeb833fa 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -1,7 +1,7 @@
import os
import sys
from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang
-from platform_methods import detect_arch
+from platform_methods import detect_arch, detect_mvk
from typing import TYPE_CHECKING
@@ -33,6 +33,12 @@ def get_opts():
BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False),
BoolVariable("use_coverage", "Use instrumentation codes in the binary (e.g. for code coverage)", False),
("angle_libs", "Path to the ANGLE static libraries", ""),
+ (
+ "bundle_sign_identity",
+ "The 'Full Name', 'Common Name' or SHA-1 hash of the signing identity used to sign editor .app bundle.",
+ "-",
+ ),
+ BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False),
]
@@ -53,47 +59,6 @@ def get_flags():
]
-def get_mvk_sdk_path():
- def int_or_zero(i):
- try:
- return int(i)
- except:
- return 0
-
- def ver_parse(a):
- return [int_or_zero(i) for i in a.split(".")]
-
- dirname = os.path.expanduser("~/VulkanSDK")
- if not os.path.exists(dirname):
- return ""
-
- ver_min = ver_parse("1.3.231.0")
- ver_num = ver_parse("0.0.0.0")
- files = os.listdir(dirname)
- lib_name_out = dirname
- for file in files:
- if os.path.isdir(os.path.join(dirname, file)):
- ver_comp = ver_parse(file)
- if ver_comp > ver_num and ver_comp >= ver_min:
- # Try new SDK location.
- lib_name = os.path.join(
- os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
- )
- if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
- ver_num = ver_comp
- lib_name_out = lib_name
- else:
- # Try old SDK location.
- lib_name = os.path.join(
- os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
- )
- if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
- ver_num = ver_comp
- lib_name_out = lib_name
-
- return lib_name_out
-
-
def configure(env: "Environment"):
# Validate arch.
supported_arches = ["x86_64", "arm64"]
@@ -274,32 +239,11 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"])
if not env["use_volk"]:
env.Append(LINKFLAGS=["-lMoltenVK"])
- mvk_found = False
-
- mvk_list = [get_mvk_sdk_path(), "/opt/homebrew/lib", "/usr/local/homebrew/lib", "/opt/local/lib"]
- if env["vulkan_sdk_path"] != "":
- mvk_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"]))
- mvk_list.insert(
- 0,
- os.path.join(
- os.path.expanduser(env["vulkan_sdk_path"]), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
- ),
- )
- mvk_list.insert(
- 0,
- os.path.join(
- os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
- ),
- )
-
- for mvk_path in mvk_list:
- if mvk_path and os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")):
- mvk_found = True
- print("MoltenVK found at: " + mvk_path)
- env.Append(LINKFLAGS=["-L" + mvk_path])
- break
+ mvk_path = detect_mvk(env, "macos-arm64_x86_64")
- if not mvk_found:
+ if mvk_path != "":
+ env.Append(LINKFLAGS=["-L" + os.path.join(mvk_path, "macos-arm64_x86_64")])
+ else:
print(
"MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path."
)
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index e298b54970..10c8abe663 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -201,6 +201,14 @@ private:
HashMap<WindowID, WindowData> windows;
+ struct IndicatorData {
+ id view;
+ id item;
+ };
+
+ IndicatorID indicator_id_counter = 0;
+ HashMap<IndicatorID, IndicatorData> indicators;
+
IOPMAssertionID screen_keep_on_assertion = kIOPMNullAssertionID;
struct MenuCall {
@@ -486,6 +494,12 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
+ virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override;
+ virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override;
+ virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override;
+ virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override;
+ virtual void delete_status_indicator(IndicatorID p_id) override;
+
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);
static Vector<String> get_rendering_drivers_func();
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index ad8afaf46b..19632dd799 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -35,6 +35,7 @@
#include "godot_menu_delegate.h"
#include "godot_menu_item.h"
#include "godot_open_save_delegate.h"
+#include "godot_status_item.h"
#include "godot_window.h"
#include "godot_window_delegate.h"
#include "key_mapping_macos.h"
@@ -203,7 +204,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
} wpd;
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
- wpd.vulkan.view_ptr = &wd.window_view;
+ wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
}
#endif
Error err = rendering_context->window_create(window_id_counter, &wpd);
@@ -838,6 +839,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_EXTEND_TO_TITLE:
case FEATURE_SCREEN_CAPTURE:
+ case FEATURE_STATUS_INDICATOR:
return true;
default: {
}
@@ -4296,6 +4298,124 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) {
}
}
+DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
+ NSImage *nsimg = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nullptr
+ pixelsWide:img->get_width()
+ pixelsHigh:img->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:img->get_width() * 4
+ bitsPerPixel:32];
+ if (imgrep) {
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = img->get_width() * img->get_height();
+ const uint8_t *r = img->get_data().ptr();
+
+ /* Premultiply the alpha channel */
+ for (int i = 0; i < len; i++) {
+ uint8_t alpha = r[i * 4 + 3];
+ pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
+ pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
+ pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
+ pixels[i * 4 + 3] = alpha;
+ }
+
+ nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+ if (nsimg) {
+ [nsimg addRepresentation:imgrep];
+ }
+ }
+ }
+
+ IndicatorData idat;
+
+ idat.item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
+ idat.view = [[GodotStatusItemView alloc] init];
+
+ [idat.view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+ [idat.view setImage:nsimg];
+ [idat.view setCallback:p_callback];
+ [idat.item setView:idat.view];
+
+ IndicatorID iid = indicator_id_counter++;
+ indicators[iid] = idat;
+
+ return iid;
+}
+
+void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ NSImage *nsimg = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+
+ NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nullptr
+ pixelsWide:img->get_width()
+ pixelsHigh:img->get_height()
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:img->get_width() * 4
+ bitsPerPixel:32];
+ if (imgrep) {
+ uint8_t *pixels = [imgrep bitmapData];
+
+ int len = img->get_width() * img->get_height();
+ const uint8_t *r = img->get_data().ptr();
+
+ /* Premultiply the alpha channel */
+ for (int i = 0; i < len; i++) {
+ uint8_t alpha = r[i * 4 + 3];
+ pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
+ pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
+ pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
+ pixels[i * 4 + 3] = alpha;
+ }
+
+ nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
+ if (nsimg) {
+ [nsimg addRepresentation:imgrep];
+ }
+ }
+ }
+
+ [indicators[p_id].view setImage:nsimg];
+}
+
+void DisplayServerMacOS::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [indicators[p_id].view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+}
+
+void DisplayServerMacOS::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [indicators[p_id].view setCallback:p_callback];
+}
+
+void DisplayServerMacOS::delete_status_indicator(IndicatorID p_id) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ [[NSStatusBar systemStatusBar] removeStatusItem:indicators[p_id].item];
+ indicators.erase(p_id);
+}
+
DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) {
DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, r_error));
if (r_error != OK) {
@@ -4700,6 +4820,11 @@ DisplayServerMacOS::~DisplayServerMacOS() {
screen_keep_on_assertion = kIOPMNullAssertionID;
}
+ // Destroy all status indicators.
+ for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
+ [[NSStatusBar systemStatusBar] removeStatusItem:E->value.item];
+ }
+
// Destroy all windows.
for (HashMap<WindowID, WindowData>::Iterator E = windows.begin(); E;) {
HashMap<WindowID, WindowData>::Iterator F = E;
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 7ed78db6f8..9cc57e4066 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -354,6 +354,9 @@ List<String> EditorExportPlatformMacOS::get_binary_extensions(const Ref<EditorEx
list.push_back("dmg");
#endif
list.push_back("zip");
+#ifndef WINDOWS_ENABLED
+ list.push_back("app");
+#endif
} else if (dist_type == 2) {
#ifdef MACOS_ENABLED
list.push_back("pkg");
@@ -497,65 +500,49 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source,
int src_len = p_size * p_size;
Vector<uint8_t> result;
- result.resize(src_len * 1.25); //temp vector for rle encoded data, make it 25% larger for worst case scenario
- int res_size = 0;
-
- uint8_t buf[128];
- int buf_size = 0;
int i = 0;
+ const uint8_t *src = p_source.ptr();
while (i < src_len) {
- uint8_t cur = p_source.ptr()[i * 4 + p_ch];
-
- if (i < src_len - 2) {
- if ((p_source.ptr()[(i + 1) * 4 + p_ch] == cur) && (p_source.ptr()[(i + 2) * 4 + p_ch] == cur)) {
- if (buf_size > 0) {
- result.write[res_size++] = (uint8_t)(buf_size - 1);
- memcpy(&result.write[res_size], &buf, buf_size);
- res_size += buf_size;
- buf_size = 0;
- }
-
- uint8_t lim = i + 130 >= src_len ? src_len - i - 1 : 130;
- bool hit_lim = true;
-
- for (int j = 3; j <= lim; j++) {
- if (p_source.ptr()[(i + j) * 4 + p_ch] != cur) {
- hit_lim = false;
- i = i + j - 1;
- result.write[res_size++] = (uint8_t)(j - 3 + 0x80);
- result.write[res_size++] = cur;
- break;
- }
- }
- if (hit_lim) {
- result.write[res_size++] = (uint8_t)(lim - 3 + 0x80);
- result.write[res_size++] = cur;
- i = i + lim;
- }
- } else {
- buf[buf_size++] = cur;
- if (buf_size == 128) {
- result.write[res_size++] = (uint8_t)(buf_size - 1);
- memcpy(&result.write[res_size], &buf, buf_size);
- res_size += buf_size;
- buf_size = 0;
- }
+ Vector<uint8_t> seq;
+
+ uint8_t count = 0;
+ while (count <= 0x7f && i < src_len) {
+ if (i + 2 < src_len && src[i * 4 + p_ch] == src[(i + 1) * 4 + p_ch] && src[i] == src[(i + 2) * 4 + p_ch]) {
+ break;
}
- } else {
- buf[buf_size++] = cur;
- result.write[res_size++] = (uint8_t)(buf_size - 1);
- memcpy(&result.write[res_size], &buf, buf_size);
- res_size += buf_size;
- buf_size = 0;
+ seq.push_back(src[i * 4 + p_ch]);
+ i++;
+ count++;
+ }
+ if (!seq.is_empty()) {
+ result.push_back(count - 1);
+ result.append_array(seq);
+ }
+ if (i >= src_len) {
+ break;
}
- i++;
+ uint8_t rep = src[i * 4 + p_ch];
+ count = 0;
+ while (count <= 0x7f && i < src_len && src[i * 4 + p_ch] == rep) {
+ i++;
+ count++;
+ }
+ if (count >= 3) {
+ result.push_back(0x80 + count - 3);
+ result.push_back(rep);
+ } else {
+ result.push_back(count - 1);
+ for (int j = 0; j < count; j++) {
+ result.push_back(rep);
+ }
+ }
}
int ofs = p_dest.size();
- p_dest.resize(p_dest.size() + res_size);
- memcpy(&p_dest.write[ofs], result.ptr(), res_size);
+ p_dest.resize(p_dest.size() + result.size());
+ memcpy(&p_dest.write[ofs], result.ptr(), result.size());
}
void EditorExportPlatformMacOS::_make_icon(const Ref<EditorExportPreset> &p_preset, const Ref<Image> &p_icon, Vector<uint8_t> &p_data) {
@@ -618,6 +605,9 @@ void EditorExportPlatformMacOS::_make_icon(const Ref<EditorExportPreset> &p_pres
_rgba8_to_packbits_encode(1, icon_infos[i].size, src_data, data); // Encode G.
_rgba8_to_packbits_encode(2, icon_infos[i].size, src_data, data); // Encode B.
+ // Note: workaround for macOS icon decoder bug corrupting last RLE encoded value.
+ data.push_back(0x00);
+
int len = data.size() - ofs;
len = BSWAP32(len);
memcpy(&data.write[ofs], icon_infos[i].name, 4);
@@ -1116,10 +1106,73 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Relative symlinks are not supported, exported \"%s\" might be broken!"), p_src_path.get_file()));
#endif
print_verbose("export framework: " + p_src_path + " -> " + p_in_app_path);
+
+ bool plist_misssing = false;
+ Ref<PList> plist;
+ plist.instantiate();
+ plist->load_file(p_src_path.path_join("Resources").path_join("Info.plist"));
+
+ Ref<PListNode> root_node = plist->get_root();
+ if (root_node.is_null()) {
+ plist_misssing = true;
+ } else {
+ Dictionary root = root_node->get_value();
+ if (!root.has("CFBundleExecutable") || !root.has("CFBundleIdentifier") || !root.has("CFBundlePackageType") || !root.has("CFBundleInfoDictionaryVersion") || !root.has("CFBundleName") || !root.has("CFBundleSupportedPlatforms")) {
+ plist_misssing = true;
+ }
+ }
+
err = dir_access->make_dir_recursive(p_in_app_path);
if (err == OK) {
err = dir_access->copy_dir(p_src_path, p_in_app_path, -1, true);
}
+ if (err == OK && plist_misssing) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export"), vformat(TTR("\"%s\": Info.plist missing or invalid, new Info.plist generated."), p_src_path.get_file()));
+ // Generate Info.plist
+ String lib_name = p_src_path.get_basename().get_file();
+ String lib_id = p_preset->get("application/bundle_identifier");
+ String lib_clean_name = lib_name;
+ for (int i = 0; i < lib_clean_name.length(); i++) {
+ if (!is_ascii_alphanumeric_char(lib_clean_name[i]) && lib_clean_name[i] != '.' && lib_clean_name[i] != '-') {
+ lib_clean_name[i] = '-';
+ }
+ }
+
+ String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ " <dict>\n"
+ " <key>CFBundleExecutable</key>\n"
+ " <string>$name</string>\n"
+ " <key>CFBundleIdentifier</key>\n"
+ " <string>$id.framework.$cl_name</string>\n"
+ " <key>CFBundleInfoDictionaryVersion</key>\n"
+ " <string>6.0</string>\n"
+ " <key>CFBundleName</key>\n"
+ " <string>$name</string>\n"
+ " <key>CFBundlePackageType</key>\n"
+ " <string>FMWK</string>\n"
+ " <key>CFBundleShortVersionString</key>\n"
+ " <string>1.0.0</string>\n"
+ " <key>CFBundleSupportedPlatforms</key>\n"
+ " <array>\n"
+ " <string>MacOSX</string>\n"
+ " </array>\n"
+ " <key>CFBundleVersion</key>\n"
+ " <string>1.0.0</string>\n"
+ " <key>LSMinimumSystemVersion</key>\n"
+ " <string>10.12</string>\n"
+ " </dict>\n"
+ "</plist>";
+
+ String info_plist = info_plist_format.replace("$id", lib_id).replace("$name", lib_name).replace("$cl_name", lib_clean_name);
+
+ err = dir_access->make_dir_recursive(p_in_app_path.path_join("Resources"));
+ Ref<FileAccess> f = FileAccess::open(p_in_app_path.path_join("Resources").path_join("Info.plist"), FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_string(info_plist);
+ }
+ }
} else {
print_verbose("export dylib: " + p_src_path + " -> " + p_in_app_path);
err = dir_access->copy(p_src_path, p_in_app_path);
@@ -1954,6 +2007,8 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
err = _code_sign(p_preset, tmp_app_path_name, ent_path);
}
+ String noto_path = p_path;
+ bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0);
if (export_format == "dmg") {
// Create a DMG.
if (err == OK) {
@@ -1995,17 +2050,36 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
zipClose(zip, nullptr);
}
+ } else if (export_format == "app" && noto_enabled) {
+ // Create temporary ZIP.
+ if (err == OK) {
+ noto_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name + ".zip");
+
+ if (ep.step(TTR("Making ZIP"), 3)) {
+ return ERR_SKIP;
+ }
+ if (FileAccess::exists(noto_path)) {
+ OS::get_singleton()->move_to_trash(noto_path);
+ }
+
+ Ref<FileAccess> io_fa_dst;
+ zlib_filefunc_def io_dst = zipio_create_io(&io_fa_dst);
+ zipFile zip = zipOpen2(noto_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst);
+
+ zip_folder_recursive(zip, tmp_base_path_name, tmp_app_dir_name, pkg_name);
+
+ zipClose(zip, nullptr);
+ }
}
- bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0);
if (err == OK && noto_enabled) {
- if (export_format == "app" || export_format == "pkg") {
+ if (export_format == "pkg") {
add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead."));
} else {
if (ep.step(TTR("Sending archive for notarization"), 4)) {
return ERR_SKIP;
}
- err = _notarize(p_preset, p_path);
+ err = _notarize(p_preset, noto_path);
}
}
@@ -2024,6 +2098,10 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
tmp_app_dir->change_dir("..");
tmp_app_dir->remove(pkg_name);
}
+ } else if (noto_path != p_path) {
+ if (FileAccess::exists(noto_path)) {
+ DirAccess::remove_file_or_error(noto_path);
+ }
}
}
diff --git a/platform/macos/godot_content_view.h b/platform/macos/godot_content_view.h
index c6060c96c6..dc8d11be54 100644
--- a/platform/macos/godot_content_view.h
+++ b/platform/macos/godot_content_view.h
@@ -72,7 +72,7 @@
- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor;
- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy;
-- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed;
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed outofstream:(bool)outofstream;
- (void)setWindowID:(DisplayServer::WindowID)wid;
- (void)updateLayerDelegate;
- (void)cancelComposition;
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index f6f054c1e6..93bba84783 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -356,7 +356,7 @@
ds->cursor_update_shape();
}
-- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed {
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed outofstream:(bool)outofstream {
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (!ds || !ds->has_window(window_id)) {
return;
@@ -377,14 +377,18 @@
Ref<InputEventMouseButton> mb;
mb.instantiate();
mb->set_window_id(window_id);
- ds->update_mouse_pos(wd, [event locationInWindow]);
+ if (outofstream) {
+ ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+ } else {
+ ds->update_mouse_pos(wd, [event locationInWindow]);
+ }
ds->get_key_modifier_state([event modifierFlags], mb);
mb->set_button_index(index);
mb->set_pressed(pressed);
mb->set_position(wd.mouse_pos);
mb->set_global_position(wd.mouse_pos);
mb->set_button_mask(last_button_state);
- if (index == MouseButton::LEFT && pressed) {
+ if (!outofstream && index == MouseButton::LEFT && pressed) {
mb->set_double_click([event clickCount] == 2);
}
@@ -394,10 +398,10 @@
- (void)mouseDown:(NSEvent *)event {
if (([event modifierFlags] & NSEventModifierFlagControl)) {
mouse_down_control = true;
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:true];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:true outofstream:false];
} else {
mouse_down_control = false;
- [self processMouseEvent:event index:MouseButton::LEFT pressed:true];
+ [self processMouseEvent:event index:MouseButton::LEFT pressed:true outofstream:false];
}
}
@@ -407,9 +411,9 @@
- (void)mouseUp:(NSEvent *)event {
if (mouse_down_control) {
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:false];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:false outofstream:false];
} else {
- [self processMouseEvent:event index:MouseButton::LEFT pressed:false];
+ [self processMouseEvent:event index:MouseButton::LEFT pressed:false outofstream:false];
}
}
@@ -458,7 +462,7 @@
}
- (void)rightMouseDown:(NSEvent *)event {
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:true];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:true outofstream:false];
}
- (void)rightMouseDragged:(NSEvent *)event {
@@ -466,16 +470,16 @@
}
- (void)rightMouseUp:(NSEvent *)event {
- [self processMouseEvent:event index:MouseButton::RIGHT pressed:false];
+ [self processMouseEvent:event index:MouseButton::RIGHT pressed:false outofstream:false];
}
- (void)otherMouseDown:(NSEvent *)event {
if ((int)[event buttonNumber] == 2) {
- [self processMouseEvent:event index:MouseButton::MIDDLE pressed:true];
+ [self processMouseEvent:event index:MouseButton::MIDDLE pressed:true outofstream:false];
} else if ((int)[event buttonNumber] == 3) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:true outofstream:false];
} else if ((int)[event buttonNumber] == 4) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:true outofstream:false];
} else {
return;
}
@@ -487,16 +491,31 @@
- (void)otherMouseUp:(NSEvent *)event {
if ((int)[event buttonNumber] == 2) {
- [self processMouseEvent:event index:MouseButton::MIDDLE pressed:false];
+ [self processMouseEvent:event index:MouseButton::MIDDLE pressed:false outofstream:false];
} else if ((int)[event buttonNumber] == 3) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:false];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:false outofstream:false];
} else if ((int)[event buttonNumber] == 4) {
- [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:false];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:false outofstream:false];
} else {
return;
}
}
+- (void)swipeWithEvent:(NSEvent *)event {
+ // Swipe gesture on Trackpad/Magic Mouse, or physical back/forward mouse buttons.
+ if ([event phase] == NSEventPhaseEnded || [event phase] == NSEventPhaseChanged) {
+ if (Math::is_equal_approx([event deltaX], 1.0)) {
+ // Swipe left (back).
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:true outofstream:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 pressed:false outofstream:true];
+ } else if (Math::is_equal_approx([event deltaX], -1.0)) {
+ // Swipe right (forward).
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:true outofstream:true];
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 pressed:false outofstream:true];
+ }
+ }
+}
+
- (void)mouseExited:(NSEvent *)event {
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (!ds || !ds->has_window(window_id)) {
diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm
index 306015d644..6b55b70629 100644
--- a/platform/macos/godot_open_save_delegate.mm
+++ b/platform/macos/godot_open_save_delegate.mm
@@ -103,7 +103,7 @@
}
NSMutableArray *new_allowed_types = [[NSMutableArray alloc] init];
- bool allow_other = false;
+ bool has_type_popup = false;
{
NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
if (@available(macOS 10.14, *)) {
@@ -113,13 +113,38 @@
label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
}
- NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
- if (p_filters.is_empty()) {
- [popup addItemWithTitle:@"All Files"];
- }
+ if (p_filters.size() > 1) {
+ NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ for (int i = 0; i < p_filters.size(); i++) {
+ Vector<String> tokens = p_filters[i].split(";");
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+
+ NSMutableArray *type_filters = [[NSMutableArray alloc] init];
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (!str.is_empty()) {
+ [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
+ }
+ }
+
+ if ([type_filters count] > 0) {
+ NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1].strip_edges(), tokens[0].strip_edges())).utf8().get_data()];
+ [new_allowed_types addObject:type_filters];
+ [popup addItemWithTitle:name_str];
+ }
+ }
+ }
+ if (popup.numberOfItems > 1) {
+ has_type_popup = true;
+ popup.target = self;
+ popup.action = @selector(popupFileAction:);
- for (int i = 0; i < p_filters.size(); i++) {
- Vector<String> tokens = p_filters[i].split(";");
+ [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]];
+ }
+ } else if (p_filters.size() == 1) {
+ Vector<String> tokens = p_filters[0].split(";");
if (tokens.size() >= 1) {
String flt = tokens[0].strip_edges();
int filter_slice_count = flt.get_slice_count(",");
@@ -127,25 +152,17 @@
NSMutableArray *type_filters = [[NSMutableArray alloc] init];
for (int j = 0; j < filter_slice_count; j++) {
String str = (flt.get_slice(",", j).strip_edges());
- if (str.strip_edges() == "*.*" || str.strip_edges() == "*") {
- allow_other = true;
- } else if (!str.is_empty()) {
+ if (!str.is_empty()) {
[type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
}
}
if ([type_filters count] > 0) {
- NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()];
[new_allowed_types addObject:type_filters];
- [popup addItemWithTitle:name_str];
}
}
}
[self setFileTypes:new_allowed_types];
- popup.target = self;
- popup.action = @selector(popupFileAction:);
-
- [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]];
}
[base_view addSubview:view];
@@ -154,12 +171,21 @@
[constraints addObject:[base_view.centerXAnchor constraintEqualToAnchor:view.centerXAnchor constant:10]];
[NSLayoutConstraint activateConstraints:constraints];
- [p_panel setAllowsOtherFileTypes:allow_other];
- if (option_count > 0 || [new_allowed_types count] > 0) {
+ if (option_count > 0 || has_type_popup) {
[p_panel setAccessoryView:base_view];
}
if ([new_allowed_types count] > 0) {
- [p_panel setAllowedFileTypes:[new_allowed_types objectAtIndex:0]];
+ NSMutableArray *type_filters = [new_allowed_types objectAtIndex:0];
+ if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) {
+ [p_panel setAllowedFileTypes:@[]];
+ [p_panel setAllowsOtherFileTypes:true];
+ } else {
+ [p_panel setAllowsOtherFileTypes:false];
+ [p_panel setAllowedFileTypes:type_filters];
+ }
+ } else {
+ [p_panel setAllowedFileTypes:@[]];
+ [p_panel setAllowsOtherFileTypes:true];
}
}
@@ -220,10 +246,18 @@
if (btn) {
NSUInteger index = [btn indexOfSelectedItem];
if (allowed_types && index < [allowed_types count]) {
- [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
+ NSMutableArray *type_filters = [allowed_types objectAtIndex:index];
+ if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) {
+ [dialog setAllowedFileTypes:@[]];
+ [dialog setAllowsOtherFileTypes:true];
+ } else {
+ [dialog setAllowsOtherFileTypes:false];
+ [dialog setAllowedFileTypes:type_filters];
+ }
cur_index = index;
} else {
[dialog setAllowedFileTypes:@[]];
+ [dialog setAllowsOtherFileTypes:true];
cur_index = -1;
}
}
diff --git a/platform/macos/godot_status_item.h b/platform/macos/godot_status_item.h
new file mode 100644
index 0000000000..1827baa9bd
--- /dev/null
+++ b/platform/macos/godot_status_item.h
@@ -0,0 +1,51 @@
+/**************************************************************************/
+/* godot_status_item.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GODOT_STATUS_ITEM_H
+#define GODOT_STATUS_ITEM_H
+
+#include "core/input/input_enums.h"
+#include "core/variant/callable.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotStatusItemView : NSView {
+ NSImage *image;
+ Callable cb;
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index;
+- (void)setImage:(NSImage *)image;
+- (void)setCallback:(const Callable &)callback;
+
+@end
+
+#endif // GODOT_STATUS_ITEM_H
diff --git a/platform/macos/godot_status_item.mm b/platform/macos/godot_status_item.mm
new file mode 100644
index 0000000000..71ed0a0f71
--- /dev/null
+++ b/platform/macos/godot_status_item.mm
@@ -0,0 +1,101 @@
+/**************************************************************************/
+/* godot_status_item.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "godot_status_item.h"
+
+#include "display_server_macos.h"
+
+@implementation GodotStatusItemView
+
+- (id)init {
+ self = [super init];
+ image = nullptr;
+ return self;
+}
+
+- (void)setImage:(NSImage *)newImage {
+ image = newImage;
+ [self setNeedsDisplayInRect:self.frame];
+}
+
+- (void)setCallback:(const Callable &)callback {
+ cb = callback;
+}
+
+- (void)drawRect:(NSRect)rect {
+ if (image) {
+ [image drawInRect:rect];
+ }
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (!ds) {
+ return;
+ }
+
+ if (cb.is_valid()) {
+ Variant v_button = index;
+ Variant v_pos = ds->mouse_get_position();
+ Variant *v_args[2] = { &v_button, &v_pos };
+ Variant ret;
+ Callable::CallError ce;
+ cb.callp((const Variant **)&v_args, 2, ret, ce);
+ }
+}
+
+- (void)mouseDown:(NSEvent *)event {
+ [super mouseDown:event];
+ if (([event modifierFlags] & NSEventModifierFlagControl)) {
+ [self processMouseEvent:event index:MouseButton::RIGHT];
+ } else {
+ [self processMouseEvent:event index:MouseButton::LEFT];
+ }
+}
+
+- (void)rightMouseDown:(NSEvent *)event {
+ [super rightMouseDown:event];
+
+ [self processMouseEvent:event index:MouseButton::RIGHT];
+}
+
+- (void)otherMouseDown:(NSEvent *)event {
+ [super otherMouseDown:event];
+
+ if ((int)[event buttonNumber] == 2) {
+ [self processMouseEvent:event index:MouseButton::MIDDLE];
+ } else if ((int)[event buttonNumber] == 3) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1];
+ } else if ((int)[event buttonNumber] == 4) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2];
+ }
+}
+
+@end
diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm
index 93396b0e01..2d83b46007 100644
--- a/platform/macos/godot_window_delegate.mm
+++ b/platform/macos/godot_window_delegate.mm
@@ -362,6 +362,7 @@
}
DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
+ wd.is_visible = ([wd.window_object occlusionState] & NSWindowOcclusionStateVisible) && [wd.window_object isVisible];
if ([wd.window_object isKeyWindow]) {
wd.focused = true;
ds->set_last_focused_window(window_id);
diff --git a/platform/macos/rendering_context_driver_vulkan_macos.h b/platform/macos/rendering_context_driver_vulkan_macos.h
index bbc67581db..32f8891a2e 100644
--- a/platform/macos/rendering_context_driver_vulkan_macos.h
+++ b/platform/macos/rendering_context_driver_vulkan_macos.h
@@ -35,7 +35,7 @@
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
-#import <AppKit/AppKit.h>
+#import <QuartzCore/CAMetalLayer.h>
class RenderingContextDriverVulkanMacOS : public RenderingContextDriverVulkan {
private:
@@ -46,7 +46,7 @@ protected:
public:
struct WindowPlatformData {
- const id *view_ptr;
+ CAMetalLayer *const *layer_ptr;
};
RenderingContextDriverVulkanMacOS();
diff --git a/platform/macos/rendering_context_driver_vulkan_macos.mm b/platform/macos/rendering_context_driver_vulkan_macos.mm
index e0f8bf9e67..afefe5a6f7 100644
--- a/platform/macos/rendering_context_driver_vulkan_macos.mm
+++ b/platform/macos/rendering_context_driver_vulkan_macos.mm
@@ -35,22 +35,22 @@
#ifdef USE_VOLK
#include <volk.h>
#else
-#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_metal.h>
#endif
const char *RenderingContextDriverVulkanMacOS::_get_platform_surface_extension() const {
- return VK_MVK_MACOS_SURFACE_EXTENSION_NAME;
+ return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
}
RenderingContextDriver::SurfaceID RenderingContextDriverVulkanMacOS::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
- VkMacOSSurfaceCreateInfoMVK create_info = {};
- create_info.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
- create_info.pView = (__bridge const void *)(*wpd->view_ptr);
+ VkMetalSurfaceCreateInfoEXT create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
+ create_info.pLayer = *wpd->layer_ptr;
VkSurfaceKHR vk_surface = VK_NULL_HANDLE;
- VkResult err = vkCreateMacOSSurfaceMVK(instance_get(), &create_info, nullptr, &vk_surface);
+ VkResult err = vkCreateMetalSurfaceEXT(instance_get(), &create_info, nullptr, &vk_surface);
ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID());
Surface *surface = memnew(Surface);
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 37b6fa439c..34c8f8e7a1 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -134,7 +134,7 @@ if env["d3d12"]:
)
if not os.getenv("VCINSTALLDIR"):
- if env["debug_symbols"] and env["separate_debug_symbols"]:
+ if env["debug_symbols"]:
env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw))
if env["windows_subsystem"] == "gui":
env.AddPostAction(prog_wrap, run_in_subprocess(platform_windows_builders.make_debug_mingw))
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 80863441ce..96e2f95abd 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -53,6 +53,7 @@
#include <dwmapi.h>
#include <shlwapi.h>
#include <shobjidl.h>
+#include <wbemcli.h>
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
@@ -62,6 +63,8 @@
#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
#endif
+#define WM_INDICATOR_CALLBACK_MESSAGE (WM_USER + 1)
+
#if defined(__GNUC__)
// Workaround GCC warning from -Wcast-function-type.
#define GetProcAddress (void *)GetProcAddress
@@ -107,6 +110,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_SCREEN_CAPTURE:
+ case FEATURE_STATUS_INDICATOR:
return true;
default:
return false;
@@ -2842,6 +2846,172 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) {
}
}
+DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
+ HICON hicon = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon;
+ if (img != icon) {
+ img = img->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+ }
+
+ int w = img->get_width();
+ int h = img->get_height();
+
+ // Create temporary bitmap buffer.
+ int icon_len = 40 + h * w * 4;
+ Vector<BYTE> v;
+ v.resize(icon_len);
+ BYTE *icon_bmp = v.ptrw();
+
+ encode_uint32(40, &icon_bmp[0]);
+ encode_uint32(w, &icon_bmp[4]);
+ encode_uint32(h * 2, &icon_bmp[8]);
+ encode_uint16(1, &icon_bmp[12]);
+ encode_uint16(32, &icon_bmp[14]);
+ encode_uint32(BI_RGB, &icon_bmp[16]);
+ encode_uint32(w * h * 4, &icon_bmp[20]);
+ encode_uint32(0, &icon_bmp[24]);
+ encode_uint32(0, &icon_bmp[28]);
+ encode_uint32(0, &icon_bmp[32]);
+ encode_uint32(0, &icon_bmp[36]);
+
+ uint8_t *wr = &icon_bmp[40];
+ const uint8_t *r = img->get_data().ptr();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
+ uint8_t *wpx = &wr[(i * w + j) * 4];
+ wpx[0] = rpx[2];
+ wpx[1] = rpx[1];
+ wpx[2] = rpx[0];
+ wpx[3] = rpx[3];
+ }
+ }
+
+ hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
+ }
+
+ IndicatorData idat;
+ idat.callback = p_callback;
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = indicator_id_counter;
+ ndat.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
+ ndat.uCallbackMessage = WM_INDICATOR_CALLBACK_MESSAGE;
+ ndat.hIcon = hicon;
+ memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR));
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_ADD, &ndat);
+ Shell_NotifyIconW(NIM_SETVERSION, &ndat);
+
+ IndicatorID iid = indicator_id_counter++;
+ indicators[iid] = idat;
+
+ return iid;
+}
+
+void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ HICON hicon = nullptr;
+ if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
+ Ref<Image> img = p_icon;
+ if (img != icon) {
+ img = img->duplicate();
+ img->convert(Image::FORMAT_RGBA8);
+ }
+
+ int w = img->get_width();
+ int h = img->get_height();
+
+ // Create temporary bitmap buffer.
+ int icon_len = 40 + h * w * 4;
+ Vector<BYTE> v;
+ v.resize(icon_len);
+ BYTE *icon_bmp = v.ptrw();
+
+ encode_uint32(40, &icon_bmp[0]);
+ encode_uint32(w, &icon_bmp[4]);
+ encode_uint32(h * 2, &icon_bmp[8]);
+ encode_uint16(1, &icon_bmp[12]);
+ encode_uint16(32, &icon_bmp[14]);
+ encode_uint32(BI_RGB, &icon_bmp[16]);
+ encode_uint32(w * h * 4, &icon_bmp[20]);
+ encode_uint32(0, &icon_bmp[24]);
+ encode_uint32(0, &icon_bmp[28]);
+ encode_uint32(0, &icon_bmp[32]);
+ encode_uint32(0, &icon_bmp[36]);
+
+ uint8_t *wr = &icon_bmp[40];
+ const uint8_t *r = img->get_data().ptr();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
+ uint8_t *wpx = &wr[(i * w + j) * 4];
+ wpx[0] = rpx[2];
+ wpx[1] = rpx[1];
+ wpx[2] = rpx[0];
+ wpx[3] = rpx[3];
+ }
+ }
+
+ hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
+ }
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = p_id;
+ ndat.uFlags = NIF_ICON;
+ ndat.hIcon = hicon;
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_MODIFY, &ndat);
+}
+
+void DisplayServerWindows::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = p_id;
+ ndat.uFlags = NIF_TIP;
+ memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR));
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_MODIFY, &ndat);
+}
+
+void DisplayServerWindows::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ indicators[p_id].callback = p_callback;
+}
+
+void DisplayServerWindows::delete_status_indicator(IndicatorID p_id) {
+ ERR_FAIL_COND(!indicators.has(p_id));
+
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = p_id;
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_DELETE, &ndat);
+ indicators.erase(p_id);
+}
+
void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
@@ -3351,6 +3521,30 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
}
} break;
+ case WM_INDICATOR_CALLBACK_MESSAGE: {
+ if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN || lParam == WM_MBUTTONDOWN || lParam == WM_XBUTTONDOWN) {
+ IndicatorID iid = (IndicatorID)wParam;
+ MouseButton mb = MouseButton::LEFT;
+ if (lParam == WM_RBUTTONDOWN) {
+ mb = MouseButton::RIGHT;
+ } else if (lParam == WM_MBUTTONDOWN) {
+ mb = MouseButton::MIDDLE;
+ } else if (lParam == WM_XBUTTONDOWN) {
+ mb = MouseButton::MB_XBUTTON1;
+ }
+ if (indicators.has(iid)) {
+ if (indicators[iid].callback.is_valid()) {
+ Variant v_button = mb;
+ Variant v_pos = mouse_get_position();
+ Variant *v_args[2] = { &v_button, &v_pos };
+ Variant ret;
+ Callable::CallError ce;
+ indicators[iid].callback.callp((const Variant **)&v_args, 2, ret, ce);
+ }
+ }
+ return 0;
+ }
+ } break;
case WM_CLOSE: // Did we receive a close message?
{
if (windows[window_id].focus_timer_id != 0U) {
@@ -4779,6 +4973,68 @@ GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr;
PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr;
+Vector2i _get_device_ids(const String &p_device_name) {
+ if (p_device_name.is_empty()) {
+ return Vector2i();
+ }
+
+ REFCLSID clsid = CLSID_WbemLocator; // Unmarshaler CLSID
+ REFIID uuid = IID_IWbemLocator; // Interface UUID
+ IWbemLocator *wbemLocator = NULL; // to get the services
+ IWbemServices *wbemServices = NULL; // to get the class
+ IEnumWbemClassObject *iter = NULL;
+ IWbemClassObject *pnpSDriverObject[1]; // contains driver name, version, etc.
+
+ HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator);
+ if (hr != S_OK) {
+ return Vector2i();
+ }
+ BSTR resource_name = SysAllocString(L"root\\CIMV2");
+ hr = wbemLocator->ConnectServer(resource_name, NULL, NULL, NULL, 0, NULL, NULL, &wbemServices);
+ SysFreeString(resource_name);
+
+ SAFE_RELEASE(wbemLocator) // from now on, use `wbemServices`
+ if (hr != S_OK) {
+ SAFE_RELEASE(wbemServices)
+ return Vector2i();
+ }
+
+ Vector2i ids;
+
+ const String gpu_device_class_query = vformat("SELECT * FROM Win32_PnPSignedDriver WHERE DeviceName = \"%s\"", p_device_name);
+ BSTR query = SysAllocString((const WCHAR *)gpu_device_class_query.utf16().get_data());
+ BSTR query_lang = SysAllocString(L"WQL");
+ hr = wbemServices->ExecQuery(query_lang, query, WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &iter);
+ SysFreeString(query_lang);
+ SysFreeString(query);
+ if (hr == S_OK) {
+ ULONG resultCount;
+ hr = iter->Next(5000, 1, pnpSDriverObject, &resultCount); // Get exactly 1. Wait max 5 seconds.
+
+ if (hr == S_OK && resultCount > 0) {
+ VARIANT did;
+ VariantInit(&did);
+ BSTR object_name = SysAllocString(L"DeviceID");
+ hr = pnpSDriverObject[0]->Get(object_name, 0, &did, NULL, NULL);
+ SysFreeString(object_name);
+ if (hr == S_OK) {
+ String device_id = String(V_BSTR(&did));
+ ids.x = device_id.get_slice("&", 0).lstrip("PCI\\VEN_").hex_to_int();
+ ids.y = device_id.get_slice("&", 1).lstrip("DEV_").hex_to_int();
+ }
+
+ for (ULONG i = 0; i < resultCount; i++) {
+ SAFE_RELEASE(pnpSDriverObject[i])
+ }
+ }
+ }
+
+ SAFE_RELEASE(wbemServices)
+ SAFE_RELEASE(iter)
+
+ return ids;
+}
+
typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_DPI_UNAWARE = 0,
SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
@@ -5000,12 +5256,22 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
bool force_angle = false;
+ Vector2i device_id = _get_device_ids(gl_info["name"]);
Array device_list = GLOBAL_GET("rendering/gl_compatibility/force_angle_on_devices");
for (int i = 0; i < device_list.size(); i++) {
const Dictionary &device = device_list[i];
- if (device.has("vendor") && device.has("name") && gl_info["vendor"].operator String().to_upper().contains(device["vendor"].operator String().to_upper()) && (device["name"] == "*" || gl_info["name"].operator String().to_upper().contains(device["name"].operator String().to_upper()))) {
- force_angle = true;
- break;
+ if (device.has("vendor") && device.has("name")) {
+ const String &vendor = device["vendor"];
+ const String &name = device["name"];
+ if (device_id != Vector2i() && vendor.begins_with("0x") && name.begins_with("0x") && device_id.x == vendor.lstrip("0x").hex_to_int() && device_id.y == name.lstrip("0x").hex_to_int()) {
+ // Check vendor/device IDs.
+ force_angle = true;
+ break;
+ } else if (gl_info["vendor"].operator String().to_upper().contains(vendor.to_upper()) && (name == "*" || gl_info["name"].operator String().to_upper().contains(name.to_upper()))) {
+ // Check vendor/device names.
+ force_angle = true;
+ break;
+ }
}
}
@@ -5166,6 +5432,18 @@ DisplayServerWindows::~DisplayServerWindows() {
cursors_cache.clear();
+ // Destroy all status indicators.
+ for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
+ NOTIFYICONDATAW ndat;
+ ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
+ ndat.cbSize = sizeof(NOTIFYICONDATAW);
+ ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
+ ndat.uID = E->key;
+ ndat.uVersion = NOTIFYICON_VERSION;
+
+ Shell_NotifyIconW(NIM_DELETE, &ndat);
+ }
+
if (mouse_monitor) {
UnhookWindowsHookEx(mouse_monitor);
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 91e7424de9..e66c533da5 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -447,6 +447,13 @@ class DisplayServerWindows : public DisplayServer {
WNDPROC user_proc = nullptr;
+ struct IndicatorData {
+ Callable callback;
+ };
+
+ IndicatorID indicator_id_counter = 0;
+ HashMap<IndicatorID, IndicatorData> indicators;
+
void _send_window_event(const WindowData &wd, WindowEvent p_event);
void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
@@ -655,6 +662,12 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
+ virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override;
+ virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override;
+ virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override;
+ virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override;
+ virtual void delete_status_indicator(IndicatorID p_id) override;
+
virtual void set_context(Context p_context) override;
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);
diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py
index 51652fa814..652dc06acf 100644
--- a/platform/windows/platform_windows_builders.py
+++ b/platform/windows/platform_windows_builders.py
@@ -10,19 +10,21 @@ from platform_methods import subprocess_main
def make_debug_mingw(target, source, env):
- mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
- if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
- else:
- os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
- if try_cmd("strip --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
- else:
- os.system("strip --strip-debug --strip-unneeded {0}".format(target[0]))
- if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
- else:
- os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
+ # Force separate debug symbols if executable size is larger than 1.9 GB.
+ if env["separate_debug_symbols"] or os.stat(target[0]).st_size >= 2040109465:
+ mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
+ if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ else:
+ os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ if try_cmd("strip --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ else:
+ os.system("strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
+ else:
+ os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
if __name__ == "__main__":
diff --git a/platform_methods.py b/platform_methods.py
index 8b2c62ad4a..a05298bfa5 100644
--- a/platform_methods.py
+++ b/platform_methods.py
@@ -140,3 +140,108 @@ def generate_export_icons(platform_path, platform_name):
wf = export_path + "/" + name + "_svg.gen.h"
with open(wf, "w") as svgw:
svgw.write(svg_str)
+
+
+def get_build_version(short):
+ import version
+
+ name = "custom_build"
+ if os.getenv("BUILD_NAME") != None:
+ name = os.getenv("BUILD_NAME")
+ v = "%d.%d" % (version.major, version.minor)
+ if version.patch > 0:
+ v += ".%d" % version.patch
+ status = version.status
+ if not short:
+ if os.getenv("GODOT_VERSION_STATUS") != None:
+ status = str(os.getenv("GODOT_VERSION_STATUS"))
+ v += ".%s.%s" % (status, name)
+ return v
+
+
+def lipo(prefix, suffix):
+ from pathlib import Path
+
+ target_bin = ""
+ lipo_command = ["lipo", "-create"]
+ arch_found = 0
+
+ for arch in architectures:
+ bin_name = prefix + "." + arch + suffix
+ if Path(bin_name).is_file():
+ target_bin = bin_name
+ lipo_command += [bin_name]
+ arch_found += 1
+
+ if arch_found > 1:
+ target_bin = prefix + ".fat" + suffix
+ lipo_command += ["-output", target_bin]
+ subprocess.run(lipo_command)
+
+ return target_bin
+
+
+def get_mvk_sdk_path(osname):
+ def int_or_zero(i):
+ try:
+ return int(i)
+ except:
+ return 0
+
+ def ver_parse(a):
+ return [int_or_zero(i) for i in a.split(".")]
+
+ dirname = os.path.expanduser("~/VulkanSDK")
+ if not os.path.exists(dirname):
+ return ""
+
+ ver_min = ver_parse("1.3.231.0")
+ ver_num = ver_parse("0.0.0.0")
+ files = os.listdir(dirname)
+ lib_name_out = dirname
+ for file in files:
+ if os.path.isdir(os.path.join(dirname, file)):
+ ver_comp = ver_parse(file)
+ if ver_comp > ver_num and ver_comp >= ver_min:
+ # Try new SDK location.
+ lib_name = os.path.join(os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/" + osname + "/")
+ if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
+ ver_num = ver_comp
+ lib_name_out = os.path.join(os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework")
+ else:
+ # Try old SDK location.
+ lib_name = os.path.join(
+ os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/" + osname + "/"
+ )
+ if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
+ ver_num = ver_comp
+ lib_name_out = os.path.join(os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework")
+
+ return lib_name_out
+
+
+def detect_mvk(env, osname):
+ mvk_list = [
+ get_mvk_sdk_path(osname),
+ "/opt/homebrew/Frameworks/MoltenVK.xcframework",
+ "/usr/local/homebrew/Frameworks/MoltenVK.xcframework",
+ "/opt/local/Frameworks/MoltenVK.xcframework",
+ ]
+ if env["vulkan_sdk_path"] != "":
+ mvk_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"]))
+ mvk_list.insert(
+ 0,
+ os.path.join(os.path.expanduser(env["vulkan_sdk_path"]), "macOS/lib/MoltenVK.xcframework"),
+ )
+ mvk_list.insert(
+ 0,
+ os.path.join(os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework"),
+ )
+
+ for mvk_path in mvk_list:
+ if mvk_path and os.path.isfile(os.path.join(mvk_path, osname + "/libMoltenVK.a")):
+ mvk_found = True
+ print("MoltenVK found at: " + mvk_path)
+ return mvk_path
+
+ return ""
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 1fe6cb718c..2bb376c9ab 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -123,20 +123,20 @@ void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tig
switch (AudioServer::get_singleton()->get_speaker_mode()) {
case AudioServer::SPEAKER_SURROUND_71:
- output.write[3].l = volumes[5]; // side-left
- output.write[3].r = volumes[6]; // side-right
+ output.write[3].left = volumes[5]; // side-left
+ output.write[3].right = volumes[6]; // side-right
[[fallthrough]];
case AudioServer::SPEAKER_SURROUND_51:
- output.write[2].l = volumes[3]; // rear-left
- output.write[2].r = volumes[4]; // rear-right
+ output.write[2].left = volumes[3]; // rear-left
+ output.write[2].right = volumes[4]; // rear-right
[[fallthrough]];
case AudioServer::SPEAKER_SURROUND_31:
- output.write[1].r = 1.0; // LFE - always full power
- output.write[1].l = volumes[2]; // center
+ output.write[1].right = 1.0; // LFE - always full power
+ output.write[1].left = volumes[2]; // center
[[fallthrough]];
case AudioServer::SPEAKER_MODE_STEREO:
- output.write[0].r = volumes[1]; // front-right
- output.write[0].l = volumes[0]; // front-left
+ output.write[0].right = volumes[1]; // front-right
+ output.write[0].left = volumes[0]; // front-left
break;
}
}
@@ -168,25 +168,25 @@ void AudioStreamPlayer3D::_calc_reverb_vol(Area3D *area, Vector3 listener_area_p
// Stereo pair.
float c = rev_pos.x * 0.5 + 0.5;
- reverb_vol.write[0].l = 1.0 - c;
- reverb_vol.write[0].r = c;
+ reverb_vol.write[0].left = 1.0 - c;
+ reverb_vol.write[0].right = c;
if (channel_count >= 3) {
// Center pair + Side pair
float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
- reverb_vol.write[1].l = xl;
- reverb_vol.write[1].r = xr;
- reverb_vol.write[2].l = 1.0 - xr;
- reverb_vol.write[2].r = 1.0 - xl;
+ reverb_vol.write[1].left = xl;
+ reverb_vol.write[1].right = xr;
+ reverb_vol.write[2].left = 1.0 - xr;
+ reverb_vol.write[2].right = 1.0 - xl;
}
if (channel_count >= 4) {
// Rear pair
// FIXME: Not sure what math should be done here
- reverb_vol.write[3].l = 1.0 - c;
- reverb_vol.write[3].r = c;
+ reverb_vol.write[3].left = 1.0 - c;
+ reverb_vol.write[3].right = c;
}
for (int i = 0; i < channel_count; i++) {
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 1bab02e9b4..76bd8c5413 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -165,13 +165,14 @@ void Path3D::_curve_changed() {
emit_signal(SNAME("curve_changed"));
}
- // update the configuration warnings of all children of type PathFollow
- // previously used for PathFollowOriented (now enforced orientation is done in PathFollow)
+ // Update the configuration warnings of all children of type PathFollow
+ // previously used for PathFollowOriented (now enforced orientation is done in PathFollow). Also trigger transform update on PathFollow3Ds in deferred mode.
if (is_inside_tree()) {
for (int i = 0; i < get_child_count(); i++) {
PathFollow3D *child = Object::cast_to<PathFollow3D>(get_child(i));
if (child) {
child->update_configuration_warnings();
+ child->update_transform();
}
}
}
@@ -207,9 +208,24 @@ void Path3D::_bind_methods() {
ADD_SIGNAL(MethodInfo("curve_changed"));
}
-//////////////
+// Update transform, in deferred mode by default to avoid superfluity.
+void PathFollow3D::update_transform(bool p_immediate) {
+ transform_dirty = true;
+
+ if (p_immediate) {
+ _update_transform();
+ } else {
+ callable_mp(this, &PathFollow3D::_update_transform).call_deferred();
+ }
+}
+
+// Update transform immediately .
+void PathFollow3D::_update_transform() {
+ if (!transform_dirty) {
+ return;
+ }
+ transform_dirty = false;
-void PathFollow3D::_update_transform(bool p_update_xyz_rot) {
if (!path) {
return;
}
@@ -231,23 +247,25 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) {
t.origin = pos;
} else {
t = c->sample_baked_with_rotation(progress, cubic, false);
+ Vector3 tangent = -t.basis.get_column(2); // Retain tangent for applying tilt.
+ t = PathFollow3D::correct_posture(t, rotation_mode);
+
+ // Switch Z+ and Z- if necessary.
if (use_model_front) {
t.basis *= Basis::from_scale(Vector3(-1.0, 1.0, -1.0));
}
- Vector3 forward = t.basis.get_column(2); // Retain tangent for applying tilt
- t = PathFollow3D::correct_posture(t, rotation_mode);
- // Apply tilt *after* correct_posture
+ // Apply tilt *after* correct_posture().
if (tilt_enabled) {
const real_t tilt = c->sample_baked_tilt(progress);
- const Basis twist(forward, tilt);
+ const Basis twist(tangent, tilt);
t.basis = twist * t.basis;
}
}
+ // Apply offset and scale.
Vector3 scale = get_transform().basis.get_scale();
-
t.translate_local(Vector3(h_offset, v_offset, 0));
t.basis.scale_local(scale);
@@ -261,7 +279,7 @@ void PathFollow3D::_notification(int p_what) {
if (parent) {
path = Object::cast_to<Path3D>(parent);
if (path) {
- _update_transform(false);
+ update_transform();
}
}
} break;
@@ -316,11 +334,10 @@ Transform3D PathFollow3D::correct_posture(Transform3D p_transform, PathFollow3D:
// Clear rotation.
t.basis = Basis();
} else if (p_rotation_mode == PathFollow3D::ROTATION_ORIENTED) {
- // Y-axis always straight up.
- Vector3 up(0.0, 1.0, 0.0);
- Vector3 forward = t.basis.get_column(2);
+ Vector3 tangent = -t.basis.get_column(2);
- t.basis = Basis::looking_at(-forward, up);
+ // Y-axis points up by default.
+ t.basis = Basis::looking_at(tangent);
} else {
// Lock some euler axes.
Vector3 euler = t.basis.get_euler_normalized(EulerOrder::YXZ);
@@ -405,14 +422,14 @@ void PathFollow3D::set_progress(real_t p_progress) {
}
}
- _update_transform();
+ update_transform();
}
}
void PathFollow3D::set_h_offset(real_t p_h_offset) {
h_offset = p_h_offset;
if (path) {
- _update_transform();
+ update_transform();
}
}
@@ -423,7 +440,7 @@ real_t PathFollow3D::get_h_offset() const {
void PathFollow3D::set_v_offset(real_t p_v_offset) {
v_offset = p_v_offset;
if (path) {
- _update_transform();
+ update_transform();
}
}
@@ -453,7 +470,7 @@ void PathFollow3D::set_rotation_mode(RotationMode p_rotation_mode) {
rotation_mode = p_rotation_mode;
update_configuration_warnings();
- _update_transform();
+ update_transform();
}
PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
@@ -462,6 +479,7 @@ PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
void PathFollow3D::set_use_model_front(bool p_use_model_front) {
use_model_front = p_use_model_front;
+ update_transform();
}
bool PathFollow3D::is_using_model_front() const {
@@ -470,6 +488,7 @@ bool PathFollow3D::is_using_model_front() const {
void PathFollow3D::set_loop(bool p_loop) {
loop = p_loop;
+ update_transform();
}
bool PathFollow3D::has_loop() const {
@@ -478,6 +497,7 @@ bool PathFollow3D::has_loop() const {
void PathFollow3D::set_tilt_enabled(bool p_enabled) {
tilt_enabled = p_enabled;
+ update_transform();
}
bool PathFollow3D::is_tilt_enabled() const {
diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h
index fe44b73e14..076af95ebc 100644
--- a/scene/3d/path_3d.h
+++ b/scene/3d/path_3d.h
@@ -72,10 +72,6 @@ public:
ROTATION_ORIENTED
};
- bool use_model_front = false;
-
- static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
-
private:
Path3D *path = nullptr;
real_t progress = 0.0;
@@ -84,14 +80,16 @@ private:
bool cubic = true;
bool loop = true;
bool tilt_enabled = true;
+ bool transform_dirty = true;
+ bool use_model_front = false;
RotationMode rotation_mode = ROTATION_XYZ;
- void _update_transform(bool p_update_xyz_rot = true);
-
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
+ void _update_transform();
+
static void _bind_methods();
public:
@@ -124,6 +122,10 @@ public:
Array get_configuration_warnings() const override;
+ void update_transform(bool p_immediate = false);
+
+ static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
+
PathFollow3D() {}
};
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index e63e1ee42a..f50ac4a0ee 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -253,8 +253,9 @@ void CodeEdit::_notification(int p_what) {
void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventMouseButton> mb = p_gui_input;
if (mb.is_valid()) {
- /* Ignore mouse clicks in IME input mode. */
+ // Ignore mouse clicks in IME input mode, let TextEdit handle it.
if (has_ime_text()) {
+ TextEdit::gui_input(p_gui_input);
return;
}
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 44f2ab5880..588bfb6b04 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -35,8 +35,6 @@
#include "core/string/translation.h"
#include "scene/theme/theme_db.h"
-PropertyListHelper ItemList::base_property_helper;
-
void ItemList::_shape_text(int p_idx) {
Item &item = items.write[p_idx];
@@ -1705,22 +1703,6 @@ bool ItemList::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
-bool ItemList::_get(const StringName &p_name, Variant &r_ret) const {
- return property_helper.property_get_value(p_name, r_ret);
-}
-
-void ItemList::_get_property_list(List<PropertyInfo> *p_list) const {
- property_helper.get_property_list(p_list, items.size());
-}
-
-bool ItemList::_property_can_revert(const StringName &p_name) const {
- return property_helper.property_can_revert(p_name);
-}
-
-bool ItemList::_property_get_revert(const StringName &p_name, Variant &r_property) const {
- return property_helper.property_get_revert(p_name, r_property);
-}
-
void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_item", "text", "icon", "selectable"), &ItemList::add_item, DEFVAL(Variant()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("add_icon_item", "icon", "selectable"), &ItemList::add_icon_item, DEFVAL(true));
@@ -1889,10 +1871,10 @@ void ItemList::_bind_methods() {
Item defaults(true);
base_property_helper.set_prefix("item_");
- base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, "set_item_text", "get_item_text");
- base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, "set_item_icon", "get_item_icon");
- base_property_helper.register_property(PropertyInfo(Variant::BOOL, "selectable"), defaults.selectable, "set_item_selectable", "is_item_selectable");
- base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, "set_item_disabled", "is_item_disabled");
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &ItemList::set_item_text, &ItemList::get_item_text);
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &ItemList::set_item_icon, &ItemList::get_item_icon);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "selectable"), defaults.selectable, &ItemList::set_item_selectable, &ItemList::is_item_selectable);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &ItemList::set_item_disabled, &ItemList::is_item_disabled);
}
ItemList::ItemList() {
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index 571f5b77e6..4c035ee4e6 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -87,7 +87,7 @@ private:
Item(bool p_dummy) {}
};
- static PropertyListHelper base_property_helper;
+ static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
int current = -1;
@@ -161,10 +161,10 @@ private:
protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
- bool _property_can_revert(const StringName &p_name) const;
- bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, items.size()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
public:
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index beafe7ec7a..72a84e4884 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -140,7 +140,9 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) {
if (p_all_to_left) {
deselect();
- text = text.substr(0, caret_column);
+ text = text.substr(caret_column);
+ _shape();
+ set_caret_column(0);
_text_changed();
return;
}
@@ -176,9 +178,8 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
if (p_all_to_right) {
deselect();
- text = text.substr(caret_column, text.length() - caret_column);
+ text = text.substr(0, caret_column);
_shape();
- set_caret_column(0);
_text_changed();
return;
}
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index a35c0ba2d7..301de291a4 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -234,10 +234,13 @@ String MenuBar::bind_global_menu() {
String submenu_name = popups[i]->bind_global_menu();
if (!popups[i]->is_system_menu()) {
int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+ menu_cache.write[i].global_index = index;
ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ } else {
+ menu_cache.write[i].global_index = -1;
}
}
@@ -250,12 +253,14 @@ void MenuBar::unbind_global_menu() {
}
DisplayServer *ds = DisplayServer::get_singleton();
- int global_start = _find_global_start_index();
Vector<PopupMenu *> popups = _get_popups();
for (int i = menu_cache.size() - 1; i >= 0; i--) {
if (!popups[i]->is_system_menu()) {
popups[i]->unbind_global_menu();
- ds->global_menu_remove_item("_main", global_start + i);
+ if (menu_cache[i].global_index >= 0) {
+ ds->global_menu_remove_item("_main", menu_cache[i].global_index);
+ }
+ menu_cache.write[i].global_index = -1;
}
}
@@ -283,11 +288,10 @@ void MenuBar::_notification(int p_what) {
case NOTIFICATION_TRANSLATION_CHANGED: {
DisplayServer *ds = DisplayServer::get_singleton();
bool is_global = !global_menu_name.is_empty();
- int global_start = _find_global_start_index();
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
- if (is_global) {
- ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ if (is_global && menu_cache[i].global_index >= 0) {
+ ds->global_menu_set_item_text("_main", menu_cache[i].global_index, atr(menu_cache[i].name));
}
}
} break;
@@ -487,15 +491,14 @@ void MenuBar::shape(Menu &p_menu) {
void MenuBar::_refresh_menu_names() {
DisplayServer *ds = DisplayServer::get_singleton();
bool is_global = !global_menu_name.is_empty();
- int global_start = _find_global_start_index();
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
- if (is_global) {
- ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ if (is_global && menu_cache[i].global_index >= 0) {
+ ds->global_menu_set_item_text("_main", menu_cache[i].global_index, atr(menu_cache[i].name));
}
}
}
@@ -546,6 +549,7 @@ void MenuBar::add_child_notify(Node *p_child) {
String submenu_name = pm->bind_global_menu();
if (!pm->is_system_menu()) {
int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+ menu_cache.write[menu_cache.size() - 1].global_index = index;
DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
}
}
@@ -577,12 +581,13 @@ void MenuBar::move_child_notify(Node *p_child) {
if (!global_menu_name.is_empty()) {
if (!pm->is_system_menu()) {
int global_start = _find_global_start_index();
- if (old_idx != -1) {
- DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+ if (menu.global_index >= 0) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", menu.global_index);
}
if (new_idx != -1) {
String submenu_name = pm->bind_global_menu();
int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+ menu_cache.write[new_idx].global_index = index;
DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
}
}
@@ -604,7 +609,10 @@ void MenuBar::remove_child_notify(Node *p_child) {
if (!global_menu_name.is_empty()) {
if (!pm->is_system_menu()) {
pm->unbind_global_menu();
- DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ if (menu_cache[idx].global_index >= 0) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", menu_cache[idx].global_index);
+ menu_cache.write[idx].global_index = -1;
+ }
}
}
@@ -800,8 +808,8 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) {
}
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
- if (!global_menu_name.is_empty()) {
- DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name));
+ if (!global_menu_name.is_empty() && menu_cache[p_menu].global_index >= 0) {
+ DisplayServer::get_singleton()->global_menu_set_item_text("_main", menu_cache[p_menu].global_index, atr(menu_cache[p_menu].name));
}
update_minimum_size();
}
@@ -816,8 +824,8 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) {
PopupMenu *pm = get_menu_popup(p_menu);
pm->set_meta("_menu_tooltip", p_tooltip);
menu_cache.write[p_menu].tooltip = p_tooltip;
- if (!global_menu_name.is_empty()) {
- DisplayServer::get_singleton()->global_menu_set_item_tooltip("_main", _find_global_start_index() + p_menu, p_tooltip);
+ if (!global_menu_name.is_empty() && menu_cache[p_menu].global_index >= 0) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip("_main", menu_cache[p_menu].global_index, p_tooltip);
}
}
@@ -829,8 +837,8 @@ String MenuBar::get_menu_tooltip(int p_menu) const {
void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].disabled = p_disabled;
- if (!global_menu_name.is_empty()) {
- DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", _find_global_start_index() + p_menu, p_disabled);
+ if (!global_menu_name.is_empty() && menu_cache[p_menu].global_index >= 0) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", menu_cache[p_menu].global_index, p_disabled);
}
}
@@ -842,8 +850,8 @@ bool MenuBar::is_menu_disabled(int p_menu) const {
void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].hidden = p_hidden;
- if (!global_menu_name.is_empty()) {
- DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden);
+ if (!global_menu_name.is_empty() && menu_cache[p_menu].global_index >= 0) {
+ DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", menu_cache[p_menu].global_index, p_hidden);
}
update_minimum_size();
}
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
index 8431ac958b..c7d44aa927 100644
--- a/scene/gui/menu_bar.h
+++ b/scene/gui/menu_bar.h
@@ -55,6 +55,7 @@ class MenuBar : public Control {
Ref<TextLine> text_buf;
bool hidden = false;
bool disabled = false;
+ int global_index = -1;
Menu(const String &p_name) {
name = p_name;
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 38c326773d..0da5093ab8 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -57,6 +57,25 @@ bool PopupMenu::_set_item_accelerator(int p_index, const Ref<InputEventKey> &p_i
return false;
}
+void PopupMenu::_set_item_checkable_type(int p_index, int p_checkable_type) {
+ switch (p_checkable_type) {
+ case Item::CHECKABLE_TYPE_NONE: {
+ set_item_as_checkable(p_index, false);
+ } break;
+ case Item::CHECKABLE_TYPE_CHECK_BOX: {
+ set_item_as_checkable(p_index, true);
+ } break;
+ case Item::CHECKABLE_TYPE_RADIO_BUTTON: {
+ set_item_as_radio_checkable(p_index, true);
+ } break;
+ }
+}
+
+int PopupMenu::_get_item_checkable_type(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, items.size(), Item::CHECKABLE_TYPE_NONE);
+ return items[p_index].checkable_type;
+}
+
String PopupMenu::bind_global_menu() {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
@@ -2561,39 +2580,10 @@ void PopupMenu::take_mouse_focus() {
}
bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
- int item_index = components[0].trim_prefix("item_").to_int();
- const String &property = components[1];
- if (property == "text") {
- set_item_text(item_index, p_value);
- return true;
- } else if (property == "icon") {
- set_item_icon(item_index, p_value);
- return true;
- } else if (property == "checkable") {
- bool radio_checkable = (int)p_value == Item::CHECKABLE_TYPE_RADIO_BUTTON;
- if (radio_checkable) {
- set_item_as_radio_checkable(item_index, true);
- } else {
- bool checkable = p_value;
- set_item_as_checkable(item_index, checkable);
- }
- return true;
- } else if (property == "checked") {
- set_item_checked(item_index, p_value);
- return true;
- } else if (property == "id") {
- set_item_id(item_index, p_value);
- return true;
- } else if (property == "disabled") {
- set_item_disabled(item_index, p_value);
- return true;
- } else if (property == "separator") {
- set_item_as_separator(item_index, p_value);
- return true;
- }
+ if (property_helper.property_set_value(p_name, p_value)) {
+ return true;
}
+
#ifndef DISABLE_DEPRECATED
// Compatibility.
if (p_name == "items") {
@@ -2639,71 +2629,6 @@ bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
-bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
- int item_index = components[0].trim_prefix("item_").to_int();
- const String &property = components[1];
- if (property == "text") {
- r_ret = get_item_text(item_index);
- return true;
- } else if (property == "icon") {
- r_ret = get_item_icon(item_index);
- return true;
- } else if (property == "checkable") {
- if (item_index >= 0 && item_index < items.size()) {
- r_ret = items[item_index].checkable_type;
- return true;
- } else {
- r_ret = Item::CHECKABLE_TYPE_NONE;
- ERR_FAIL_V(true);
- }
- } else if (property == "checked") {
- r_ret = is_item_checked(item_index);
- return true;
- } else if (property == "id") {
- r_ret = get_item_id(item_index);
- return true;
- } else if (property == "disabled") {
- r_ret = is_item_disabled(item_index);
- return true;
- } else if (property == "separator") {
- r_ret = is_item_separator(item_index);
- return true;
- }
- }
- return false;
-}
-
-void PopupMenu::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < items.size(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("item_%d/text", i)));
-
- PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
- pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button");
- pi.usage &= ~(!is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("item_%d/checked", i));
- pi.usage &= ~(!is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i));
- pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("item_%d/separator", i));
- pi.usage &= ~(!is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
- }
-}
-
void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("activate_item_by_event", "event", "for_global_only"), &PopupMenu::activate_item_by_event, DEFVAL(false));
@@ -2860,6 +2785,17 @@ void PopupMenu::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_separator_color);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, PopupMenu, font_separator_outline_size, "separator_outline_size");
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_separator_outline_color);
+
+ Item defaults(true);
+
+ base_property_helper.set_prefix("item_");
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &PopupMenu::set_item_text, &PopupMenu::get_item_text);
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &PopupMenu::set_item_icon, &PopupMenu::get_item_icon);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"), defaults.checkable_type, &PopupMenu::_set_item_checkable_type, &PopupMenu::_get_item_checkable_type);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked, &PopupMenu::set_item_checked, &PopupMenu::is_item_checked);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &PopupMenu::set_item_id, &PopupMenu::get_item_id);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &PopupMenu::set_item_disabled, &PopupMenu::is_item_disabled);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &PopupMenu::set_item_as_separator, &PopupMenu::is_item_separator);
}
void PopupMenu::popup(const Rect2i &p_bounds) {
@@ -2900,6 +2836,8 @@ PopupMenu::PopupMenu() {
minimum_lifetime_timer->set_one_shot(true);
minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT);
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
PopupMenu::~PopupMenu() {
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 35ababd913..d068418059 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -35,6 +35,7 @@
#include "scene/gui/margin_container.h"
#include "scene/gui/popup.h"
#include "scene/gui/scroll_container.h"
+#include "scene/property_list_helper.h"
#include "scene/resources/text_line.h"
class PopupMenu : public Popup {
@@ -59,7 +60,7 @@ class PopupMenu : public Popup {
CHECKABLE_TYPE_NONE,
CHECKABLE_TYPE_CHECK_BOX,
CHECKABLE_TYPE_RADIO_BUTTON,
- } checkable_type;
+ } checkable_type = CHECKABLE_TYPE_NONE;
int max_states = 0;
int state = 0;
bool separator = false;
@@ -89,8 +90,13 @@ class PopupMenu : public Popup {
accel_text_buf.instantiate();
checkable_type = CHECKABLE_TYPE_NONE;
}
+
+ Item(bool p_dummy) {}
};
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
String global_menu_name;
String system_menu_name;
@@ -195,6 +201,8 @@ class PopupMenu : public Popup {
void _menu_changed();
void _input_from_window_internal(const Ref<InputEvent> &p_event);
bool _set_item_accelerator(int p_index, const Ref<InputEventKey> &p_ie);
+ void _set_item_checkable_type(int p_index, int p_checkable_type);
+ int _get_item_checkable_type(int p_index) const;
protected:
virtual void add_child_notify(Node *p_child) override;
@@ -203,8 +211,10 @@ protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, items.size()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 95dc50f203..f8bd65ae06 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1489,14 +1489,7 @@ void TextEdit::_notification(int p_what) {
}
if (has_focus()) {
- if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- Point2 pos = get_global_position() + get_caret_draw_pos();
- if (get_window()->get_embedder()) {
- pos += get_viewport()->get_popup_base_transform().get_origin();
- }
- DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
- }
+ _update_ime_window_position();
}
} break;
@@ -1507,14 +1500,7 @@ void TextEdit::_notification(int p_what) {
draw_caret = true;
}
- if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- Point2 pos = get_global_position() + get_caret_draw_pos();
- if (get_window()->get_embedder()) {
- pos += get_viewport()->get_popup_base_transform().get_origin();
- }
- DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
- }
+ _update_ime_window_position();
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
int caret_start = -1;
@@ -1541,17 +1527,7 @@ void TextEdit::_notification(int p_what) {
caret_blink_timer->stop();
}
- if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
- DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
- }
- if (!ime_text.is_empty()) {
- ime_text = "";
- ime_selection = Point2();
- for (int i = 0; i < carets.size(); i++) {
- text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, ime_text);
- }
- }
+ apply_ime();
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
@@ -1571,15 +1547,8 @@ void TextEdit::_notification(int p_what) {
delete_selection();
}
- for (int i = 0; i < carets.size(); i++) {
- String t;
- if (get_caret_column(i) >= 0) {
- t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
- } else {
- t = ime_text;
- }
- text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t));
- }
+ _update_ime_text();
+ adjust_viewport_to_caret(0);
queue_redraw();
}
} break;
@@ -1681,10 +1650,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (is_layout_rtl()) {
mpos.x = get_size().x - mpos.x;
}
- if (ime_text.length() != 0) {
- // Ignore mouse clicks in IME input mode.
- return;
- }
if (mb->is_pressed()) {
if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) {
@@ -1718,6 +1683,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MouseButton::LEFT) {
_reset_caret_blink_timer();
+ apply_ime();
+
Point2i pos = get_line_column_at_pos(mpos);
int row = pos.y;
int col = pos.x;
@@ -1865,11 +1832,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ apply_ime();
paste_primary_clipboard();
}
if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) {
_reset_caret_blink_timer();
+ apply_ime();
Point2i pos = get_line_column_at_pos(mpos);
int row = pos.y;
@@ -1909,6 +1878,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
} else {
+ if (has_ime_text()) {
+ // Ignore mouse up in IME input mode.
+ return;
+ }
+
if (mb->get_button_index() == MouseButton::LEFT) {
if (selection_drag_attempt && is_mouse_over_selection()) {
remove_secondary_carets();
@@ -1967,7 +1941,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
_update_minimap_drag();
}
- if (!dragging_minimap) {
+ if (!dragging_minimap && !has_ime_text()) {
switch (selecting_mode) {
case SelectionMode::SELECTION_MODE_POINTER: {
_update_selection_mode_pointer();
@@ -2012,6 +1986,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) {
+ apply_ime();
drag_caret_force_displayed = true;
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
set_caret_line(pos.y, false, true, 0, 0);
@@ -3030,6 +3005,43 @@ void TextEdit::_update_caches() {
}
}
+void TextEdit::_close_ime_window() {
+ if (get_viewport()->get_window_id() == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ return;
+ }
+ DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
+ DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
+}
+
+void TextEdit::_update_ime_window_position() {
+ if (get_viewport()->get_window_id() == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ return;
+ }
+ DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
+ Point2 pos = get_global_position() + get_caret_draw_pos();
+ if (get_window()->get_embedder()) {
+ pos += get_viewport()->get_popup_base_transform().get_origin();
+ }
+ // The window will move to the updated position the next time the IME is updated, not immediately.
+ DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
+}
+
+void TextEdit::_update_ime_text() {
+ if (has_ime_text()) {
+ // Update text to visually include IME text.
+ for (int i = 0; i < get_caret_count(); i++) {
+ String text_with_ime = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
+ text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, text_with_ime, structured_text_parser(st_parser, st_args, text_with_ime));
+ }
+ } else {
+ // Reset text.
+ for (int i = 0; i < get_caret_count(); i++) {
+ text.invalidate_cache(get_caret_line(i), get_caret_column(i), true);
+ }
+ }
+ queue_redraw();
+}
+
/* General overrides. */
Size2 TextEdit::get_minimum_size() const {
Size2 size = theme_cache.style_normal->get_minimum_size();
@@ -3189,6 +3201,26 @@ bool TextEdit::has_ime_text() const {
return !ime_text.is_empty();
}
+void TextEdit::cancel_ime() {
+ if (!has_ime_text()) {
+ return;
+ }
+ ime_text = String();
+ ime_selection = Point2();
+ _close_ime_window();
+ _update_ime_text();
+}
+
+void TextEdit::apply_ime() {
+ if (!has_ime_text()) {
+ return;
+ }
+ // Force apply the current IME text.
+ String insert_ime_text = ime_text;
+ cancel_ime();
+ insert_text_at_caret(insert_ime_text);
+}
+
void TextEdit::set_editable(const bool p_editable) {
if (editable == p_editable) {
return;
@@ -3568,16 +3600,8 @@ void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
adjust_carets_after_edit(i, new_line, new_column, from_line, from_col);
}
- if (!ime_text.is_empty()) {
- for (int i = 0; i < carets.size(); i++) {
- String t;
- if (get_caret_column(i) >= 0) {
- t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
- } else {
- t = ime_text;
- }
- text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t));
- }
+ if (has_ime_text()) {
+ _update_ime_text();
}
end_complex_operation();
@@ -5560,14 +5584,14 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) {
Vector2i caret_pos;
// Get position of the start of caret.
- if (ime_text.length() != 0 && ime_selection.x != 0) {
+ if (has_ime_text() && ime_selection.x != 0) {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
}
// Get position of the end of caret.
- if (ime_text.length() != 0) {
+ if (has_ime_text()) {
if (ime_selection.y != 0) {
caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
@@ -5612,14 +5636,14 @@ void TextEdit::center_viewport_to_caret(int p_caret) {
Vector2i caret_pos;
// Get position of the start of caret.
- if (ime_text.length() != 0 && ime_selection.x != 0) {
+ if (has_ime_text() && ime_selection.x != 0) {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
}
// Get position of the end of caret.
- if (ime_text.length() != 0) {
+ if (has_ime_text()) {
if (ime_selection.y != 0) {
caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
} else {
@@ -6029,6 +6053,8 @@ void TextEdit::_bind_methods() {
/* Text */
// Text properties
ClassDB::bind_method(D_METHOD("has_ime_text"), &TextEdit::has_ime_text);
+ ClassDB::bind_method(D_METHOD("cancel_ime"), &TextEdit::cancel_ime);
+ ClassDB::bind_method(D_METHOD("apply_ime"), &TextEdit::apply_ime);
ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &TextEdit::set_editable);
ClassDB::bind_method(D_METHOD("is_editable"), &TextEdit::is_editable);
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 8a541b623b..d49be860a9 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -293,6 +293,10 @@ private:
void _clear();
void _update_caches();
+ void _close_ime_window();
+ void _update_ime_window_position();
+ void _update_ime_text();
+
// User control.
bool overtype_mode = false;
bool context_menu_enabled = true;
@@ -696,6 +700,8 @@ public:
/* Text */
// Text properties.
bool has_ime_text() const;
+ void cancel_ime();
+ void apply_ime();
void set_editable(const bool p_editable);
bool is_editable() const;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 73a49fd427..bf456cd048 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3394,7 +3394,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
bool is_command = k.is_valid() && k->is_command_or_control_pressed();
- if (p_event->is_action("ui_right", true) && p_event->is_pressed()) {
+ if (p_event->is_action("ui_right") && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3402,17 +3402,12 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) {
return;
}
- if (k.is_valid() && k->is_alt_pressed()) {
- selected_item->set_collapsed(false);
- TreeItem *next = selected_item->get_first_child();
- while (next && next != selected_item->next) {
- next->set_collapsed(false);
- next = next->get_next_visible();
- }
+ if (k.is_valid() && k->is_shift_pressed()) {
+ selected_item->set_collapsed_recursive(false);
} else {
_go_right();
}
- } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3421,32 +3416,27 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (k.is_valid() && k->is_alt_pressed()) {
- selected_item->set_collapsed(true);
- TreeItem *next = selected_item->get_first_child();
- while (next && next != selected_item->next) {
- next->set_collapsed(true);
- next = next->get_next_visible();
- }
+ if (k.is_valid() && k->is_shift_pressed()) {
+ selected_item->set_collapsed_recursive(true);
} else {
_go_left();
}
- } else if (p_event->is_action("ui_up", true) && p_event->is_pressed() && !is_command) {
+ } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) {
if (!cursor_can_exit_tree) {
accept_event();
}
_go_up();
- } else if (p_event->is_action("ui_down", true) && p_event->is_pressed() && !is_command) {
+ } else if (p_event->is_action("ui_down") && p_event->is_pressed() && !is_command) {
if (!cursor_can_exit_tree) {
accept_event();
}
_go_down();
- } else if (p_event->is_action("ui_page_down", true) && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_page_down") && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3484,7 +3474,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
ensure_cursor_is_visible();
- } else if (p_event->is_action("ui_page_up", true) && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_page_up") && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3521,7 +3511,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
prev->select(selected_col);
}
ensure_cursor_is_visible();
- } else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
if (selected_item) {
//bring up editor if possible
if (!edit_selected()) {
@@ -3530,7 +3520,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
accept_event();
- } else if (p_event->is_action("ui_select", true) && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_select") && p_event->is_pressed()) {
if (select_mode == SELECT_MULTI) {
if (!selected_item) {
return;
diff --git a/scene/main/status_indicator.cpp b/scene/main/status_indicator.cpp
new file mode 100644
index 0000000000..ae58bc0b18
--- /dev/null
+++ b/scene/main/status_indicator.cpp
@@ -0,0 +1,135 @@
+/**************************************************************************/
+/* status_indicator.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "status_indicator.h"
+
+void StatusIndicator::_notification(int p_what) {
+ ERR_MAIN_THREAD_GUARD;
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return;
+ }
+#endif
+
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
+ if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
+ iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
+ }
+ }
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
+ if (iid != DisplayServer::INVALID_INDICATOR_ID) {
+ DisplayServer::get_singleton()->delete_status_indicator(iid);
+ iid = DisplayServer::INVALID_INDICATOR_ID;
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+}
+
+void StatusIndicator::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_tooltip", "tooltip"), &StatusIndicator::set_tooltip);
+ ClassDB::bind_method(D_METHOD("get_tooltip"), &StatusIndicator::get_tooltip);
+ ClassDB::bind_method(D_METHOD("set_icon", "texture"), &StatusIndicator::set_icon);
+ ClassDB::bind_method(D_METHOD("get_icon"), &StatusIndicator::get_icon);
+ ClassDB::bind_method(D_METHOD("set_visible", "visible"), &StatusIndicator::set_visible);
+ ClassDB::bind_method(D_METHOD("is_visible"), &StatusIndicator::is_visible);
+
+ ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "position")));
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "get_tooltip");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_icon", "get_icon");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
+}
+
+void StatusIndicator::_callback(MouseButton p_index, const Point2i &p_pos) {
+ emit_signal(SNAME("pressed"), p_index, p_pos);
+}
+
+void StatusIndicator::set_icon(const Ref<Image> &p_icon) {
+ ERR_MAIN_THREAD_GUARD;
+ icon = p_icon;
+ if (iid != DisplayServer::INVALID_INDICATOR_ID) {
+ DisplayServer::get_singleton()->status_indicator_set_icon(iid, icon);
+ }
+}
+
+Ref<Image> StatusIndicator::get_icon() const {
+ return icon;
+}
+
+void StatusIndicator::set_tooltip(const String &p_tooltip) {
+ ERR_MAIN_THREAD_GUARD;
+ tooltip = p_tooltip;
+ if (iid != DisplayServer::INVALID_INDICATOR_ID) {
+ DisplayServer::get_singleton()->status_indicator_set_tooltip(iid, tooltip);
+ }
+}
+
+String StatusIndicator::get_tooltip() const {
+ return tooltip;
+}
+
+void StatusIndicator::set_visible(bool p_visible) {
+ ERR_MAIN_THREAD_GUARD;
+ if (visible == p_visible) {
+ return;
+ }
+ visible = p_visible;
+
+ if (!is_inside_tree()) {
+ return;
+ }
+
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return;
+ }
+#endif
+
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
+ if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
+ iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
+ }
+ if (!visible && iid != DisplayServer::INVALID_INDICATOR_ID) {
+ DisplayServer::get_singleton()->delete_status_indicator(iid);
+ iid = DisplayServer::INVALID_INDICATOR_ID;
+ }
+ }
+}
+
+bool StatusIndicator::is_visible() const {
+ return visible;
+}
diff --git a/scene/main/status_indicator.h b/scene/main/status_indicator.h
new file mode 100644
index 0000000000..aa3aa68d78
--- /dev/null
+++ b/scene/main/status_indicator.h
@@ -0,0 +1,62 @@
+/**************************************************************************/
+/* status_indicator.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef STATUS_INDICATOR_H
+#define STATUS_INDICATOR_H
+
+#include "scene/main/node.h"
+#include "servers/display_server.h"
+
+class StatusIndicator : public Node {
+ GDCLASS(StatusIndicator, Node);
+
+ Ref<Image> icon;
+ String tooltip;
+ bool visible = true;
+ DisplayServer::IndicatorID iid = DisplayServer::INVALID_INDICATOR_ID;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+ void _callback(MouseButton p_index, const Point2i &p_pos);
+
+public:
+ void set_icon(const Ref<Image> &p_icon);
+ Ref<Image> get_icon() const;
+
+ void set_tooltip(const String &p_tooltip);
+ String get_tooltip() const;
+
+ void set_visible(bool p_visible);
+ bool is_visible() const;
+};
+
+#endif // STATUS_INDICATOR_H
diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp
index 596a6a050a..2d3179d9fd 100644
--- a/scene/property_list_helper.cpp
+++ b/scene/property_list_helper.cpp
@@ -47,35 +47,28 @@ const PropertyListHelper::Property *PropertyListHelper::_get_property(const Stri
return property_list.getptr(components[1]);
}
-void PropertyListHelper::_bind_property(const Property &p_property, const Object *p_object) {
- Property property = p_property;
- property.info = p_property.info;
- property.default_value = p_property.default_value;
- property.setter = Callable(p_object, p_property.setter_name);
- property.getter = Callable(p_object, p_property.getter_name);
-
- property_list[property.info.name] = property;
+void PropertyListHelper::_call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const {
+ Variant args[] = { p_index, p_value };
+ const Variant *argptrs[] = { &args[0], &args[1] };
+ Callable::CallError ce;
+ p_setter->call(object, argptrs, 2, ce);
}
-void PropertyListHelper::set_prefix(const String &p_prefix) {
- prefix = p_prefix;
+Variant PropertyListHelper::_call_getter(const MethodBind *p_getter, int p_index) const {
+ Callable::CallError ce;
+ Variant args[] = { p_index };
+ const Variant *argptrs[] = { &args[0] };
+ return p_getter->call(object, argptrs, 1, ce);
}
-void PropertyListHelper::register_property(const PropertyInfo &p_info, const Variant &p_default, const StringName &p_setter, const StringName &p_getter) {
- Property property;
- property.info = p_info;
- property.default_value = p_default;
- property.setter_name = p_setter;
- property.getter_name = p_getter;
-
- property_list[p_info.name] = property;
+void PropertyListHelper::set_prefix(const String &p_prefix) {
+ prefix = p_prefix;
}
-void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, const Object *p_object) {
+void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) {
prefix = p_base.prefix;
- for (const KeyValue<String, Property> &E : p_base.property_list) {
- _bind_property(E.value, p_object);
- }
+ property_list = p_base.property_list;
+ object = p_object;
}
void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_count) const {
@@ -84,7 +77,7 @@ void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_cou
const Property &property = E.value;
PropertyInfo info = property.info;
- if (property.getter.call(i) == property.default_value) {
+ if (_call_getter(property.getter, i) == property.default_value) {
info.usage &= (~PROPERTY_USAGE_STORAGE);
}
@@ -99,7 +92,7 @@ bool PropertyListHelper::property_get_value(const String &p_property, Variant &r
const Property *property = _get_property(p_property, &index);
if (property) {
- r_ret = property->getter.call(index);
+ r_ret = _call_getter(property->getter, index);
return true;
}
return false;
@@ -110,7 +103,7 @@ bool PropertyListHelper::property_set_value(const String &p_property, const Vari
const Property *property = _get_property(p_property, &index);
if (property) {
- property->setter.call(index, p_value);
+ _call_setter(property->setter, index, p_value);
return true;
}
return false;
@@ -121,7 +114,7 @@ bool PropertyListHelper::property_can_revert(const String &p_property) const {
const Property *property = _get_property(p_property, &index);
if (property) {
- return property->getter.call(index) != property->default_value;
+ return _call_getter(property->getter, index) != property->default_value;
}
return false;
}
@@ -136,3 +129,13 @@ bool PropertyListHelper::property_get_revert(const String &p_property, Variant &
}
return false;
}
+
+PropertyListHelper::~PropertyListHelper() {
+ // No object = it's the main helper. Do a cleanup.
+ if (!object) {
+ for (const KeyValue<String, Property> &E : property_list) {
+ memdelete(E.value.setter);
+ memdelete(E.value.getter);
+ }
+ }
+}
diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h
index f3cf0239a1..6c1ad21a05 100644
--- a/scene/property_list_helper.h
+++ b/scene/property_list_helper.h
@@ -31,34 +31,48 @@
#ifndef PROPERTY_LIST_HELPER_H
#define PROPERTY_LIST_HELPER_H
+#include "core/object/method_bind.h"
#include "core/object/object.h"
class PropertyListHelper {
struct Property {
PropertyInfo info;
Variant default_value;
- StringName setter_name;
- StringName getter_name;
- Callable setter;
- Callable getter;
+ MethodBind *setter = nullptr;
+ MethodBind *getter = nullptr;
};
String prefix;
HashMap<String, Property> property_list;
+ Object *object = nullptr;
const Property *_get_property(const String &p_property, int *r_index) const;
- void _bind_property(const Property &p_property, const Object *p_object);
+ void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const;
+ Variant _call_getter(const MethodBind *p_getter, int p_index) const;
public:
void set_prefix(const String &p_prefix);
- void register_property(const PropertyInfo &p_info, const Variant &p_default, const StringName &p_setter, const StringName &p_getter);
- void setup_for_instance(const PropertyListHelper &p_base, const Object *p_object);
+
+ template <typename S, typename G>
+ void register_property(const PropertyInfo &p_info, const Variant &p_default, S p_setter, G p_getter) {
+ Property property;
+ property.info = p_info;
+ property.default_value = p_default;
+ property.setter = create_method_bind(p_setter);
+ property.getter = create_method_bind(p_getter);
+
+ property_list[p_info.name] = property;
+ }
+
+ void setup_for_instance(const PropertyListHelper &p_base, Object *p_object);
void get_property_list(List<PropertyInfo> *p_list, int p_count) const;
bool property_get_value(const String &p_property, Variant &r_ret) const;
bool property_set_value(const String &p_property, const Variant &p_value) const;
bool property_can_revert(const String &p_property) const;
bool property_get_revert(const String &p_property, Variant &r_value) const;
+
+ ~PropertyListHelper();
};
#endif // PROPERTY_LIST_HELPER_H
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index c7d044caf2..d7e5eb4698 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -136,6 +136,7 @@
#include "scene/main/multiplayer_api.h"
#include "scene/main/resource_preloader.h"
#include "scene/main/scene_tree.h"
+#include "scene/main/status_indicator.h"
#include "scene/main/timer.h"
#include "scene/main/viewport.h"
#include "scene/main/window.h"
@@ -352,6 +353,8 @@ void register_scene_types() {
GDREGISTER_CLASS(ResourcePreloader);
GDREGISTER_CLASS(Window);
+ GDREGISTER_CLASS(StatusIndicator);
+
/* REGISTER GUI */
GDREGISTER_CLASS(ButtonGroup);
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index 669b455f89..b6e6493b68 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -213,8 +213,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
final_r = final; //copy to right channel if stereo
}
- p_dst->l = final / 32767.0;
- p_dst->r = final_r / 32767.0;
+ p_dst->left = final / 32767.0;
+ p_dst->right = final_r / 32767.0;
p_dst++;
p_offset += p_increment;
diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp
index 711630d09e..4c82e5ba5f 100644
--- a/scene/theme/theme_db.cpp
+++ b/scene/theme/theme_db.cpp
@@ -373,7 +373,7 @@ void ThemeDB::update_class_instance_items(Node *p_instance) {
}
}
-void ThemeDB::get_class_items(const StringName &p_class_name, List<ThemeItemBind> *r_list, bool p_include_inherited) {
+void ThemeDB::get_class_items(const StringName &p_class_name, List<ThemeItemBind> *r_list, bool p_include_inherited, Theme::DataType p_filter_type) {
List<StringName> class_hierarchy;
StringName class_name = p_class_name;
while (class_name != StringName()) {
@@ -386,6 +386,9 @@ void ThemeDB::get_class_items(const StringName &p_class_name, List<ThemeItemBind
HashMap<StringName, List<ThemeItemBind>>::Iterator E = theme_item_binds_list.find(theme_type);
if (E) {
for (const ThemeItemBind &F : E->value) {
+ if (p_filter_type != Theme::DATA_TYPE_MAX && F.data_type != p_filter_type) {
+ continue;
+ }
if (inherited_props.has(F.item_name)) {
continue; // Skip inherited properties.
}
diff --git a/scene/theme/theme_db.h b/scene/theme/theme_db.h
index d9428ad213..f403c1ef20 100644
--- a/scene/theme/theme_db.h
+++ b/scene/theme/theme_db.h
@@ -171,7 +171,7 @@ public:
void bind_class_external_item(Theme::DataType p_data_type, const StringName &p_class_name, const StringName &p_prop_name, const StringName &p_item_name, const StringName &p_type_name, ThemeItemSetter p_setter);
void update_class_instance_items(Node *p_instance);
- void get_class_items(const StringName &p_class_name, List<ThemeItemBind> *r_list, bool p_include_inherited = false);
+ void get_class_items(const StringName &p_class_name, List<ThemeItemBind> *r_list, bool p_include_inherited = false, Theme::DataType p_filter_type = Theme::DATA_TYPE_MAX);
// Memory management, reference, and initialization.
diff --git a/servers/audio/effects/audio_effect_capture.cpp b/servers/audio/effects/audio_effect_capture.cpp
index 7e7aa73f9c..a5740c7e27 100644
--- a/servers/audio/effects/audio_effect_capture.cpp
+++ b/servers/audio/effects/audio_effect_capture.cpp
@@ -49,7 +49,7 @@ PackedVector2Array AudioEffectCapture::get_buffer(int p_frames) {
streaming_data.resize(p_frames);
buffer.read(streaming_data.ptrw(), p_frames);
for (int32_t i = 0; i < p_frames; i++) {
- ret.write[i] = Vector2(streaming_data[i].l, streaming_data[i].r);
+ ret.write[i] = Vector2(streaming_data[i].left, streaming_data[i].right);
}
return ret;
}
diff --git a/servers/audio/effects/audio_effect_chorus.cpp b/servers/audio/effects/audio_effect_chorus.cpp
index 796abfee02..16f42aea9e 100644
--- a/servers/audio/effects/audio_effect_chorus.cpp
+++ b/servers/audio/effects/audio_effect_chorus.cpp
@@ -96,8 +96,8 @@ void AudioEffectChorusInstance::_process_chunk(const AudioFrame *p_src_frames, A
//vol modifier
AudioFrame vol_modifier = AudioFrame(base->wet, base->wet) * Math::db_to_linear(v.level);
- vol_modifier.l *= CLAMP(1.0 - v.pan, 0, 1);
- vol_modifier.r *= CLAMP(1.0 + v.pan, 0, 1);
+ vol_modifier.left *= CLAMP(1.0 - v.pan, 0, 1);
+ vol_modifier.right *= CLAMP(1.0 + v.pan, 0, 1);
for (int i = 0; i < p_frame_count; i++) {
/** COMPUTE WAVEFORM **/
diff --git a/servers/audio/effects/audio_effect_compressor.cpp b/servers/audio/effects/audio_effect_compressor.cpp
index ed6690c120..1ab1b6eaf3 100644
--- a/servers/audio/effects/audio_effect_compressor.cpp
+++ b/servers/audio/effects/audio_effect_compressor.cpp
@@ -59,10 +59,10 @@ void AudioEffectCompressorInstance::process(const AudioFrame *p_src_frames, Audi
for (int i = 0; i < p_frame_count; i++) {
AudioFrame s = src[i];
//convert to positive
- s.l = Math::abs(s.l);
- s.r = Math::abs(s.r);
+ s.left = Math::abs(s.left);
+ s.right = Math::abs(s.right);
- float peak = MAX(s.l, s.r);
+ float peak = MAX(s.left, s.right);
float overdb = 2.08136898f * Math::linear_to_db(peak / threshold);
diff --git a/servers/audio/effects/audio_effect_delay.cpp b/servers/audio/effects/audio_effect_delay.cpp
index 40a7a28fc4..3014dc703f 100644
--- a/servers/audio/effects/audio_effect_delay.cpp
+++ b/servers/audio/effects/audio_effect_delay.cpp
@@ -64,13 +64,13 @@ void AudioEffectDelayInstance::_process_chunk(const AudioFrame *p_src_frames, Au
AudioFrame tap1_vol = AudioFrame(tap_1_level_f, tap_1_level_f);
- tap1_vol.l *= CLAMP(1.0 - base->tap_1_pan, 0, 1);
- tap1_vol.r *= CLAMP(1.0 + base->tap_1_pan, 0, 1);
+ tap1_vol.left *= CLAMP(1.0 - base->tap_1_pan, 0, 1);
+ tap1_vol.right *= CLAMP(1.0 + base->tap_1_pan, 0, 1);
AudioFrame tap2_vol = AudioFrame(tap_2_level_f, tap_2_level_f);
- tap2_vol.l *= CLAMP(1.0 - base->tap_2_pan, 0, 1);
- tap2_vol.r *= CLAMP(1.0 + base->tap_2_pan, 0, 1);
+ tap2_vol.left *= CLAMP(1.0 - base->tap_2_pan, 0, 1);
+ tap2_vol.right *= CLAMP(1.0 + base->tap_2_pan, 0, 1);
// feedback lowpass here
float lpf_c = expf(-Math_TAU * base->feedback_lowpass / mix_rate); // 0 .. 10khz
diff --git a/servers/audio/effects/audio_effect_eq.cpp b/servers/audio/effects/audio_effect_eq.cpp
index 8d4bc1891f..59032f99d8 100644
--- a/servers/audio/effects/audio_effect_eq.cpp
+++ b/servers/audio/effects/audio_effect_eq.cpp
@@ -46,14 +46,14 @@ void AudioEffectEQInstance::process(const AudioFrame *p_src_frames, AudioFrame *
AudioFrame dst = AudioFrame(0, 0);
for (int j = 0; j < band_count; j++) {
- float l = src.l;
- float r = src.r;
+ float l = src.left;
+ float r = src.right;
proc_l[j].process_one(l);
proc_r[j].process_one(r);
- dst.l += l * bgain[j];
- dst.r += r * bgain[j];
+ dst.left += l * bgain[j];
+ dst.right += r * bgain[j];
}
p_dst_frames[i] = dst;
diff --git a/servers/audio/effects/audio_effect_filter.cpp b/servers/audio/effects/audio_effect_filter.cpp
index dd89784010..cd2839044f 100644
--- a/servers/audio/effects/audio_effect_filter.cpp
+++ b/servers/audio/effects/audio_effect_filter.cpp
@@ -34,7 +34,7 @@
template <int S>
void AudioEffectFilterInstance::_process_filter(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
for (int i = 0; i < p_frame_count; i++) {
- float f = p_src_frames[i].l;
+ float f = p_src_frames[i].left;
filter_process[0][0].process_one(f);
if constexpr (S > 1) {
filter_process[0][1].process_one(f);
@@ -46,11 +46,11 @@ void AudioEffectFilterInstance::_process_filter(const AudioFrame *p_src_frames,
filter_process[0][3].process_one(f);
}
- p_dst_frames[i].l = f;
+ p_dst_frames[i].left = f;
}
for (int i = 0; i < p_frame_count; i++) {
- float f = p_src_frames[i].r;
+ float f = p_src_frames[i].right;
filter_process[1][0].process_one(f);
if constexpr (S > 1) {
filter_process[1][1].process_one(f);
@@ -62,7 +62,7 @@ void AudioEffectFilterInstance::_process_filter(const AudioFrame *p_src_frames,
filter_process[1][3].process_one(f);
}
- p_dst_frames[i].r = f;
+ p_dst_frames[i].right = f;
}
}
diff --git a/servers/audio/effects/audio_effect_limiter.cpp b/servers/audio/effects/audio_effect_limiter.cpp
index ede6d77fbc..8c5dc731bb 100644
--- a/servers/audio/effects/audio_effect_limiter.cpp
+++ b/servers/audio/effects/audio_effect_limiter.cpp
@@ -41,8 +41,8 @@ void AudioEffectLimiterInstance::process(const AudioFrame *p_src_frames, AudioFr
float scmult = Math::abs((ceildb - sc) / (peakdb - sc));
for (int i = 0; i < p_frame_count; i++) {
- float spl0 = p_src_frames[i].l;
- float spl1 = p_src_frames[i].r;
+ float spl0 = p_src_frames[i].left;
+ float spl1 = p_src_frames[i].right;
spl0 = spl0 * makeup;
spl1 = spl1 * makeup;
float sign0 = (spl0 < 0.0 ? -1.0 : 1.0);
@@ -62,8 +62,8 @@ void AudioEffectLimiterInstance::process(const AudioFrame *p_src_frames, AudioFr
spl0 = MIN(ceiling, Math::abs(spl0)) * (spl0 < 0.0 ? -1.0 : 1.0);
spl1 = MIN(ceiling, Math::abs(spl1)) * (spl1 < 0.0 ? -1.0 : 1.0);
- p_dst_frames[i].l = spl0;
- p_dst_frames[i].r = spl1;
+ p_dst_frames[i].left = spl0;
+ p_dst_frames[i].right = spl1;
}
}
diff --git a/servers/audio/effects/audio_effect_panner.cpp b/servers/audio/effects/audio_effect_panner.cpp
index 74742d60de..3fbdcd450a 100644
--- a/servers/audio/effects/audio_effect_panner.cpp
+++ b/servers/audio/effects/audio_effect_panner.cpp
@@ -35,8 +35,8 @@ void AudioEffectPannerInstance::process(const AudioFrame *p_src_frames, AudioFra
float rvol = CLAMP(1.0 + base->pan, 0, 1);
for (int i = 0; i < p_frame_count; i++) {
- p_dst_frames[i].l = p_src_frames[i].l * lvol + p_src_frames[i].r * (1.0 - rvol);
- p_dst_frames[i].r = p_src_frames[i].r * rvol + p_src_frames[i].l * (1.0 - lvol);
+ p_dst_frames[i].left = p_src_frames[i].left * lvol + p_src_frames[i].right * (1.0 - rvol);
+ p_dst_frames[i].right = p_src_frames[i].right * rvol + p_src_frames[i].left * (1.0 - lvol);
}
}
diff --git a/servers/audio/effects/audio_effect_phaser.cpp b/servers/audio/effects/audio_effect_phaser.cpp
index 76d2ca5689..8093fd01ca 100644
--- a/servers/audio/effects/audio_effect_phaser.cpp
+++ b/servers/audio/effects/audio_effect_phaser.cpp
@@ -61,20 +61,20 @@ void AudioEffectPhaserInstance::process(const AudioFrame *p_src_frames, AudioFra
allpass[0][2].update(
allpass[0][3].update(
allpass[0][4].update(
- allpass[0][5].update(p_src_frames[i].l + h.l * base->feedback))))));
- h.l = y;
+ allpass[0][5].update(p_src_frames[i].left + h.left * base->feedback))))));
+ h.left = y;
- p_dst_frames[i].l = p_src_frames[i].l + y * base->depth;
+ p_dst_frames[i].left = p_src_frames[i].left + y * base->depth;
y = allpass[1][0].update(
allpass[1][1].update(
allpass[1][2].update(
allpass[1][3].update(
allpass[1][4].update(
- allpass[1][5].update(p_src_frames[i].r + h.r * base->feedback))))));
- h.r = y;
+ allpass[1][5].update(p_src_frames[i].right + h.right * base->feedback))))));
+ h.right = y;
- p_dst_frames[i].r = p_src_frames[i].r + y * base->depth;
+ p_dst_frames[i].right = p_src_frames[i].right + y * base->depth;
}
}
diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp
index e1e9270074..e30a8fa99e 100644
--- a/servers/audio/effects/audio_effect_record.cpp
+++ b/servers/audio/effects/audio_effect_record.cpp
@@ -87,8 +87,8 @@ void AudioEffectRecordInstance::_io_store_buffer() {
while (to_read) {
AudioFrame buffered_frame = rb_buf[ring_buffer_read_pos & ring_buffer_mask];
- recording_data.push_back(buffered_frame.l);
- recording_data.push_back(buffered_frame.r);
+ recording_data.push_back(buffered_frame.left);
+ recording_data.push_back(buffered_frame.right);
ring_buffer_read_pos++;
to_read--;
diff --git a/servers/audio/effects/audio_effect_reverb.cpp b/servers/audio/effects/audio_effect_reverb.cpp
index 00d21120fe..06d890fb58 100644
--- a/servers/audio/effects/audio_effect_reverb.cpp
+++ b/servers/audio/effects/audio_effect_reverb.cpp
@@ -51,20 +51,20 @@ void AudioEffectReverbInstance::process(const AudioFrame *p_src_frames, AudioFra
int to_mix = MIN(todo, Reverb::INPUT_BUFFER_MAX_SIZE);
for (int j = 0; j < to_mix; j++) {
- tmp_src[j] = p_src_frames[offset + j].l;
+ tmp_src[j] = p_src_frames[offset + j].left;
}
reverb[0].process(tmp_src, tmp_dst, to_mix);
for (int j = 0; j < to_mix; j++) {
- p_dst_frames[offset + j].l = tmp_dst[j];
- tmp_src[j] = p_src_frames[offset + j].r;
+ p_dst_frames[offset + j].left = tmp_dst[j];
+ tmp_src[j] = p_src_frames[offset + j].right;
}
reverb[1].process(tmp_src, tmp_dst, to_mix);
for (int j = 0; j < to_mix; j++) {
- p_dst_frames[offset + j].r = tmp_dst[j];
+ p_dst_frames[offset + j].right = tmp_dst[j];
}
offset += to_mix;
diff --git a/servers/audio/effects/audio_effect_spectrum_analyzer.cpp b/servers/audio/effects/audio_effect_spectrum_analyzer.cpp
index bd5b226e15..378ca2458f 100644
--- a/servers/audio/effects/audio_effect_spectrum_analyzer.cpp
+++ b/servers/audio/effects/audio_effect_spectrum_analyzer.cpp
@@ -115,9 +115,9 @@ void AudioEffectSpectrumAnalyzerInstance::process(const AudioFrame *p_src_frames
float *fftw = temporal_fft.ptrw();
for (int i = 0; i < to_fill; i++) { //left and right buffers
float window = -0.5 * Math::cos(to_fill_step * (double)temporal_fft_pos) + 0.5;
- fftw[temporal_fft_pos * 2] = window * p_src_frames->l;
+ fftw[temporal_fft_pos * 2] = window * p_src_frames->left;
fftw[temporal_fft_pos * 2 + 1] = 0;
- fftw[(temporal_fft_pos + fft_size * 2) * 2] = window * p_src_frames->r;
+ fftw[(temporal_fft_pos + fft_size * 2) * 2] = window * p_src_frames->right;
fftw[(temporal_fft_pos + fft_size * 2) * 2 + 1] = 0;
++p_src_frames;
++temporal_fft_pos;
@@ -135,8 +135,8 @@ void AudioEffectSpectrumAnalyzerInstance::process(const AudioFrame *p_src_frames
for (int i = 0; i < fft_size; i++) {
//abs(vec)/fft_size normalizes each frequency
- hw[i].l = Vector2(fftw[i * 2], fftw[i * 2 + 1]).length() / float(fft_size);
- hw[i].r = Vector2(fftw[fft_size * 4 + i * 2], fftw[fft_size * 4 + i * 2 + 1]).length() / float(fft_size);
+ hw[i].left = Vector2(fftw[i * 2], fftw[i * 2 + 1]).length() / float(fft_size);
+ hw[i].right = Vector2(fftw[fft_size * 4 + i * 2], fftw[fft_size * 4 + i * 2 + 1]).length() / float(fft_size);
}
fft_pos = next; //swap
@@ -199,8 +199,8 @@ Vector2 AudioEffectSpectrumAnalyzerInstance::get_magnitude_for_frequency_range(f
Vector2 max;
for (int i = begin_pos; i <= end_pos; i++) {
- max.x = MAX(max.x, r[i].l);
- max.y = MAX(max.y, r[i].r);
+ max.x = MAX(max.x, r[i].left);
+ max.y = MAX(max.y, r[i].right);
}
return max;
diff --git a/servers/audio/effects/audio_effect_stereo_enhance.cpp b/servers/audio/effects/audio_effect_stereo_enhance.cpp
index be9905724e..e0ea97f90f 100644
--- a/servers/audio/effects/audio_effect_stereo_enhance.cpp
+++ b/servers/audio/effects/audio_effect_stereo_enhance.cpp
@@ -39,8 +39,8 @@ void AudioEffectStereoEnhanceInstance::process(const AudioFrame *p_src_frames, A
unsigned int delay_frames = (base->time_pullout / 1000.0) * AudioServer::get_singleton()->get_mix_rate();
for (int i = 0; i < p_frame_count; i++) {
- float l = p_src_frames[i].l;
- float r = p_src_frames[i].r;
+ float l = p_src_frames[i].left;
+ float r = p_src_frames[i].right;
float center = (l + r) / 2.0f;
@@ -65,8 +65,8 @@ void AudioEffectStereoEnhanceInstance::process(const AudioFrame *p_src_frames, A
r = delay_ringbuff[(ringbuff_pos - delay_frames) & ringbuff_mask];
}
- p_dst_frames[i].l = l;
- p_dst_frames[i].r = r;
+ p_dst_frames[i].left = l;
+ p_dst_frames[i].right = r;
ringbuff_pos++;
}
}
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 4a901897de..69a66a7d62 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -285,13 +285,13 @@ void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) {
const AudioFrame *buf = master->channels[k].buffer.ptr();
for (int j = 0; j < to_copy; j++) {
- float l = CLAMP(buf[from + j].l, -1.0, 1.0);
+ float l = CLAMP(buf[from + j].left, -1.0, 1.0);
int32_t vl = l * ((1 << 20) - 1);
int32_t vl2 = (vl < 0 ? -1 : 1) * (ABS(vl) << 11);
*dest = vl2;
dest++;
- float r = CLAMP(buf[from + j].r, -1.0, 1.0);
+ float r = CLAMP(buf[from + j].right, -1.0, 1.0);
int32_t vr = r * ((1 << 20) - 1);
int32_t vr2 = (vr < 0 ? -1 : 1) * (ABS(vr) << 11);
*dest = vr2;
@@ -588,22 +588,22 @@ void AudioServer::_mix_step() {
for (uint32_t j = 0; j < buffer_size; j++) {
buf[j] *= volume;
- float l = ABS(buf[j].l);
- if (l > peak.l) {
- peak.l = l;
+ float l = ABS(buf[j].left);
+ if (l > peak.left) {
+ peak.left = l;
}
- float r = ABS(buf[j].r);
- if (r > peak.r) {
- peak.r = r;
+ float r = ABS(buf[j].right);
+ if (r > peak.right) {
+ peak.right = r;
}
}
- bus->channels.write[k].peak_volume = AudioFrame(Math::linear_to_db(peak.l + AUDIO_PEAK_OFFSET), Math::linear_to_db(peak.r + AUDIO_PEAK_OFFSET));
+ bus->channels.write[k].peak_volume = AudioFrame(Math::linear_to_db(peak.left + AUDIO_PEAK_OFFSET), Math::linear_to_db(peak.right + AUDIO_PEAK_OFFSET));
if (!bus->channels[k].used) {
//see if any audio is contained, because channel was not used
- if (MAX(peak.r, peak.l) > Math::db_to_linear(channel_disable_threshold_db)) {
+ if (MAX(peak.right, peak.left) > Math::db_to_linear(channel_disable_threshold_db)) {
bus->channels.write[k].last_mix_with_audio = mix_frames;
} else if (mix_frames - bus->channels[k].last_mix_with_audio > channel_disable_frames) {
bus->channels.write[k].active = false;
@@ -639,7 +639,7 @@ void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_sou
ERR_FAIL_NULL(p_processor_l);
ERR_FAIL_NULL(p_processor_r);
- bool is_just_started = p_vol_start.l == 0 && p_vol_start.r == 0;
+ bool is_just_started = p_vol_start.left == 0 && p_vol_start.right == 0;
p_processor_l->set_filter(&filter, /* clear_history= */ is_just_started);
p_processor_l->update_coeffs(buffer_size);
p_processor_r->set_filter(&filter, /* clear_history= */ is_just_started);
@@ -650,8 +650,8 @@ void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_sou
float lerp_param = (float)frame_idx / buffer_size;
AudioFrame vol = p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start;
AudioFrame mixed = vol * p_source_buf[frame_idx];
- p_processor_l->process_one_interp(mixed.l);
- p_processor_r->process_one_interp(mixed.r);
+ p_processor_l->process_one_interp(mixed.left);
+ p_processor_r->process_one_interp(mixed.right);
p_out_buf[frame_idx] += mixed;
}
@@ -1107,14 +1107,14 @@ float AudioServer::get_bus_peak_volume_left_db(int p_bus, int p_channel) const {
ERR_FAIL_INDEX_V(p_bus, buses.size(), 0);
ERR_FAIL_INDEX_V(p_channel, buses[p_bus]->channels.size(), 0);
- return buses[p_bus]->channels[p_channel].peak_volume.l;
+ return buses[p_bus]->channels[p_channel].peak_volume.left;
}
float AudioServer::get_bus_peak_volume_right_db(int p_bus, int p_channel) const {
ERR_FAIL_INDEX_V(p_bus, buses.size(), 0);
ERR_FAIL_INDEX_V(p_channel, buses[p_bus]->channels.size(), 0);
- return buses[p_bus]->channels[p_channel].peak_volume.r;
+ return buses[p_bus]->channels[p_channel].peak_volume.right;
}
bool AudioServer::is_bus_channel_active(int p_bus, int p_channel) const {
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 847be469db..0496fb9180 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -587,6 +587,27 @@ void DisplayServer::set_icon(const Ref<Image> &p_icon) {
WARN_PRINT("Icon not supported by this display server.");
}
+DisplayServer::IndicatorID DisplayServer::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
+ WARN_PRINT("Status indicator not supported by this display server.");
+ return INVALID_INDICATOR_ID;
+}
+
+void DisplayServer::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
+ WARN_PRINT("Status indicator not supported by this display server.");
+}
+
+void DisplayServer::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
+ WARN_PRINT("Status indicator not supported by this display server.");
+}
+
+void DisplayServer::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
+ WARN_PRINT("Status indicator not supported by this display server.");
+}
+
+void DisplayServer::delete_status_indicator(IndicatorID p_id) {
+ WARN_PRINT("Status indicator not supported by this display server.");
+}
+
int64_t DisplayServer::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
WARN_PRINT("Native handle not supported by this display server.");
return 0;
@@ -825,6 +846,12 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_native_icon", "filename"), &DisplayServer::set_native_icon);
ClassDB::bind_method(D_METHOD("set_icon", "image"), &DisplayServer::set_icon);
+ ClassDB::bind_method(D_METHOD("create_status_indicator", "icon", "tooltip", "callback"), &DisplayServer::create_status_indicator);
+ ClassDB::bind_method(D_METHOD("status_indicator_set_icon", "id", "icon"), &DisplayServer::status_indicator_set_icon);
+ ClassDB::bind_method(D_METHOD("status_indicator_set_tooltip", "id", "tooltip"), &DisplayServer::status_indicator_set_tooltip);
+ ClassDB::bind_method(D_METHOD("status_indicator_set_callback", "id", "callback"), &DisplayServer::status_indicator_set_callback);
+ ClassDB::bind_method(D_METHOD("delete_status_indicator", "id"), &DisplayServer::delete_status_indicator);
+
ClassDB::bind_method(D_METHOD("tablet_get_driver_count"), &DisplayServer::tablet_get_driver_count);
ClassDB::bind_method(D_METHOD("tablet_get_driver_name", "idx"), &DisplayServer::tablet_get_driver_name);
ClassDB::bind_method(D_METHOD("tablet_get_current_driver"), &DisplayServer::tablet_get_current_driver);
@@ -851,6 +878,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_TEXT_TO_SPEECH);
BIND_ENUM_CONSTANT(FEATURE_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(FEATURE_SCREEN_CAPTURE);
+ BIND_ENUM_CONSTANT(FEATURE_STATUS_INDICATOR);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
@@ -865,6 +893,7 @@ void DisplayServer::_bind_methods() {
BIND_CONSTANT(MAIN_WINDOW_ID);
BIND_CONSTANT(INVALID_WINDOW_ID);
+ BIND_CONSTANT(INVALID_INDICATOR_ID);
BIND_ENUM_CONSTANT(SCREEN_LANDSCAPE);
BIND_ENUM_CONSTANT(SCREEN_PORTRAIT);
diff --git a/servers/display_server.h b/servers/display_server.h
index 6ec0831500..6e5a4655a2 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -125,6 +125,7 @@ public:
FEATURE_TEXT_TO_SPEECH,
FEATURE_EXTEND_TO_TITLE,
FEATURE_SCREEN_CAPTURE,
+ FEATURE_STATUS_INDICATOR,
};
virtual bool has_feature(Feature p_feature) const = 0;
@@ -332,10 +333,12 @@ public:
virtual bool screen_is_kept_on() const;
enum {
MAIN_WINDOW_ID = 0,
- INVALID_WINDOW_ID = -1
+ INVALID_WINDOW_ID = -1,
+ INVALID_INDICATOR_ID = -1
};
typedef int WindowID;
+ typedef int IndicatorID;
virtual Vector<DisplayServer::WindowID> get_window_list() const = 0;
@@ -540,6 +543,12 @@ public:
virtual void set_native_icon(const String &p_filename);
virtual void set_icon(const Ref<Image> &p_icon);
+ virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback);
+ virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon);
+ virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip);
+ virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback);
+ virtual void delete_status_indicator(IndicatorID p_id);
+
enum Context {
CONTEXT_EDITOR,
CONTEXT_PROJECTMAN,