diff options
60 files changed, 840 insertions, 314 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index bf1595b41b..4e3196dec3 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1387,7 +1387,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/occlusion_culling/bvh_build_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), 2); GLOBAL_DEF(PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1"), 60); // No negative and limit to 500 due to crashes. GLOBAL_DEF_RST("internationalization/rendering/force_right_to_left_layout_direction", false); - GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Locale,Left-to-Right,Right-to-Left"), 0); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Application Locale,Left-to-Right,Right-to-Left,Based on System Locale"), 0); GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 2000); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 64fa597a67..04ecabaf27 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -489,12 +489,14 @@ RWLock ResourceCache::path_cache_lock; #endif void ResourceCache::clear() { - if (resources.size()) { - ERR_PRINT("Resources still in use at exit (run with --verbose for details)."); + if (!resources.is_empty()) { if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT(vformat("%d resources still in use at exit.", resources.size())); for (const KeyValue<String, Resource *> &E : resources) { print_line(vformat("Resource still in use: %s (%s)", E.key, E.value->get_class())); } + } else { + ERR_PRINT(vformat("%d resources still in use at exit (run with --verbose for details).", resources.size())); } } diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 193284896a..77c5a168f3 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1393,7 +1393,7 @@ <description> Converts the given [param variant] to the given [param type], using the [enum Variant.Type] values. This method is generous with how it handles types, it can automatically convert between array types, convert numeric [String]s to [int], and converting most things to [String]. If the type conversion cannot be done, this method will return the default value for that type, for example converting [Rect2] to [Vector2] will always return [constant Vector2.ZERO]. This method will never show error messages as long as [param type] is a valid Variant type. - The returned value is a [Variant], but the data inside and the [enum Variant.Type] will be the same as the requested type. + The returned value is a [Variant], but the data inside and its type will be the same as the requested type. [codeblock] type_convert("Hi!", TYPE_INT) # Returns 0 type_convert("123", TYPE_INT) # Returns 123 diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index b8f82accfc..8436cbf6ee 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1122,6 +1122,10 @@ <param index="0" name="name" type="String" /> <description> Set active tablet driver name. + Supported drivers: + - [code]winink[/code]: Windows Ink API, default (Windows 8.1+ required). + - [code]wintab[/code]: Wacom Wintab API (compatible device driver required). + - [code]dummy[/code]: Dummy driver, tablet input is disabled. [b]Note:[/b] This method is implemented only on Windows. </description> </method> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index ec051c0545..98b4920953 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -599,6 +599,9 @@ <member name="interface/editor/single_window_mode" type="bool" setter="" getter=""> If [code]true[/code], embed modal windows such as docks inside the main editor window. When single-window mode is enabled, tooltips will also be embedded inside the main editor window, which means they can't be displayed outside of the editor window. </member> + <member name="interface/editor/ui_layout_direction" type="int" setter="" getter=""> + Editor UI default layout direction. + </member> <member name="interface/editor/unfocused_low_processor_mode_sleep_usec" type="float" setter="" getter=""> When the editor window is unfocused, the amount of sleeping between frames when the low-processor usage mode is enabled (in microseconds). Higher values will result in lower CPU/GPU usage, which can improve battery life on laptops (in addition to improving the running project's performance if the editor has to redraw continuously). However, higher values will result in a less responsive editor. The default value is set to limit the editor to 20 FPS when the editor window is unfocused. See also [member interface/editor/low_processor_mode_sleep_usec]. </member> diff --git a/doc/classes/MultiplayerPeer.xml b/doc/classes/MultiplayerPeer.xml index 350fffed32..39980a05e1 100644 --- a/doc/classes/MultiplayerPeer.xml +++ b/doc/classes/MultiplayerPeer.xml @@ -48,7 +48,7 @@ <method name="get_packet_mode" qualifiers="const"> <return type="int" enum="MultiplayerPeer.TransferMode" /> <description> - Returns the [enum MultiplayerPeer.TransferMode] the remote peer used to send the next available packet. See [method PacketPeer.get_available_packet_count]. + Returns the transfer mode the remote peer used to send the next available packet. See [method PacketPeer.get_available_packet_count]. </description> </method> <method name="get_packet_peer" qualifiers="const"> diff --git a/doc/classes/MultiplayerPeerExtension.xml b/doc/classes/MultiplayerPeerExtension.xml index 9b2b5dbc94..8fd6755a7b 100644 --- a/doc/classes/MultiplayerPeerExtension.xml +++ b/doc/classes/MultiplayerPeerExtension.xml @@ -58,7 +58,7 @@ <method name="_get_packet_mode" qualifiers="virtual const"> <return type="int" enum="MultiplayerPeer.TransferMode" /> <description> - Called to get the [enum MultiplayerPeer.TransferMode] the remote peer used to send the next available packet. See [method MultiplayerPeer.get_packet_mode]. + Called to get the transfer mode the remote peer used to send the next available packet. See [method MultiplayerPeer.get_packet_mode]. </description> </method> <method name="_get_packet_peer" qualifiers="virtual const"> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 6bbef47763..a3ec4d25f1 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -3337,7 +3337,7 @@ <return type="int" enum="Image.Format" /> <param index="0" name="texture" type="RID" /> <description> - Returns the [enum Image.Format] for the texture. + Returns the format for the texture. </description> </method> <method name="texture_get_native_handle" qualifiers="const"> diff --git a/doc/classes/TextureProgressBar.xml b/doc/classes/TextureProgressBar.xml index 0c1e9adf71..6ab1b86e5c 100644 --- a/doc/classes/TextureProgressBar.xml +++ b/doc/classes/TextureProgressBar.xml @@ -34,28 +34,28 @@ If [code]true[/code], Godot treats the bar's textures like in [NinePatchRect]. Use the [code]stretch_margin_*[/code] properties like [member stretch_margin_bottom] to set up the nine patch's 3×3 grid. When using a radial [member fill_mode], this setting will enable stretching. </member> <member name="radial_center_offset" type="Vector2" setter="set_radial_center_offset" getter="get_radial_center_offset" default="Vector2(0, 0)"> - Offsets [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE] or [constant FILL_COUNTER_CLOCKWISE]. + Offsets [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE], [constant FILL_COUNTER_CLOCKWISE], or [constant FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE]. </member> <member name="radial_fill_degrees" type="float" setter="set_fill_degrees" getter="get_fill_degrees" default="360.0"> - Upper limit for the fill of [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE] or [constant FILL_COUNTER_CLOCKWISE]. When the node's [code]value[/code] is equal to its [code]max_value[/code], the texture fills up to this angle. + Upper limit for the fill of [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE], [constant FILL_COUNTER_CLOCKWISE], or [constant FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE]. When the node's [code]value[/code] is equal to its [code]max_value[/code], the texture fills up to this angle. See [member Range.value], [member Range.max_value]. </member> <member name="radial_initial_angle" type="float" setter="set_radial_initial_angle" getter="get_radial_initial_angle" default="0.0"> - Starting angle for the fill of [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE] or [constant FILL_COUNTER_CLOCKWISE]. When the node's [code]value[/code] is equal to its [code]min_value[/code], the texture doesn't show up at all. When the [code]value[/code] increases, the texture fills and tends towards [member radial_fill_degrees]. + Starting angle for the fill of [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE], [constant FILL_COUNTER_CLOCKWISE], or [constant FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE]. When the node's [code]value[/code] is equal to its [code]min_value[/code], the texture doesn't show up at all. When the [code]value[/code] increases, the texture fills and tends towards [member radial_fill_degrees]. </member> <member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="1" /> <member name="step" type="float" setter="set_step" getter="get_step" overrides="Range" default="1.0" /> <member name="stretch_margin_bottom" type="int" setter="set_stretch_margin" getter="get_stretch_margin" default="0"> - The height of the 9-patch's bottom row. A margin of 16 means the 9-slice's bottom corners and side will have a height of 16 pixels. You can set all 4 margin values individually to create panels with non-uniform borders. + The height of the 9-patch's bottom row. A margin of 16 means the 9-slice's bottom corners and side will have a height of 16 pixels. You can set all 4 margin values individually to create panels with non-uniform borders. Only effective if [member nine_patch_stretch] is [code]true[/code]. </member> <member name="stretch_margin_left" type="int" setter="set_stretch_margin" getter="get_stretch_margin" default="0"> - The width of the 9-patch's left column. + The width of the 9-patch's left column. Only effective if [member nine_patch_stretch] is [code]true[/code]. </member> <member name="stretch_margin_right" type="int" setter="set_stretch_margin" getter="get_stretch_margin" default="0"> - The width of the 9-patch's right column. + The width of the 9-patch's right column. Only effective if [member nine_patch_stretch] is [code]true[/code]. </member> <member name="stretch_margin_top" type="int" setter="set_stretch_margin" getter="get_stretch_margin" default="0"> - The height of the 9-patch's top row. + The height of the 9-patch's top row. Only effective if [member nine_patch_stretch] is [code]true[/code]. </member> <member name="texture_over" type="Texture2D" setter="set_over_texture" getter="get_over_texture"> [Texture2D] that draws over the progress bar. Use it to add highlights or an upper-frame that hides part of [member texture_progress]. diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml index 755c266cd9..6f212274f8 100644 --- a/doc/classes/TileSetAtlasSource.xml +++ b/doc/classes/TileSetAtlasSource.xml @@ -90,7 +90,7 @@ <return type="int" enum="TileSetAtlasSource.TileAnimationMode" /> <param index="0" name="atlas_coords" type="Vector2i" /> <description> - Returns the [enum TileAnimationMode] of the tile at [param atlas_coords]. See also [method set_tile_animation_mode]. + Returns the tile animation mode of the tile at [param atlas_coords]. See also [method set_tile_animation_mode]. </description> </method> <method name="get_tile_animation_separation" qualifiers="const"> @@ -239,7 +239,7 @@ <param index="0" name="atlas_coords" type="Vector2i" /> <param index="1" name="mode" type="int" enum="TileSetAtlasSource.TileAnimationMode" /> <description> - Sets the [enum TileAnimationMode] of the tile at [param atlas_coords] to [param mode]. See also [method get_tile_animation_mode]. + Sets the tile animation mode of the tile at [param atlas_coords] to [param mode]. See also [method get_tile_animation_mode]. </description> </method> <method name="set_tile_animation_separation"> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index e322e3adc0..bf5a504aba 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -506,6 +506,9 @@ <theme_item name="font_color" data_type="color" type="Color" default="Color(0.7, 0.7, 0.7, 1)"> Default text [Color] of the item. </theme_item> + <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> + Text [Color] for a [constant TreeItem.CELL_MODE_CHECK] mode cell when it's non-editable (see [method TreeItem.set_editable]). + </theme_item> <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> The tint of text outline of the item. </theme_item> @@ -619,16 +622,25 @@ The arrow icon used when a foldable item is collapsed (for right-to-left layouts). </theme_item> <theme_item name="checked" data_type="icon" type="Texture2D"> - The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is checked. + The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is checked and editable (see [method TreeItem.set_editable]). + </theme_item> + <theme_item name="checked_disabled" data_type="icon" type="Texture2D"> + The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is checked and non-editable (see [method TreeItem.set_editable]). </theme_item> <theme_item name="indeterminate" data_type="icon" type="Texture2D"> - The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is indeterminate. + The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is indeterminate and editable (see [method TreeItem.set_editable]). + </theme_item> + <theme_item name="indeterminate_disabled" data_type="icon" type="Texture2D"> + The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is indeterminate and non-editable (see [method TreeItem.set_editable]). </theme_item> <theme_item name="select_arrow" data_type="icon" type="Texture2D"> The arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell. </theme_item> <theme_item name="unchecked" data_type="icon" type="Texture2D"> - The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is unchecked. + The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is unchecked and editable (see [method TreeItem.set_editable]). + </theme_item> + <theme_item name="unchecked_disabled" data_type="icon" type="Texture2D"> + The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is unchecked and non-editable (see [method TreeItem.set_editable]). </theme_item> <theme_item name="updown" data_type="icon" type="Texture2D"> The updown arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell. diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 4d0f453fe0..6e521c9e14 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -75,7 +75,7 @@ <return type="int" enum="Viewport.PositionalShadowAtlasQuadrantSubdiv" /> <param index="0" name="quadrant" type="int" /> <description> - Returns the [enum PositionalShadowAtlasQuadrantSubdiv] of the specified quadrant. + Returns the positional shadow atlas quadrant subdivision of the specified quadrant. </description> </method> <method name="get_render_info"> diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml index 99d6e67e51..a7878378dd 100644 --- a/doc/classes/XRInterface.xml +++ b/doc/classes/XRInterface.xml @@ -119,7 +119,7 @@ <param index="0" name="mode" type="int" enum="XRInterface.EnvironmentBlendMode" /> <description> Sets the active environment blend mode. - [param mode] is the [enum XRInterface.EnvironmentBlendMode] starting with the next frame. + [param mode] is the environment blend mode starting with the next frame. [b]Note:[/b] Not all runtimes support all environment blend modes, so it is important to check this at startup. For example: [codeblock] func _ready(): diff --git a/doc/classes/XRInterfaceExtension.xml b/doc/classes/XRInterfaceExtension.xml index ea2bbf4cfb..b2c27bedf3 100644 --- a/doc/classes/XRInterfaceExtension.xml +++ b/doc/classes/XRInterfaceExtension.xml @@ -67,7 +67,7 @@ <method name="_get_play_area_mode" qualifiers="virtual const"> <return type="int" enum="XRInterface.PlayAreaMode" /> <description> - Returns the [enum XRInterface.PlayAreaMode] that sets up our play area. + Returns the play area mode that sets up our play area. </description> </method> <method name="_get_projection_for_view" qualifiers="virtual"> diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index c514ca7ba7..cdaa0c036d 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6864,6 +6864,10 @@ EditorNode::EditorNode() { AcceptDialog::set_swap_cancel_ok(swap_cancel_ok == 2); } + int ed_root_dir = EDITOR_GET("interface/editor/ui_layout_direction"); + Control::set_root_layout_direction(ed_root_dir); + Window::set_root_layout_direction(ed_root_dir); + ResourceLoader::set_abort_on_missing_resources(false); ResourceLoader::set_error_notify_func(&EditorNode::add_io_error); ResourceLoader::set_dependency_error_notify_func(&EditorNode::_dependency_error_report); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index f891bfbff7..27530dc641 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1312,17 +1312,12 @@ void EditorPropertyInteger::_set_read_only(bool p_read_only) { } void EditorPropertyInteger::_value_changed(int64_t val) { - if (setting) { - return; - } emit_changed(get_edited_property(), val); } void EditorPropertyInteger::update_property() { int64_t val = get_edited_property_value(); - setting = true; - spin->set_value(val); - setting = false; + spin->set_value_no_signal(val); #ifdef DEBUG_ENABLED // If spin (currently EditorSplinSlider : Range) is changed so that it can use int64_t, then the below warning wouldn't be a problem. if (val != (int64_t)(double)(val)) { @@ -1452,10 +1447,6 @@ void EditorPropertyFloat::_set_read_only(bool p_read_only) { } void EditorPropertyFloat::_value_changed(double val) { - if (setting) { - return; - } - if (radians_as_degrees) { val = Math::deg_to_rad(val); } @@ -1467,9 +1458,7 @@ void EditorPropertyFloat::update_property() { if (radians_as_degrees) { val = Math::rad_to_deg(val); } - setting = true; - spin->set_value(val); - setting = false; + spin->set_value_no_signal(val); } void EditorPropertyFloat::_bind_methods() { @@ -1627,18 +1616,12 @@ void EditorPropertyEasing::_set_preset(int p_preset) { } void EditorPropertyEasing::_setup_spin() { - setting = true; spin->setup_and_show(); spin->get_line_edit()->set_text(TS->format_number(rtos(get_edited_property_value()))); - setting = false; spin->show(); } void EditorPropertyEasing::_spin_value_changed(double p_value) { - if (setting) { - return; - } - // 0 is a singularity, but both positive and negative values // are otherwise allowed. Enforce 0+ as workaround. if (Math::is_zero_approx(p_value)) { @@ -1725,10 +1708,6 @@ void EditorPropertyRect2::_set_read_only(bool p_read_only) { } void EditorPropertyRect2::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Rect2 r2; r2.position.x = spin[0]->get_value(); r2.position.y = spin[1]->get_value(); @@ -1739,12 +1718,10 @@ void EditorPropertyRect2::_value_changed(double val, const String &p_name) { void EditorPropertyRect2::update_property() { Rect2 val = get_edited_property_value(); - setting = true; - spin[0]->set_value(val.position.x); - spin[1]->set_value(val.position.y); - spin[2]->set_value(val.size.x); - spin[3]->set_value(val.size.y); - setting = false; + spin[0]->set_value_no_signal(val.position.x); + spin[1]->set_value_no_signal(val.position.y); + spin[2]->set_value_no_signal(val.size.x); + spin[3]->set_value_no_signal(val.size.y); } void EditorPropertyRect2::_notification(int p_what) { @@ -1828,10 +1805,6 @@ void EditorPropertyRect2i::_set_read_only(bool p_read_only) { } void EditorPropertyRect2i::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Rect2i r2; r2.position.x = spin[0]->get_value(); r2.position.y = spin[1]->get_value(); @@ -1842,12 +1815,10 @@ void EditorPropertyRect2i::_value_changed(double val, const String &p_name) { void EditorPropertyRect2i::update_property() { Rect2i val = get_edited_property_value(); - setting = true; - spin[0]->set_value(val.position.x); - spin[1]->set_value(val.position.y); - spin[2]->set_value(val.size.x); - spin[3]->set_value(val.size.y); - setting = false; + spin[0]->set_value_no_signal(val.position.x); + spin[1]->set_value_no_signal(val.position.y); + spin[2]->set_value_no_signal(val.size.x); + spin[3]->set_value_no_signal(val.size.y); } void EditorPropertyRect2i::_notification(int p_what) { @@ -1930,10 +1901,6 @@ void EditorPropertyPlane::_set_read_only(bool p_read_only) { } void EditorPropertyPlane::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Plane p; p.normal.x = spin[0]->get_value(); p.normal.y = spin[1]->get_value(); @@ -1944,12 +1911,10 @@ void EditorPropertyPlane::_value_changed(double val, const String &p_name) { void EditorPropertyPlane::update_property() { Plane val = get_edited_property_value(); - setting = true; - spin[0]->set_value(val.normal.x); - spin[1]->set_value(val.normal.y); - spin[2]->set_value(val.normal.z); - spin[3]->set_value(val.d); - setting = false; + spin[0]->set_value_no_signal(val.normal.x); + spin[1]->set_value_no_signal(val.normal.y); + spin[2]->set_value_no_signal(val.normal.z); + spin[3]->set_value_no_signal(val.d); } void EditorPropertyPlane::_notification(int p_what) { @@ -2041,10 +2006,6 @@ void EditorPropertyQuaternion::_edit_custom_value() { } void EditorPropertyQuaternion::_custom_value_changed(double val) { - if (setting) { - return; - } - edit_euler.x = euler[0]->get_value(); edit_euler.y = euler[1]->get_value(); edit_euler.z = euler[2]->get_value(); @@ -2055,17 +2016,13 @@ void EditorPropertyQuaternion::_custom_value_changed(double val) { v.z = Math::deg_to_rad(edit_euler.z); Quaternion temp_q = Quaternion::from_euler(v); - spin[0]->set_value(temp_q.x); - spin[1]->set_value(temp_q.y); - spin[2]->set_value(temp_q.z); - spin[3]->set_value(temp_q.w); + spin[0]->set_value_no_signal(temp_q.x); + spin[1]->set_value_no_signal(temp_q.y); + spin[2]->set_value_no_signal(temp_q.z); + spin[3]->set_value_no_signal(temp_q.w); } void EditorPropertyQuaternion::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Quaternion p; p.x = spin[0]->get_value(); p.y = spin[1]->get_value(); @@ -2085,21 +2042,19 @@ bool EditorPropertyQuaternion::is_grabbing_euler() { void EditorPropertyQuaternion::update_property() { Quaternion val = get_edited_property_value(); - setting = true; - spin[0]->set_value(val.x); - spin[1]->set_value(val.y); - spin[2]->set_value(val.z); - spin[3]->set_value(val.w); + spin[0]->set_value_no_signal(val.x); + spin[1]->set_value_no_signal(val.y); + spin[2]->set_value_no_signal(val.z); + spin[3]->set_value_no_signal(val.w); if (!is_grabbing_euler()) { Vector3 v = val.normalized().get_euler(); edit_euler.x = Math::rad_to_deg(v.x); edit_euler.y = Math::rad_to_deg(v.y); edit_euler.z = Math::rad_to_deg(v.z); - euler[0]->set_value(edit_euler.x); - euler[1]->set_value(edit_euler.y); - euler[2]->set_value(edit_euler.z); + euler[0]->set_value_no_signal(edit_euler.x); + euler[1]->set_value_no_signal(edit_euler.y); + euler[2]->set_value_no_signal(edit_euler.z); } - setting = false; } void EditorPropertyQuaternion::_warning_pressed() { @@ -2240,10 +2195,6 @@ void EditorPropertyAABB::_set_read_only(bool p_read_only) { } void EditorPropertyAABB::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - AABB p; p.position.x = spin[0]->get_value(); p.position.y = spin[1]->get_value(); @@ -2251,21 +2202,17 @@ void EditorPropertyAABB::_value_changed(double val, const String &p_name) { p.size.x = spin[3]->get_value(); p.size.y = spin[4]->get_value(); p.size.z = spin[5]->get_value(); - emit_changed(get_edited_property(), p, p_name); } void EditorPropertyAABB::update_property() { AABB val = get_edited_property_value(); - setting = true; - spin[0]->set_value(val.position.x); - spin[1]->set_value(val.position.y); - spin[2]->set_value(val.position.z); - spin[3]->set_value(val.size.x); - spin[4]->set_value(val.size.y); - spin[5]->set_value(val.size.z); - - setting = false; + spin[0]->set_value_no_signal(val.position.x); + spin[1]->set_value_no_signal(val.position.y); + spin[2]->set_value_no_signal(val.position.z); + spin[3]->set_value_no_signal(val.size.x); + spin[4]->set_value_no_signal(val.size.y); + spin[5]->set_value_no_signal(val.size.z); } void EditorPropertyAABB::_notification(int p_what) { @@ -2323,10 +2270,6 @@ void EditorPropertyTransform2D::_set_read_only(bool p_read_only) { } void EditorPropertyTransform2D::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Transform2D p; p[0][0] = spin[0]->get_value(); p[1][0] = spin[1]->get_value(); @@ -2340,15 +2283,12 @@ void EditorPropertyTransform2D::_value_changed(double val, const String &p_name) void EditorPropertyTransform2D::update_property() { Transform2D val = get_edited_property_value(); - setting = true; - spin[0]->set_value(val[0][0]); - spin[1]->set_value(val[1][0]); - spin[2]->set_value(val[2][0]); - spin[3]->set_value(val[0][1]); - spin[4]->set_value(val[1][1]); - spin[5]->set_value(val[2][1]); - - setting = false; + spin[0]->set_value_no_signal(val[0][0]); + spin[1]->set_value_no_signal(val[1][0]); + spin[2]->set_value_no_signal(val[2][0]); + spin[3]->set_value_no_signal(val[0][1]); + spin[4]->set_value_no_signal(val[1][1]); + spin[5]->set_value_no_signal(val[2][1]); } void EditorPropertyTransform2D::_notification(int p_what) { @@ -2414,10 +2354,6 @@ void EditorPropertyBasis::_set_read_only(bool p_read_only) { } void EditorPropertyBasis::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Basis p; p[0][0] = spin[0]->get_value(); p[0][1] = spin[1]->get_value(); @@ -2434,18 +2370,15 @@ void EditorPropertyBasis::_value_changed(double val, const String &p_name) { void EditorPropertyBasis::update_property() { Basis val = get_edited_property_value(); - setting = true; - spin[0]->set_value(val[0][0]); - spin[1]->set_value(val[0][1]); - spin[2]->set_value(val[0][2]); - spin[3]->set_value(val[1][0]); - spin[4]->set_value(val[1][1]); - spin[5]->set_value(val[1][2]); - spin[6]->set_value(val[2][0]); - spin[7]->set_value(val[2][1]); - spin[8]->set_value(val[2][2]); - - setting = false; + spin[0]->set_value_no_signal(val[0][0]); + spin[1]->set_value_no_signal(val[0][1]); + spin[2]->set_value_no_signal(val[0][2]); + spin[3]->set_value_no_signal(val[1][0]); + spin[4]->set_value_no_signal(val[1][1]); + spin[5]->set_value_no_signal(val[1][2]); + spin[6]->set_value_no_signal(val[2][0]); + spin[7]->set_value_no_signal(val[2][1]); + spin[8]->set_value_no_signal(val[2][2]); } void EditorPropertyBasis::_notification(int p_what) { @@ -2504,10 +2437,6 @@ void EditorPropertyTransform3D::_set_read_only(bool p_read_only) { } void EditorPropertyTransform3D::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Transform3D p; p.basis[0][0] = spin[0]->get_value(); p.basis[0][1] = spin[1]->get_value(); @@ -2530,20 +2459,18 @@ void EditorPropertyTransform3D::update_property() { } void EditorPropertyTransform3D::update_using_transform(Transform3D p_transform) { - setting = true; - spin[0]->set_value(p_transform.basis[0][0]); - spin[1]->set_value(p_transform.basis[0][1]); - spin[2]->set_value(p_transform.basis[0][2]); - spin[3]->set_value(p_transform.origin[0]); - spin[4]->set_value(p_transform.basis[1][0]); - spin[5]->set_value(p_transform.basis[1][1]); - spin[6]->set_value(p_transform.basis[1][2]); - spin[7]->set_value(p_transform.origin[1]); - spin[8]->set_value(p_transform.basis[2][0]); - spin[9]->set_value(p_transform.basis[2][1]); - spin[10]->set_value(p_transform.basis[2][2]); - spin[11]->set_value(p_transform.origin[2]); - setting = false; + spin[0]->set_value_no_signal(p_transform.basis[0][0]); + spin[1]->set_value_no_signal(p_transform.basis[0][1]); + spin[2]->set_value_no_signal(p_transform.basis[0][2]); + spin[3]->set_value_no_signal(p_transform.origin[0]); + spin[4]->set_value_no_signal(p_transform.basis[1][0]); + spin[5]->set_value_no_signal(p_transform.basis[1][1]); + spin[6]->set_value_no_signal(p_transform.basis[1][2]); + spin[7]->set_value_no_signal(p_transform.origin[1]); + spin[8]->set_value_no_signal(p_transform.basis[2][0]); + spin[9]->set_value_no_signal(p_transform.basis[2][1]); + spin[10]->set_value_no_signal(p_transform.basis[2][2]); + spin[11]->set_value_no_signal(p_transform.origin[2]); } void EditorPropertyTransform3D::_notification(int p_what) { @@ -2602,10 +2529,6 @@ void EditorPropertyProjection::_set_read_only(bool p_read_only) { } void EditorPropertyProjection::_value_changed(double val, const String &p_name) { - if (setting) { - return; - } - Projection p; p.columns[0][0] = spin[0]->get_value(); p.columns[0][1] = spin[1]->get_value(); @@ -2632,24 +2555,22 @@ void EditorPropertyProjection::update_property() { } void EditorPropertyProjection::update_using_transform(Projection p_transform) { - setting = true; - spin[0]->set_value(p_transform.columns[0][0]); - spin[1]->set_value(p_transform.columns[0][1]); - spin[2]->set_value(p_transform.columns[0][2]); - spin[3]->set_value(p_transform.columns[0][3]); - spin[4]->set_value(p_transform.columns[1][0]); - spin[5]->set_value(p_transform.columns[1][1]); - spin[6]->set_value(p_transform.columns[1][2]); - spin[7]->set_value(p_transform.columns[1][3]); - spin[8]->set_value(p_transform.columns[2][0]); - spin[9]->set_value(p_transform.columns[2][1]); - spin[10]->set_value(p_transform.columns[2][2]); - spin[11]->set_value(p_transform.columns[2][3]); - spin[12]->set_value(p_transform.columns[3][0]); - spin[13]->set_value(p_transform.columns[3][1]); - spin[14]->set_value(p_transform.columns[3][2]); - spin[15]->set_value(p_transform.columns[3][3]); - setting = false; + spin[0]->set_value_no_signal(p_transform.columns[0][0]); + spin[1]->set_value_no_signal(p_transform.columns[0][1]); + spin[2]->set_value_no_signal(p_transform.columns[0][2]); + spin[3]->set_value_no_signal(p_transform.columns[0][3]); + spin[4]->set_value_no_signal(p_transform.columns[1][0]); + spin[5]->set_value_no_signal(p_transform.columns[1][1]); + spin[6]->set_value_no_signal(p_transform.columns[1][2]); + spin[7]->set_value_no_signal(p_transform.columns[1][3]); + spin[8]->set_value_no_signal(p_transform.columns[2][0]); + spin[9]->set_value_no_signal(p_transform.columns[2][1]); + spin[10]->set_value_no_signal(p_transform.columns[2][2]); + spin[11]->set_value_no_signal(p_transform.columns[2][3]); + spin[12]->set_value_no_signal(p_transform.columns[3][0]); + spin[13]->set_value_no_signal(p_transform.columns[3][1]); + spin[14]->set_value_no_signal(p_transform.columns[3][2]); + spin[15]->set_value_no_signal(p_transform.columns[3][3]); } void EditorPropertyProjection::_notification(int p_what) { diff --git a/editor/editor_properties.h b/editor/editor_properties.h index ff9d47627a..b7ae4bd1ca 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -340,7 +340,6 @@ public: class EditorPropertyInteger : public EditorProperty { GDCLASS(EditorPropertyInteger, EditorProperty); EditorSpinSlider *spin = nullptr; - bool setting = false; void _value_changed(int64_t p_val); protected: @@ -399,7 +398,6 @@ public: class EditorPropertyFloat : public EditorProperty { GDCLASS(EditorPropertyFloat, EditorProperty); EditorSpinSlider *spin = nullptr; - bool setting = false; bool radians_as_degrees = false; void _value_changed(double p_val); @@ -418,7 +416,6 @@ class EditorPropertyEasing : public EditorProperty { Control *easing_draw = nullptr; PopupMenu *preset = nullptr; EditorSpinSlider *spin = nullptr; - bool setting = false; bool dragging = false; bool full = false; @@ -459,7 +456,6 @@ public: class EditorPropertyRect2 : public EditorProperty { GDCLASS(EditorPropertyRect2, EditorProperty); EditorSpinSlider *spin[4]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: @@ -476,7 +472,6 @@ public: class EditorPropertyRect2i : public EditorProperty { GDCLASS(EditorPropertyRect2i, EditorProperty); EditorSpinSlider *spin[4]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: @@ -493,7 +488,6 @@ public: class EditorPropertyPlane : public EditorProperty { GDCLASS(EditorPropertyPlane, EditorProperty); EditorSpinSlider *spin[4]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: @@ -511,7 +505,6 @@ class EditorPropertyQuaternion : public EditorProperty { GDCLASS(EditorPropertyQuaternion, EditorProperty); BoxContainer *default_layout = nullptr; EditorSpinSlider *spin[4]; - bool setting = false; Button *warning = nullptr; AcceptDialog *warning_dialog = nullptr; @@ -544,7 +537,6 @@ public: class EditorPropertyAABB : public EditorProperty { GDCLASS(EditorPropertyAABB, EditorProperty); EditorSpinSlider *spin[6]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: @@ -561,7 +553,6 @@ public: class EditorPropertyTransform2D : public EditorProperty { GDCLASS(EditorPropertyTransform2D, EditorProperty); EditorSpinSlider *spin[6]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: @@ -578,7 +569,6 @@ public: class EditorPropertyBasis : public EditorProperty { GDCLASS(EditorPropertyBasis, EditorProperty); EditorSpinSlider *spin[9]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: @@ -595,7 +585,6 @@ public: class EditorPropertyTransform3D : public EditorProperty { GDCLASS(EditorPropertyTransform3D, EditorProperty); EditorSpinSlider *spin[12]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: @@ -613,7 +602,6 @@ public: class EditorPropertyProjection : public EditorProperty { GDCLASS(EditorPropertyProjection, EditorProperty); EditorSpinSlider *spin[16]; - bool setting = false; void _value_changed(double p_val, const String &p_name); protected: diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 1eaeee97a5..7fc870d174 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -400,6 +400,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { const String display_scale_hint_string = vformat("Auto (%d%%),75%%,100%%,125%%,150%%,175%%,200%%,Custom", Math::round(get_auto_display_scale() * 100)); EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/display_scale", 0, display_scale_hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/ui_layout_direction", 0, "Based on Application Locale,Left-to-Right,Right-to-Left,Based on System Locale", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + String ed_screen_hints = "Screen With Mouse Pointer:-4,Screen With Keyboard Focus:-3,Primary Screen:-2"; // Note: Main Window Screen:-1 is not used for the main window. for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) { ed_screen_hints += ",Screen " + itos(i + 1) + ":" + itos(i); @@ -884,6 +886,10 @@ bool EditorSettings::_is_default_text_editor_theme(String p_theme_name) { return p_theme_name == "default" || p_theme_name == "godot 2" || p_theme_name == "custom"; } +const String EditorSettings::_get_project_metadata_path() const { + return EditorPaths::get_singleton()->get_project_settings_dir().path_join("project_metadata.cfg"); +} + // PUBLIC METHODS EditorSettings *EditorSettings::get_singleton() { @@ -1171,24 +1177,31 @@ void EditorSettings::add_property_hint(const PropertyInfo &p_hint) { // Metadata void EditorSettings::set_project_metadata(const String &p_section, const String &p_key, Variant p_data) { - Ref<ConfigFile> cf = memnew(ConfigFile); - String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("project_metadata.cfg"); - Error err; - err = cf->load(path); - ERR_FAIL_COND_MSG(err != OK && err != ERR_FILE_NOT_FOUND, "Cannot load editor settings from file '" + path + "'."); - cf->set_value(p_section, p_key, p_data); - err = cf->save(path); - ERR_FAIL_COND_MSG(err != OK, "Cannot save editor settings to file '" + path + "'."); + const String path = _get_project_metadata_path(); + + if (project_metadata.is_null()) { + project_metadata.instantiate(); + + Error err = project_metadata->load(path); + if (err != OK && err != ERR_FILE_NOT_FOUND) { + ERR_PRINT("Cannot load project metadata from file '" + path + "'."); + } + } + project_metadata->set_value(p_section, p_key, p_data); + + Error err = project_metadata->save(path); + ERR_FAIL_COND_MSG(err != OK, "Cannot save project metadata to file '" + path + "'."); } Variant EditorSettings::get_project_metadata(const String &p_section, const String &p_key, Variant p_default) const { - Ref<ConfigFile> cf = memnew(ConfigFile); - String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("project_metadata.cfg"); - Error err = cf->load(path); - if (err != OK) { - return p_default; + if (project_metadata.is_null()) { + project_metadata.instantiate(); + + const String path = _get_project_metadata_path(); + Error err = project_metadata->load(path); + ERR_FAIL_COND_V_MSG(err != OK && err != ERR_FILE_NOT_FOUND, p_default, "Cannot load project metadata from file '" + path + "'."); } - return cf->get_value(p_section, p_key, p_default); + return project_metadata->get_value(p_section, p_key, p_default); } void EditorSettings::set_favorites(const Vector<String> &p_favorites) { diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 660a9501a2..c3ce790e0e 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -79,6 +79,7 @@ private: HashSet<String> changed_settings; + mutable Ref<ConfigFile> project_metadata; HashMap<String, PropertyInfo> hints; HashMap<String, VariantContainer> props; int last_order; @@ -106,6 +107,7 @@ private: void _load_godot2_text_editor_theme(); bool _save_text_editor_theme(String p_file); bool _is_default_text_editor_theme(String p_theme_name); + const String _get_project_metadata_path() const; protected: static void _bind_methods(); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 96d2abf202..ef68554a3d 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1423,8 +1423,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // Tree theme->set_icon("checked", "Tree", theme->get_icon(SNAME("GuiChecked"), EditorStringName(EditorIcons))); + theme->set_icon("checked_disabled", "Tree", theme->get_icon(SNAME("GuiCheckedDisabled"), EditorStringName(EditorIcons))); theme->set_icon("indeterminate", "Tree", theme->get_icon(SNAME("GuiIndeterminate"), EditorStringName(EditorIcons))); + theme->set_icon("indeterminate_disabled", "Tree", theme->get_icon(SNAME("GuiIndeterminateDisabled"), EditorStringName(EditorIcons))); theme->set_icon("unchecked", "Tree", theme->get_icon(SNAME("GuiUnchecked"), EditorStringName(EditorIcons))); + theme->set_icon("unchecked_disabled", "Tree", theme->get_icon(SNAME("GuiUncheckedDisabled"), EditorStringName(EditorIcons))); theme->set_icon("arrow", "Tree", theme->get_icon(SNAME("GuiTreeArrowDown"), EditorStringName(EditorIcons))); theme->set_icon("arrow_collapsed", "Tree", theme->get_icon(SNAME("GuiTreeArrowRight"), EditorStringName(EditorIcons))); theme->set_icon("arrow_collapsed_mirrored", "Tree", theme->get_icon(SNAME("GuiTreeArrowLeft"), EditorStringName(EditorIcons))); @@ -1437,6 +1440,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("custom_button_font_highlight", "Tree", font_hover_color); theme->set_color("font_color", "Tree", font_color); theme->set_color("font_selected_color", "Tree", mono_color); + theme->set_color("font_disabled_color", "Tree", font_disabled_color); theme->set_color("font_outline_color", "Tree", font_outline_color); theme->set_color("title_button_color", "Tree", font_color); theme->set_color("drop_position_color", "Tree", accent_color); diff --git a/editor/icons/GuiIndeterminateDisabled.svg b/editor/icons/GuiIndeterminateDisabled.svg new file mode 100644 index 0000000000..edaf69f7a1 --- /dev/null +++ b/editor/icons/GuiIndeterminateDisabled.svg @@ -0,0 +1 @@ +<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" rx="2.33" height="14" width="14" fill="#808080"/><path d="M3 7h10v2H3z" fill="#b3b3b3"/></svg> diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index a5813cf192..80dc3f194c 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -1933,7 +1933,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/generate_lods"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/create_shadow_meshes"), true)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Static (VoxelGI/SDFGI),Static Lightmaps (VoxelGI/SDFGI/LightmapGI),Dynamic (VoxelGI only)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Static,Static Lightmaps,Dynamic", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.2)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/force_disable_compression"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 04eda502d2..eb934da4dd 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -2482,7 +2482,10 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT_OVERRIDE("script_text_editor/contextual_help", "macos", KeyModifierMask::ALT | KeyModifierMask::SHIFT | Key::SPACE); ED_SHORTCUT("script_text_editor/toggle_bookmark", TTR("Toggle Bookmark"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::B); + ED_SHORTCUT("script_text_editor/goto_next_bookmark", TTR("Go to Next Bookmark"), KeyModifierMask::CMD_OR_CTRL | Key::B); + ED_SHORTCUT_OVERRIDE("script_text_editor/goto_next_bookmark", "macos", KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::B); + ED_SHORTCUT("script_text_editor/goto_previous_bookmark", TTR("Go to Previous Bookmark"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::B); ED_SHORTCUT("script_text_editor/remove_all_bookmarks", TTR("Remove All Bookmarks"), Key::NONE); diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 7b3847e548..661af16ce8 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -3439,7 +3439,7 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { terrains_tile_list->set_item_metadata(item_index, list_metadata_dict); item_index = terrains_tile_list->add_icon_item(main_vbox_container->get_editor_theme_icon(SNAME("TerrainPath"))); - terrains_tile_list->set_item_tooltip(item_index, TTR("Path mode: paints a terrain, thens connects it to the previous tile painted within the same stroke.")); + terrains_tile_list->set_item_tooltip(item_index, TTR("Path mode: paints a terrain, then connects it to the previous tile painted within the same stroke.")); list_metadata_dict = Dictionary(); list_metadata_dict["type"] = SELECTED_TYPE_PATH; terrains_tile_list->set_item_metadata(item_index, list_metadata_dict); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 291484600c..2073b1f374 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -2845,6 +2845,10 @@ ProjectManager::ProjectManager() { AcceptDialog::set_swap_cancel_ok(swap_cancel_ok == 2); } + int pm_root_dir = EDITOR_GET("interface/editor/ui_layout_direction"); + Control::set_root_layout_direction(pm_root_dir); + Window::set_root_layout_direction(pm_root_dir); + EditorColorMap::create(); EditorTheme::initialize(); Ref<Theme> theme = create_custom_theme(); diff --git a/main/main.cpp b/main/main.cpp index 350b8606b9..0570af3566 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2502,7 +2502,7 @@ Error Main::setup2() { OS::get_singleton()->benchmark_begin_measure("Servers", "Tablet Driver"); GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver", ""); - GLOBAL_DEF_RST_NOVAL(PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.windows", PROPERTY_HINT_ENUM, "wintab,winink"), ""); + GLOBAL_DEF_RST_NOVAL(PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.windows", PROPERTY_HINT_ENUM, "winink,wintab,dummy"), ""); if (tablet_driver.is_empty()) { // specified in project.godot tablet_driver = GLOBAL_GET("input_devices/pen_tablet/driver"); diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml index 338adc9b52..5d35c04e25 100644 --- a/modules/csg/doc_classes/CSGPolygon3D.xml +++ b/modules/csg/doc_classes/CSGPolygon3D.xml @@ -39,7 +39,7 @@ When [member mode] is [constant MODE_PATH], the location of the [Path3D] object used to extrude the [member polygon]. </member> <member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon3D.PathRotation"> - When [member mode] is [constant MODE_PATH], the [enum PathRotation] method used to rotate the [member polygon] as it is extruded. + When [member mode] is [constant MODE_PATH], the path rotation method used to rotate the [member polygon] as it is extruded. </member> <member name="path_simplify_angle" type="float" setter="set_path_simplify_angle" getter="get_path_simplify_angle"> When [member mode] is [constant MODE_PATH], extrusions that are less than this angle, will be merged together to reduce polygon count. diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 55bd0f97d5..5478a46bbc 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3777,6 +3777,60 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } } + // Check non-GDScript scripts. + Ref<Script> script_type = base.script_type; + + if (base_class == nullptr && script_type.is_valid()) { + List<PropertyInfo> property_list; + script_type->get_script_property_list(&property_list); + + for (const PropertyInfo &property_info : property_list) { + if (property_info.name != p_identifier->name) { + continue; + } + + const GDScriptParser::DataType property_type = GDScriptAnalyzer::type_from_property(property_info, false, false); + + p_identifier->set_datatype(property_type); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + return; + } + + MethodInfo method_info = script_type->get_method_info(p_identifier->name); + + if (method_info.name == p_identifier->name) { + p_identifier->set_datatype(make_callable_type(method_info)); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; + return; + } + + List<MethodInfo> signal_list; + script_type->get_script_signal_list(&signal_list); + + for (const MethodInfo &signal_info : signal_list) { + if (signal_info.name != p_identifier->name) { + continue; + } + + const GDScriptParser::DataType signal_type = make_signal_type(signal_info); + + p_identifier->set_datatype(signal_type); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + return; + } + + HashMap<StringName, Variant> constant_map; + script_type->get_constants(&constant_map); + + if (constant_map.has(p_identifier->name)) { + Variant constant = constant_map.get(p_identifier->name); + + p_identifier->set_datatype(make_builtin_meta_type(constant.get_type())); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; + return; + } + } + // Check native members. No need for native class recursion because Node exposes all Object's properties. const StringName &native = base.native_type; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index ea7abc9ded..f3a4f2eaa6 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -73,8 +73,11 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { HashMap<String, String> GDScriptParser::theme_color_names; #endif +HashMap<StringName, GDScriptParser::AnnotationInfo> GDScriptParser::valid_annotations; + void GDScriptParser::cleanup() { builtin_types.clear(); + valid_annotations.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { @@ -89,41 +92,42 @@ bool GDScriptParser::annotation_exists(const String &p_annotation_name) const { GDScriptParser::GDScriptParser() { // Register valid annotations. - // TODO: Should this be static? - register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); - register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); - register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); - - register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); - // Export annotations. - register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); - register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); - register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); - register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); - register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); - register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); - register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true); - register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true); - register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); - register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true); - register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true); - register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); - register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); - register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>); - register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); - register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); - register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); - register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); - // Export grouping annotations. - register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); - register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); - register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray("")); - // Warning annotations. - register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); - // Networking. - register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0)); + if (unlikely(valid_annotations.is_empty())) { + register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); + register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); + register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); + + register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); + // Export annotations. + register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); + register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); + register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); + register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); + register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); + register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); + register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true); + register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true); + register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); + register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true); + register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true); + register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); + register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); + register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); + // Export grouping annotations. + register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); + register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); + register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray("")); + // Warning annotations. + register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); + // Networking. + register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0)); + } #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index f48ad48de0..88b5bdc43f 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1370,7 +1370,7 @@ private: AnnotationAction apply = nullptr; MethodInfo info; }; - HashMap<StringName, AnnotationInfo> valid_annotations; + static HashMap<StringName, AnnotationInfo> valid_annotations; List<AnnotationNode *> annotation_stack; typedef ExpressionNode *(GDScriptParser::*ParseFunction)(ExpressionNode *p_previous_operand, bool p_can_assign); @@ -1470,7 +1470,7 @@ private: SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false); // Annotations AnnotationNode *parse_annotation(uint32_t p_valid_targets); - bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); + static bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); bool validate_annotation_arguments(AnnotationNode *p_annotation); void clear_unused_annotations(); bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index e05979efbc..348ac5194c 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -155,7 +155,6 @@ class GridMap : public Node3D { Ref<PhysicsMaterial> physics_material; bool bake_navigation = false; RID map_override; - uint32_t navigation_layers = 1; Transform3D last_transform; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 36fdda4625..25a5720bc4 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -69,6 +69,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define OPEN_BLOCK_L1 INDENT1 OPEN_BLOCK #define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK +#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK #define CLOSE_BLOCK_L1 INDENT1 CLOSE_BLOCK #define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK #define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK @@ -2591,7 +2592,11 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, // Generate icall function r_output << MEMBER_BEGIN "internal static unsafe " << (ret_void ? "void" : return_type->c_type_out) << " " - << icall_method << "(" << c_func_sig.as_string() << ") " OPEN_BLOCK; + << icall_method << "(" << c_func_sig.as_string() << ")\n" OPEN_BLOCK_L1; + + if (!p_icall.is_static) { + r_output << INDENT2 "ExceptionUtils.ThrowIfNullPtr(" CS_PARAM_INSTANCE ");\n"; + } if (!ret_void && (!p_icall.is_vararg || return_type->cname != name_cache.type_Variant)) { String ptrcall_return_type; @@ -2619,11 +2624,6 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, r_output << ptrcall_return_type << " " C_LOCAL_RET << initialization << ";\n"; } - if (!p_icall.is_static) { - r_output << INDENT2 "if (" CS_PARAM_INSTANCE " == IntPtr.Zero)\n" - << INDENT3 "throw new ArgumentNullException(nameof(" CS_PARAM_INSTANCE "));\n"; - } - String argc_str = itos(p_icall.get_arguments_count()); auto generate_call_and_return_stmts = [&](const char *base_indent) { @@ -2714,7 +2714,7 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, r_output << c_in_statements.as_string(); - r_output << INDENT3 "for (int i = 0; i < vararg_length; i++) " OPEN_BLOCK + r_output << INDENT3 "for (int i = 0; i < vararg_length; i++)\n" OPEN_BLOCK_L3 << INDENT4 "varargs[i] = " << vararg_arg << "[i].NativeVar;\n" << INDENT4 C_LOCAL_PTRCALL_ARGS "[" << real_argc_str << " + i] = new IntPtr(&varargs[i]);\n" << CLOSE_BLOCK_L3; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index dc53e48bd0..537d863ef2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text; #nullable enable @@ -239,5 +240,13 @@ namespace Godot.NativeInterop return variant->Type.ToString(); } + + internal static void ThrowIfNullPtr(IntPtr ptr, [CallerArgumentExpression(nameof(ptr))] string? paramName = null) + { + if (ptr == IntPtr.Zero) + { + throw new ArgumentNullException(paramName); + } + } } } diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 3b875b7fa7..6429513b53 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -372,7 +372,7 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p // Stores the further reachable end polygon, in case our goal is not reachable. if (is_reachable) { - real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination) * navigation_polys[least_cost_id].poly->owner->get_travel_cost(); + real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination); if (reachable_d > d) { reachable_d = d; reachable_end = navigation_polys[least_cost_id].poly; diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 454f8f2ed4..8698c5755a 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -76,7 +76,7 @@ <method name="get_signaling_state" qualifiers="const"> <return type="int" enum="WebRTCPeerConnection.SignalingState" /> <description> - Returns the [enum SignalingState] on the local end of the connection while connecting or reconnecting to another peer. + Returns the signaling state on the local end of the connection while connecting or reconnecting to another peer. </description> </method> <method name="initialize"> diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 77dfff2e5d..0f7003c303 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -4606,6 +4606,22 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } + // Note: Windows Ink API for pen input, available on Windows 8+ only. + // Note: DPI conversion API, available on Windows 8.1+ only. + HMODULE user32_lib = LoadLibraryW(L"user32.dll"); + if (user32_lib) { + win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); + win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); + win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI"); + win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI"); + + winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo; + } + + if (winink_available) { + tablet_drivers.push_back("winink"); + } + // Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); if (wintab_lib) { @@ -4622,21 +4638,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win tablet_drivers.push_back("wintab"); } - // Note: Windows Ink API for pen input, available on Windows 8+ only. - // Note: DPI conversion API, available on Windows 8.1+ only. - HMODULE user32_lib = LoadLibraryW(L"user32.dll"); - if (user32_lib) { - win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); - win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); - win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI"); - win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI"); - - winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo; - } - - if (winink_available) { - tablet_drivers.push_back("winink"); - } + tablet_drivers.push_back("dummy"); if (OS::get_singleton()->is_hidpi_allowed()) { HMODULE Shcore = LoadLibraryW(L"Shcore.dll"); diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 1c193f991e..e04e6d7dce 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -881,7 +881,7 @@ void CPUParticles2D::_particles_process(double p_delta) { force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply tangential acceleration; Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? yx.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); + force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); //apply attractor forces p.velocity += force * local_delta; //orbit velocity diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 187ce90284..ebcd69455f 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -377,7 +377,7 @@ void Light3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_angular_distance", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_param", "get_param", PARAM_SIZE); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "light_negative"), "set_negative", "is_negative"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_specular", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_SPECULAR); - ADD_PROPERTY(PropertyInfo(Variant::INT, "light_bake_mode", PROPERTY_HINT_ENUM, "Disabled,Static (VoxelGI/SDFGI/LightmapGI),Dynamic (VoxelGI/SDFGI only)"), "set_bake_mode", "get_bake_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "light_bake_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_bake_mode", "get_bake_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "light_cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); ADD_GROUP("Shadow", "shadow_"); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 3b1faca17e..33f865382d 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -511,7 +511,7 @@ void GeometryInstance3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_occlusion_culling"), "set_ignore_occlusion_culling", "is_ignoring_occlusion_culling"); ADD_GROUP("Global Illumination", "gi_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static (VoxelGI/SDFGI/LightmapGI),Dynamic (VoxelGI only)"), "set_gi_mode", "get_gi_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Static,Dynamic"), "set_gi_mode", "get_gi_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_lightmap_scale", PROPERTY_HINT_ENUM, String::utf8("1×,2×,4×,8×")), "set_lightmap_scale", "get_lightmap_scale"); ADD_GROUP("Visibility Range", "visibility_range_"); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index ed54bd000c..84ca6bac5b 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -197,6 +197,12 @@ void Control::reparent(Node *p_parent, bool p_keep_global_transform) { // Editor integration. +int Control::root_layout_direction = 0; + +void Control::set_root_layout_direction(int p_root_dir) { + root_layout_direction = p_root_dir; +} + void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { ERR_READ_THREAD_GUARD; Node::get_argument_options(p_function, p_idx, r_options); @@ -3024,10 +3030,35 @@ bool Control::is_layout_rtl() const { if (data.is_rtl_dirty) { const_cast<Control *>(this)->data.is_rtl_dirty = false; if (data.layout_dir == LAYOUT_DIRECTION_INHERITED) { +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene() && GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + const_cast<Control *>(this)->data.is_rtl = true; + return data.is_rtl; + } + if (is_inside_tree()) { + Node *edited_scene_root = get_tree()->get_edited_scene_root(); + if (edited_scene_root == this) { + int proj_root_layout_direction = GLOBAL_GET(SNAME("internationalization/rendering/root_node_layout_direction")); + if (proj_root_layout_direction == 1) { + const_cast<Control *>(this)->data.is_rtl = false; + } else if (proj_root_layout_direction == 2) { + const_cast<Control *>(this)->data.is_rtl = true; + } else if (proj_root_layout_direction == 3) { + String locale = OS::get_singleton()->get_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + } + return data.is_rtl; + } + } +#else if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { const_cast<Control *>(this)->data.is_rtl = true; return data.is_rtl; } +#endif Node *parent_node = get_parent(); while (parent_node) { Control *parent_control = Object::cast_to<Control>(parent_node); @@ -3044,11 +3075,13 @@ bool Control::is_layout_rtl() const { parent_node = parent_node->get_parent(); } - int root_dir = GLOBAL_GET(SNAME("internationalization/rendering/root_node_layout_direction")); - if (root_dir == 1) { + if (root_layout_direction == 1) { const_cast<Control *>(this)->data.is_rtl = false; - } else if (root_dir == 2) { + } else if (root_layout_direction == 2) { const_cast<Control *>(this)->data.is_rtl = true; + } else if (root_layout_direction == 3) { + String locale = OS::get_singleton()->get_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); } else { String locale = TranslationServer::get_singleton()->get_tool_locale(); const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); diff --git a/scene/gui/control.h b/scene/gui/control.h index db1bd3a346..aec61dc2fe 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -317,6 +317,8 @@ private: // Extra properties. + static int root_layout_direction; + String get_tooltip_text() const; protected: @@ -403,6 +405,8 @@ public: // Editor integration. + static void set_root_layout_direction(int p_root_dir); + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; PackedStringArray get_configuration_warnings() const override; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 957a8f276e..3e827e76dc 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -42,6 +42,7 @@ void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) { if (close_on_escape && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { _cancel_pressed(); } + Window::_input_from_window(p_event); } void AcceptDialog::_parent_focused() { @@ -428,8 +429,6 @@ AcceptDialog::AcceptDialog() { ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed)); set_title(TTRC("Alert!")); - - connect("window_input", callable_mp(this, &AcceptDialog::_input_from_window)); } AcceptDialog::~AcceptDialog() { diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index d5cbaaeef8..e28d6b7467 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -65,11 +65,11 @@ class AcceptDialog : public Window { static bool swap_cancel_ok; - void _input_from_window(const Ref<InputEvent> &p_event); void _parent_focused(); protected: virtual Size2 _get_contents_minimum_size() const override; + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index b16e8371a2..8369bedda9 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -39,6 +39,7 @@ void Popup::_input_from_window(const Ref<InputEvent> &p_event) { if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { _close_pressed(); } + Window::_input_from_window(p_event); } void Popup::_initialize_visible_parents() { @@ -204,8 +205,6 @@ Popup::Popup() { set_flag(FLAG_BORDERLESS, true); set_flag(FLAG_RESIZE_DISABLED, true); set_flag(FLAG_POPUP, true); - - connect("window_input", callable_mp(this, &Popup::_input_from_window)); } Popup::~Popup() { diff --git a/scene/gui/popup.h b/scene/gui/popup.h index d524e448dd..25edca3657 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -47,14 +47,13 @@ class Popup : public Window { Ref<StyleBox> panel_style; } theme_cache; - void _input_from_window(const Ref<InputEvent> &p_event); - void _initialize_visible_parents(); void _deinitialize_visible_parents(); protected: void _close_pressed(); virtual Rect2i _popup_adjust_rect() const override; + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 605a3fe397..d9c633b238 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -414,9 +414,16 @@ void PopupMenu::_submenu_timeout() { submenu_over = -1; } -void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { - ERR_FAIL_COND(p_event.is_null()); +void PopupMenu::_input_from_window(const Ref<InputEvent> &p_event) { + if (p_event.is_valid()) { + _input_from_window_internal(p_event); + } else { + WARN_PRINT_ONCE("PopupMenu has received an invalid InputEvent. Consider filtering invalid events out."); + } + Popup::_input_from_window(p_event); +} +void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { if (!items.is_empty()) { Input *input = Input::get_singleton(); Ref<InputEventJoypadMotion> joypadmotion_event = p_event; @@ -2849,8 +2856,6 @@ PopupMenu::PopupMenu() { scroll_container->add_child(control, false, INTERNAL_MODE_FRONT); control->connect("draw", callable_mp(this, &PopupMenu::_draw_items)); - connect("window_input", callable_mp(this, &PopupMenu::gui_input)); - submenu_timer = memnew(Timer); submenu_timer->set_wait_time(0.3); submenu_timer->set_one_shot(true); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 4703686c51..c1ab9544ea 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -115,7 +115,6 @@ class PopupMenu : public Popup { void _shape_item(int p_idx); - virtual void gui_input(const Ref<InputEvent> &p_event); void _activate_submenu(int p_over, bool p_by_keyboard = false); void _submenu_timeout(); @@ -194,10 +193,12 @@ class PopupMenu : public Popup { void _minimum_lifetime_timeout(); void _close_pressed(); void _menu_changed(); + void _input_from_window_internal(const Ref<InputEvent> &p_event); protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 70c2cc9d65..248260a7d2 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -74,6 +74,7 @@ void TextureProgressBar::set_nine_patch_stretch(bool p_stretch) { nine_patch_stretch = p_stretch; queue_redraw(); update_minimum_size(); + notify_property_list_changed(); } bool TextureProgressBar::get_nine_patch_stretch() const { @@ -615,6 +616,7 @@ void TextureProgressBar::set_fill_mode(int p_fill) { mode = (FillMode)p_fill; queue_redraw(); + notify_property_list_changed(); } int TextureProgressBar::get_fill_mode() { @@ -669,6 +671,16 @@ Point2 TextureProgressBar::get_radial_center_offset() { return rad_center_off; } +void TextureProgressBar::_validate_property(PropertyInfo &p_property) const { + if (p_property.name.begins_with("stretch_margin_") && !nine_patch_stretch) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } + + if (p_property.name.begins_with("radial_") && (mode != FillMode::FILL_CLOCKWISE && mode != FillMode::FILL_COUNTER_CLOCKWISE && mode != FillMode::FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE)) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + void TextureProgressBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_under_texture", "tex"), &TextureProgressBar::set_under_texture); ClassDB::bind_method(D_METHOD("get_under_texture"), &TextureProgressBar::get_under_texture); @@ -710,8 +722,13 @@ void TextureProgressBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_nine_patch_stretch"), &TextureProgressBar::get_nine_patch_stretch); ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "nine_patch_stretch"), "set_nine_patch_stretch", "get_nine_patch_stretch"); + ADD_GROUP("Radial Fill", "radial_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,degrees"), "set_radial_initial_angle", "get_radial_initial_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,degrees"), "set_fill_degrees", "get_fill_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "radial_center_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_radial_center_offset", "get_radial_center_offset"); + ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "nine_patch_stretch"), "set_nine_patch_stretch", "get_nine_patch_stretch"); ADD_GROUP("Stretch Margin", "stretch_margin_"); ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_stretch_margin", "get_stretch_margin", SIDE_LEFT); ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_stretch_margin", "get_stretch_margin", SIDE_TOP); @@ -729,11 +746,6 @@ void TextureProgressBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over"), "set_tint_over", "get_tint_over"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_progress"), "set_tint_progress", "get_tint_progress"); - ADD_GROUP("Radial Fill", "radial_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_radial_initial_angle", "get_radial_initial_angle"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_fill_degrees", "get_fill_degrees"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "radial_center_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_radial_center_offset", "get_radial_center_offset"); - BIND_ENUM_CONSTANT(FILL_LEFT_TO_RIGHT); BIND_ENUM_CONSTANT(FILL_RIGHT_TO_LEFT); BIND_ENUM_CONSTANT(FILL_TOP_TO_BOTTOM); diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h index 5999aa986b..d890ce6e36 100644 --- a/scene/gui/texture_progress_bar.h +++ b/scene/gui/texture_progress_bar.h @@ -43,6 +43,7 @@ class TextureProgressBar : public Range { protected: static void _bind_methods(); void _notification(int p_what); + void _validate_property(PropertyInfo &p_property) const; public: enum FillMode { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 8a243fd68f..18ee39aeec 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2195,27 +2195,38 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color); } break; case TreeItem::CELL_MODE_CHECK: { - Ref<Texture2D> checked = theme_cache.checked; - Ref<Texture2D> unchecked = theme_cache.unchecked; - Ref<Texture2D> indeterminate = theme_cache.indeterminate; Point2i check_ofs = item_rect.position; - check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2); + check_ofs.y += Math::floor((real_t)(item_rect.size.y - theme_cache.checked->get_height()) / 2); - if (p_item->cells[i].indeterminate) { - indeterminate->draw(ci, check_ofs); - } else if (p_item->cells[i].checked) { - checked->draw(ci, check_ofs); + if (p_item->cells[i].editable) { + if (p_item->cells[i].indeterminate) { + theme_cache.indeterminate->draw(ci, check_ofs); + } else if (p_item->cells[i].checked) { + theme_cache.checked->draw(ci, check_ofs); + } else { + theme_cache.unchecked->draw(ci, check_ofs); + } } else { - unchecked->draw(ci, check_ofs); + if (p_item->cells[i].indeterminate) { + theme_cache.indeterminate_disabled->draw(ci, check_ofs); + } else if (p_item->cells[i].checked) { + theme_cache.checked_disabled->draw(ci, check_ofs); + } else { + theme_cache.unchecked_disabled->draw(ci, check_ofs); + } } - int check_w = checked->get_width() + theme_cache.h_separation; + int check_w = theme_cache.checked->get_width() + theme_cache.h_separation; text_pos.x += check_w; item_rect.size.x -= check_w; item_rect.position.x += check_w; + if (!p_item->cells[i].editable) { + cell_color = theme_cache.font_disabled_color; + } + draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color); } break; @@ -5535,7 +5546,10 @@ void Tree::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, checked); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, unchecked); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, checked_disabled); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, unchecked_disabled); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, indeterminate); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, indeterminate_disabled); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, arrow); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, arrow_collapsed); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, arrow_collapsed_mirrored); @@ -5549,6 +5563,7 @@ void Tree::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_selected_color); + BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_disabled_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, drop_position_color); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, h_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Tree, v_separation); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 4a5814c45c..2dda408dd7 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -536,7 +536,10 @@ private: Ref<Texture2D> checked; Ref<Texture2D> unchecked; + Ref<Texture2D> checked_disabled; + Ref<Texture2D> unchecked_disabled; Ref<Texture2D> indeterminate; + Ref<Texture2D> indeterminate_disabled; Ref<Texture2D> arrow; Ref<Texture2D> arrow_collapsed; Ref<Texture2D> arrow_collapsed_mirrored; @@ -545,6 +548,7 @@ private: Color font_color; Color font_selected_color; + Color font_disabled_color; Color guide_color; Color drop_position_color; Color relationship_line_color; diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp index c61fa1d9b8..438a228c4f 100644 --- a/scene/gui/view_panner.cpp +++ b/scene/gui/view_panner.cpp @@ -43,12 +43,14 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) if (scroll_vec != Vector2() && mb->is_pressed()) { if (control_scheme == SCROLL_PANS) { if (mb->is_ctrl_pressed()) { - // Compute the zoom factor. - float zoom_factor = mb->get_factor() <= 0 ? 1.0 : mb->get_factor(); - zoom_factor = ((scroll_zoom_factor - 1.0) * zoom_factor) + 1.0; - float zoom = (scroll_vec.x + scroll_vec.y) > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor; - zoom_callback.call(zoom, mb->get_position(), p_event); - return true; + if (scroll_vec.y != 0) { + // Compute the zoom factor. + float zoom_factor = mb->get_factor() <= 0 ? 1.0 : mb->get_factor(); + zoom_factor = ((scroll_zoom_factor - 1.0) * zoom_factor) + 1.0; + float zoom = scroll_vec.y > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor; + zoom_callback.call(zoom, mb->get_position(), p_event); + return true; + } } else { Vector2 panning = scroll_vec * mb->get_factor(); if (pan_axis == PAN_AXIS_HORIZONTAL) { @@ -73,11 +75,11 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) } pan_callback.call(-panning * scroll_speed, p_event); return true; - } else if (!mb->is_shift_pressed()) { + } else if (!mb->is_shift_pressed() && scroll_vec.y != 0) { // Compute the zoom factor. float zoom_factor = mb->get_factor() <= 0 ? 1.0 : mb->get_factor(); zoom_factor = ((scroll_zoom_factor - 1.0) * zoom_factor) + 1.0; - float zoom = (scroll_vec.x + scroll_vec.y) > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor; + float zoom = scroll_vec.y > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor; zoom_callback.call(zoom, mb->get_position(), p_event); return true; } diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 36d7d079b2..eb431445ed 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -40,6 +40,14 @@ #include "scene/theme/theme_db.h" #include "scene/theme/theme_owner.h" +// Editor integration. + +int Window::root_layout_direction = 0; + +void Window::set_root_layout_direction(int p_root_dir) { + root_layout_direction = p_root_dir; +} + // Dynamic properties. bool Window::_set(const StringName &p_name, const Variant &p_value) { @@ -1559,7 +1567,12 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) { } } - if (p_ev->get_device() != InputEvent::DEVICE_ID_INTERNAL) { + // If the event needs to be handled in a Window-derived class, then it should overwrite + // `_input_from_window` instead of subscribing to the `window_input` signal, because the signal + // filters out internal events. + _input_from_window(p_ev); + + if (p_ev->get_device() != InputEvent::DEVICE_ID_INTERNAL && is_inside_tree()) { emit_signal(SceneStringNames::get_singleton()->window_input, p_ev); } @@ -2533,9 +2546,32 @@ Window::LayoutDirection Window::get_layout_direction() const { bool Window::is_layout_rtl() const { ERR_READ_THREAD_GUARD_V(false); if (layout_dir == LAYOUT_DIRECTION_INHERITED) { +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene() && GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + return true; + } + if (is_inside_tree()) { + Node *edited_scene_root = get_tree()->get_edited_scene_root(); + if (edited_scene_root == this) { + int proj_root_layout_direction = GLOBAL_GET(SNAME("internationalization/rendering/root_node_layout_direction")); + if (proj_root_layout_direction == 1) { + return false; + } else if (proj_root_layout_direction == 2) { + return true; + } else if (proj_root_layout_direction == 3) { + String locale = OS::get_singleton()->get_locale(); + return TS->is_locale_right_to_left(locale); + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + return TS->is_locale_right_to_left(locale); + } + } + } +#else if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { return true; } +#endif Node *parent_node = get_parent(); while (parent_node) { Control *parent_control = Object::cast_to<Control>(parent_node); @@ -2550,11 +2586,13 @@ bool Window::is_layout_rtl() const { parent_node = parent_node->get_parent(); } - int root_dir = GLOBAL_GET(SNAME("internationalization/rendering/root_node_layout_direction")); - if (root_dir == 1) { + if (root_layout_direction == 1) { return false; - } else if (root_dir == 2) { + } else if (root_layout_direction == 2) { return true; + } else if (root_layout_direction == 3) { + String locale = OS::get_singleton()->get_locale(); + return TS->is_locale_right_to_left(locale); } else { String locale = TranslationServer::get_singleton()->get_tool_locale(); return TS->is_locale_right_to_left(locale); diff --git a/scene/main/window.h b/scene/main/window.h index 8a54b6c7d3..0682abc3c7 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -234,11 +234,14 @@ private: Ref<Shortcut> debugger_stop_shortcut; + static int root_layout_direction; + protected: virtual Rect2i _popup_adjust_rect() const { return Rect2i(); } virtual void _post_popup() {} virtual void _update_theme_item_cache(); + virtual void _input_from_window(const Ref<InputEvent> &p_event) {} void _notification(int p_what); static void _bind_methods(); @@ -260,6 +263,8 @@ public: NOTIFICATION_THEME_CHANGED = 32 }; + static void set_root_layout_direction(int p_root_dir); + void set_title(const String &p_title); String get_title() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 2e6759063b..baaa78f9f2 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -456,6 +456,10 @@ void register_scene_types() { AcceptDialog::set_swap_cancel_ok(swap_cancel_ok); #endif + int root_dir = GLOBAL_GET("internationalization/rendering/root_node_layout_direction"); + Control::set_root_layout_direction(root_dir); + Window::set_root_layout_direction(root_dir); + /* REGISTER ANIMATION */ GDREGISTER_CLASS(Tween); GDREGISTER_ABSTRACT_CLASS(Tweener); diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index ddec5e826b..09fb5aba0d 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -773,8 +773,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("custom_button_hover", "Tree", button_hover); theme->set_icon("checked", "Tree", icons["checked"]); + theme->set_icon("checked_disabled", "Tree", icons["checked_disabled"]); theme->set_icon("unchecked", "Tree", icons["unchecked"]); + theme->set_icon("unchecked_disabled", "Tree", icons["unchecked_disabled"]); theme->set_icon("indeterminate", "Tree", icons["indeterminate"]); + theme->set_icon("indeterminate_disabled", "Tree", icons["indeterminate_disabled"]); theme->set_icon("updown", "Tree", icons["updown"]); theme->set_icon("select_arrow", "Tree", icons["option_button_arrow"]); theme->set_icon("arrow", "Tree", icons["arrow_down"]); @@ -789,6 +792,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("title_button_color", "Tree", control_font_color); theme->set_color("font_color", "Tree", control_font_low_color); theme->set_color("font_selected_color", "Tree", control_font_pressed_color); + theme->set_color("font_disabled_color", "Tree", control_font_disabled_color); theme->set_color("font_outline_color", "Tree", Color(1, 1, 1)); theme->set_color("guide_color", "Tree", Color(0.7, 0.7, 0.7, 0.25)); theme->set_color("drop_position_color", "Tree", Color(1, 1, 1)); diff --git a/scene/theme/icons/indeterminate.svg b/scene/theme/icons/indeterminate.svg index 2a742e1475..e6e8dec5ed 100644 --- a/scene/theme/icons/indeterminate.svg +++ b/scene/theme/icons/indeterminate.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 15.999999" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.3333333 1c-1.2887 0-2.3333333 1.0446683-2.3333333 2.3333333v9.3333337c0 1.2887 1.0446683 2.333333 2.3333333 2.333333h9.3333337c1.2887 0 2.333333-1.044668 2.333333-2.333333v-9.3333337c0-1.2887-1.044668-2.3333333-2.333333-2.3333333z" fill="#fff" fill-opacity=".75" stroke-width="1.16667"/><path d="m3 7h10v2h-10z" fill="#1a1a1a" stroke-linecap="square" stroke-opacity=".75" stroke-width="2"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="14" height="14" rx="2.333" fill="#fff" fill-opacity=".75"/><path d="m3 7h10v2h-10z" fill="#1a1a1a"/></svg> diff --git a/scene/theme/icons/indeterminate_disabled.svg b/scene/theme/icons/indeterminate_disabled.svg new file mode 100644 index 0000000000..f238f6f31c --- /dev/null +++ b/scene/theme/icons/indeterminate_disabled.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="14" height="14" rx="2.333" fill="#fff" fill-opacity=".37"/><path d="m3 7h10v2h-10z" fill="#1a1a1a" fill-opacity=".5"/></svg> diff --git a/tests/scene/test_camera_3d.h b/tests/scene/test_camera_3d.h new file mode 100644 index 0000000000..169486d108 --- /dev/null +++ b/tests/scene/test_camera_3d.h @@ -0,0 +1,370 @@ +/**************************************************************************/ +/* test_camera_3d.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 TEST_CAMERA_3D_H +#define TEST_CAMERA_3D_H + +#include "scene/3d/camera_3d.h" +#include "scene/main/viewport.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +// Constants. +#define SQRT3 (1.7320508f) + +TEST_CASE("[SceneTree][Camera3D] Getters and setters") { + Camera3D *test_camera = memnew(Camera3D); + + SUBCASE("Cull mask") { + constexpr int cull_mask = (1 << 5) | (1 << 7) | (1 << 9); + constexpr int set_enable_layer = 3; + constexpr int set_disable_layer = 5; + test_camera->set_cull_mask(cull_mask); + CHECK(test_camera->get_cull_mask() == cull_mask); + test_camera->set_cull_mask_value(set_enable_layer, true); + CHECK(test_camera->get_cull_mask_value(set_enable_layer)); + test_camera->set_cull_mask_value(set_disable_layer, false); + CHECK_FALSE(test_camera->get_cull_mask_value(set_disable_layer)); + } + + SUBCASE("Attributes") { + Ref<CameraAttributes> attributes = memnew(CameraAttributes); + test_camera->set_attributes(attributes); + CHECK(test_camera->get_attributes() == attributes); + Ref<CameraAttributesPhysical> physical_attributes = memnew(CameraAttributesPhysical); + test_camera->set_attributes(physical_attributes); + CHECK(test_camera->get_attributes() == physical_attributes); + } + + SUBCASE("Camera frustum properties") { + constexpr float near = 0.2f; + constexpr float far = 995.0f; + constexpr float fov = 120.0f; + constexpr float size = 7.0f; + constexpr float h_offset = 1.1f; + constexpr float v_offset = -1.6f; + const Vector2 frustum_offset(5, 7); + test_camera->set_near(near); + CHECK(test_camera->get_near() == near); + test_camera->set_far(far); + CHECK(test_camera->get_far() == far); + test_camera->set_fov(fov); + CHECK(test_camera->get_fov() == fov); + test_camera->set_size(size); + CHECK(test_camera->get_size() == size); + test_camera->set_h_offset(h_offset); + CHECK(test_camera->get_h_offset() == h_offset); + test_camera->set_v_offset(v_offset); + CHECK(test_camera->get_v_offset() == v_offset); + test_camera->set_frustum_offset(frustum_offset); + CHECK(test_camera->get_frustum_offset() == frustum_offset); + test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_HEIGHT); + CHECK(test_camera->get_keep_aspect_mode() == Camera3D::KeepAspect::KEEP_HEIGHT); + test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_WIDTH); + CHECK(test_camera->get_keep_aspect_mode() == Camera3D::KeepAspect::KEEP_WIDTH); + } + + SUBCASE("Projection mode") { + test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_ORTHOGONAL); + CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_ORTHOGONAL); + test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); + CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); + } + + SUBCASE("Helper setters") { + constexpr float fov = 90.0f, size = 6.0f; + constexpr float near1 = 0.1f, near2 = 0.5f; + constexpr float far1 = 1001.0f, far2 = 1005.0f; + test_camera->set_perspective(fov, near1, far1); + CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); + CHECK(test_camera->get_near() == near1); + CHECK(test_camera->get_far() == far1); + CHECK(test_camera->get_fov() == fov); + test_camera->set_orthogonal(size, near2, far2); + CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_ORTHOGONAL); + CHECK(test_camera->get_near() == near2); + CHECK(test_camera->get_far() == far2); + CHECK(test_camera->get_size() == size); + } + + SUBCASE("Doppler tracking") { + test_camera->set_doppler_tracking(Camera3D::DopplerTracking::DOPPLER_TRACKING_IDLE_STEP); + CHECK(test_camera->get_doppler_tracking() == Camera3D::DopplerTracking::DOPPLER_TRACKING_IDLE_STEP); + test_camera->set_doppler_tracking(Camera3D::DopplerTracking::DOPPLER_TRACKING_PHYSICS_STEP); + CHECK(test_camera->get_doppler_tracking() == Camera3D::DopplerTracking::DOPPLER_TRACKING_PHYSICS_STEP); + test_camera->set_doppler_tracking(Camera3D::DopplerTracking::DOPPLER_TRACKING_DISABLED); + CHECK(test_camera->get_doppler_tracking() == Camera3D::DopplerTracking::DOPPLER_TRACKING_DISABLED); + } + + memdelete(test_camera); +} + +TEST_CASE("[SceneTree][Camera3D] Position queries") { + // Cameras need a viewport to know how to compute their frustums, so we make a fake one here. + Camera3D *test_camera = memnew(Camera3D); + SubViewport *mock_viewport = memnew(SubViewport); + // 4:2. + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_WIDTH); + REQUIRE_MESSAGE(test_camera->is_current(), "Camera3D should be made current upon entering tree."); + + SUBCASE("Orthogonal projection") { + test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_ORTHOGONAL); + // The orthogonal case is simpler, so we test a more random position + rotation combination here. + // For the other cases we'll use zero translation and rotation instead. + test_camera->set_global_position(Vector3(1, 2, 3)); + test_camera->look_at(Vector3(-4, 5, 1)); + // Width = 5, Aspect Ratio = 400 / 200 = 2, so Height is 2.5. + test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f); + const Basis basis = test_camera->get_global_basis(); + // Subtract near so offset starts from the near plane. + const Vector3 offset1 = basis.xform(Vector3(-1.5f, 3.5f, 0.2f - test_camera->get_near())); + const Vector3 offset2 = basis.xform(Vector3(2.0f, -0.5f, -0.6f - test_camera->get_near())); + const Vector3 offset3 = basis.xform(Vector3(-3.0f, 1.0f, -0.6f - test_camera->get_near())); + const Vector3 offset4 = basis.xform(Vector3(-2.0f, 1.5f, -0.6f - test_camera->get_near())); + const Vector3 offset5 = basis.xform(Vector3(0, 0, 10000.0f - test_camera->get_near())); + + SUBCASE("is_position_behind") { + CHECK(test_camera->is_position_behind(test_camera->get_global_position() + offset1)); + CHECK_FALSE(test_camera->is_position_behind(test_camera->get_global_position() + offset2)); + + SUBCASE("h/v offset should have no effect on the result of is_position_behind") { + test_camera->set_h_offset(-11.0f); + test_camera->set_v_offset(22.1f); + CHECK(test_camera->is_position_behind(test_camera->get_global_position() + offset1)); + test_camera->set_h_offset(4.7f); + test_camera->set_v_offset(-3.0f); + CHECK_FALSE(test_camera->is_position_behind(test_camera->get_global_position() + offset2)); + } + // Reset h/v offsets. + test_camera->set_h_offset(0); + test_camera->set_v_offset(0); + } + + SUBCASE("is_position_in_frustum") { + // If the point is behind the near plane, it is outside the camera frustum. + // So offset1 is not in frustum. + CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset1)); + // If |right| > 5 / 2 or |up| > 2.5 / 2, the point is outside the camera frustum. + // So offset2 is in frustum and offset3 and offset4 are not. + CHECK(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset2)); + CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset3)); + CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset4)); + // offset5 is beyond the far plane, so it is not in frustum. + CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset5)); + } + } + + SUBCASE("Perspective projection") { + test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); + // Camera at origin, looking at +Z. + test_camera->set_global_position(Vector3(0, 0, 0)); + test_camera->set_global_rotation(Vector3(0, 0, 0)); + // Keep width, so horizontal fov = 120. + // Since the near plane distance is 1, + // with trig we know the near plane's width is 2 * sqrt(3), so its height is sqrt(3). + test_camera->set_perspective(120.0f, 1.0f, 1000.0f); + + SUBCASE("is_position_behind") { + CHECK_FALSE(test_camera->is_position_behind(Vector3(0, 0, -1.5f))); + CHECK(test_camera->is_position_behind(Vector3(2, 0, -0.2f))); + } + + SUBCASE("is_position_in_frustum") { + CHECK(test_camera->is_position_in_frustum(Vector3(-1.3f, 0, -1.1f))); + CHECK_FALSE(test_camera->is_position_in_frustum(Vector3(2, 0, -1.1f))); + CHECK(test_camera->is_position_in_frustum(Vector3(1, 0.5f, -1.1f))); + CHECK_FALSE(test_camera->is_position_in_frustum(Vector3(1, 1, -1.1f))); + CHECK(test_camera->is_position_in_frustum(Vector3(0, 0, -1.5f))); + CHECK_FALSE(test_camera->is_position_in_frustum(Vector3(0, 0, -0.5f))); + } + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +TEST_CASE("[SceneTree][Camera3D] Project/Unproject position") { + // Cameras need a viewport to know how to compute their frustums, so we make a fake one here. + Camera3D *test_camera = memnew(Camera3D); + SubViewport *mock_viewport = memnew(SubViewport); + // 4:2. + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + test_camera->set_global_position(Vector3(0, 0, 0)); + test_camera->set_global_rotation(Vector3(0, 0, 0)); + test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_HEIGHT); + + SUBCASE("project_position") { + SUBCASE("Orthogonal projection") { + test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_position(Vector2(200, 100), 0.5f).is_equal_approx(Vector3(0, 0, -0.5f))); + // Top left. + CHECK(test_camera->project_position(Vector2(0, 0), 1.5f).is_equal_approx(Vector3(-5.0f, 2.5f, -1.5f))); + // Bottom right. + CHECK(test_camera->project_position(Vector2(400, 200), 5.0f).is_equal_approx(Vector3(5.0f, -2.5f, -5.0f))); + } + + SUBCASE("Perspective projection") { + test_camera->set_perspective(120.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_position(Vector2(200, 100), 0.5f).is_equal_approx(Vector3(0, 0, -0.5f))); + CHECK(test_camera->project_position(Vector2(200, 100), 100.0f).is_equal_approx(Vector3(0, 0, -100.0f))); + // 3/4th way to Top left. + CHECK(test_camera->project_position(Vector2(100, 50), 0.5f).is_equal_approx(Vector3(-SQRT3 * 0.5f, SQRT3 * 0.25f, -0.5f))); + CHECK(test_camera->project_position(Vector2(100, 50), 1.0f).is_equal_approx(Vector3(-SQRT3, SQRT3 * 0.5f, -1.0f))); + // 3/4th way to Bottom right. + CHECK(test_camera->project_position(Vector2(300, 150), 0.5f).is_equal_approx(Vector3(SQRT3 * 0.5f, -SQRT3 * 0.25f, -0.5f))); + CHECK(test_camera->project_position(Vector2(300, 150), 1.0f).is_equal_approx(Vector3(SQRT3, -SQRT3 * 0.5f, -1.0f))); + } + } + + // Uses cases that are the inverse of the above sub-case. + SUBCASE("unproject_position") { + SUBCASE("Orthogonal projection") { + test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f); + // Center + CHECK(test_camera->unproject_position(Vector3(0, 0, -0.5f)).is_equal_approx(Vector2(200, 100))); + // Top left + CHECK(test_camera->unproject_position(Vector3(-5.0f, 2.5f, -1.5f)).is_equal_approx(Vector2(0, 0))); + // Bottom right + CHECK(test_camera->unproject_position(Vector3(5.0f, -2.5f, -5.0f)).is_equal_approx(Vector2(400, 200))); + } + + SUBCASE("Perspective projection") { + test_camera->set_perspective(120.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->unproject_position(Vector3(0, 0, -0.5f)).is_equal_approx(Vector2(200, 100))); + CHECK(test_camera->unproject_position(Vector3(0, 0, -100.0f)).is_equal_approx(Vector2(200, 100))); + // 3/4th way to Top left. + WARN(test_camera->unproject_position(Vector3(-SQRT3 * 0.5f, SQRT3 * 0.25f, -0.5f)).is_equal_approx(Vector2(100, 50))); + WARN(test_camera->unproject_position(Vector3(-SQRT3, SQRT3 * 0.5f, -1.0f)).is_equal_approx(Vector2(100, 50))); + // 3/4th way to Bottom right. + CHECK(test_camera->unproject_position(Vector3(SQRT3 * 0.5f, -SQRT3 * 0.25f, -0.5f)).is_equal_approx(Vector2(300, 150))); + CHECK(test_camera->unproject_position(Vector3(SQRT3, -SQRT3 * 0.5f, -1.0f)).is_equal_approx(Vector2(300, 150))); + } + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +TEST_CASE("[SceneTree][Camera3D] Project ray") { + // Cameras need a viewport to know how to compute their frustums, so we make a fake one here. + Camera3D *test_camera = memnew(Camera3D); + SubViewport *mock_viewport = memnew(SubViewport); + // 4:2. + mock_viewport->set_size(Vector2(400, 200)); + SceneTree::get_singleton()->get_root()->add_child(mock_viewport); + mock_viewport->add_child(test_camera); + test_camera->set_global_position(Vector3(0, 0, 0)); + test_camera->set_global_rotation(Vector3(0, 0, 0)); + test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_HEIGHT); + + SUBCASE("project_ray_origin") { + SUBCASE("Orthogonal projection") { + test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_ray_origin(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -0.5f))); + // Top left. + CHECK(test_camera->project_ray_origin(Vector2(0, 0)).is_equal_approx(Vector3(-5.0f, 2.5f, -0.5f))); + // Bottom right. + CHECK(test_camera->project_ray_origin(Vector2(400, 200)).is_equal_approx(Vector3(5.0f, -2.5f, -0.5f))); + } + + SUBCASE("Perspective projection") { + test_camera->set_perspective(120.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_ray_origin(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, 0))); + // Top left. + CHECK(test_camera->project_ray_origin(Vector2(0, 0)).is_equal_approx(Vector3(0, 0, 0))); + // Bottom right. + CHECK(test_camera->project_ray_origin(Vector2(400, 200)).is_equal_approx(Vector3(0, 0, 0))); + } + } + + SUBCASE("project_ray_normal") { + SUBCASE("Orthogonal projection") { + test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1))); + // Top left. + CHECK(test_camera->project_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(0, 0, -1))); + // Bottom right. + CHECK(test_camera->project_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(0, 0, -1))); + } + + SUBCASE("Perspective projection") { + test_camera->set_perspective(120.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1))); + // Top left. + CHECK(test_camera->project_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(-SQRT3, SQRT3 / 2, -0.5f).normalized())); + // Bottom right. + CHECK(test_camera->project_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(SQRT3, -SQRT3 / 2, -0.5f).normalized())); + } + } + + SUBCASE("project_local_ray_normal") { + test_camera->set_rotation_degrees(Vector3(60, 60, 60)); + + SUBCASE("Orthogonal projection") { + test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_local_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1))); + // Top left. + CHECK(test_camera->project_local_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(0, 0, -1))); + // Bottom right. + CHECK(test_camera->project_local_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(0, 0, -1))); + } + + SUBCASE("Perspective projection") { + test_camera->set_perspective(120.0f, 0.5f, 1000.0f); + // Center. + CHECK(test_camera->project_local_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1))); + // Top left. + CHECK(test_camera->project_local_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(-SQRT3, SQRT3 / 2, -0.5f).normalized())); + // Bottom right. + CHECK(test_camera->project_local_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(SQRT3, -SQRT3 / 2, -0.5f).normalized())); + } + } + + memdelete(test_camera); + memdelete(mock_viewport); +} + +#undef SQRT3 + +#endif // TEST_CAMERA_3D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 8c120f6d3a..5187ebd00f 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -94,6 +94,7 @@ #include "tests/scene/test_arraymesh.h" #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" +#include "tests/scene/test_camera_3d.h" #include "tests/scene/test_code_edit.h" #include "tests/scene/test_color_picker.h" #include "tests/scene/test_control.h" |