diff options
127 files changed, 5860 insertions, 1355 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 74c06123e1..6275502378 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -41,7 +41,10 @@ #include "core/os/keyboard.h" #include "core/variant/variant_parser.h" #include "core/version.h" + +#ifdef TOOLS_ENABLED #include "modules/modules_enabled.gen.h" // For mono. +#endif // TOOLS_ENABLED const String ProjectSettings::PROJECT_DATA_DIR_NAME_SUFFIX = "godot"; @@ -75,6 +78,7 @@ String ProjectSettings::get_imported_files_path() const { return get_project_data_path().path_join("imported"); } +#ifdef TOOLS_ENABLED // Returns the features that a project must have when opened with this build of Godot. // This is used by the project manager to provide the initial_settings for config/features. const PackedStringArray ProjectSettings::get_required_features() { @@ -137,6 +141,7 @@ const PackedStringArray ProjectSettings::_trim_to_supported_features(const Packe features.sort(); return features; } +#endif // TOOLS_ENABLED String ProjectSettings::localize_path(const String &p_path) const { if (resource_path.is_empty() || p_path.begins_with("res://") || p_path.begins_with("user://") || @@ -897,6 +902,7 @@ Error ProjectSettings::_save_custom_bnd(const String &p_file) { // add other par Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_custom, const Vector<String> &p_custom_features, bool p_merge_with_current) { ERR_FAIL_COND_V_MSG(p_path.is_empty(), ERR_INVALID_PARAMETER, "Project settings save path cannot be empty."); +#ifdef TOOLS_ENABLED PackedStringArray project_features = get_setting("application/config/features"); // If there is no feature list currently present, force one to generate. if (project_features.is_empty()) { @@ -924,6 +930,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust } project_features = _trim_to_supported_features(project_features); set_setting("application/config/features", project_features); +#endif // TOOLS_ENABLED RBSet<_VCSort> vclist; diff --git a/core/config/project_settings.h b/core/config/project_settings.h index c845120a26..a9af63ee39 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -48,8 +48,10 @@ public: //properties that are not for built in values begin from this value, so builtin ones are displayed first NO_BUILTIN_ORDER_BASE = 1 << 16 }; +#ifdef TOOLS_ENABLED const static PackedStringArray get_required_features(); const static PackedStringArray get_unsupported_features(const PackedStringArray &p_project_features); +#endif // TOOLS_ENABLED struct AutoloadInfo { StringName name; @@ -116,8 +118,10 @@ protected: Error _save_custom_bnd(const String &p_file); +#ifdef TOOLS_ENABLED const static PackedStringArray _get_supported_features(); const static PackedStringArray _trim_to_supported_features(const PackedStringArray &p_project_features); +#endif // TOOLS_ENABLED void _convert_to_last_version(int p_from_version); diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index ae421654ca..f84a95347a 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -32,6 +32,7 @@ #include "core/io/file_access_encrypted.h" #include "core/os/keyboard.h" +#include "core/string/string_builder.h" #include "core/variant/variant_parser.h" PackedStringArray ConfigFile::_get_sections() const { @@ -130,6 +131,28 @@ void ConfigFile::erase_section_key(const String &p_section, const String &p_key) } } +String ConfigFile::encode_to_text() const { + StringBuilder sb; + bool first = true; + for (const KeyValue<String, HashMap<String, Variant>> &E : values) { + if (first) { + first = false; + } else { + sb.append("\n"); + } + if (!E.key.is_empty()) { + sb.append("[" + E.key + "]\n\n"); + } + + for (const KeyValue<String, Variant> &F : E.value) { + String vstr; + VariantWriter::write_to_string(F.value, vstr); + sb.append(F.key.property_name_encode() + "=" + vstr + "\n"); + } + } + return sb.as_string(); +} + Error ConfigFile::save(const String &p_path) { Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); @@ -295,6 +318,7 @@ Error ConfigFile::_parse(const String &p_path, VariantParser::Stream *p_stream) void ConfigFile::clear() { values.clear(); } + void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("set_value", "section", "key", "value"), &ConfigFile::set_value); ClassDB::bind_method(D_METHOD("get_value", "section", "key", "default"), &ConfigFile::get_value, DEFVAL(Variant())); @@ -312,6 +336,8 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse); ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save); + ClassDB::bind_method(D_METHOD("encode_to_text"), &ConfigFile::encode_to_text); + BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN); ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted); diff --git a/core/io/config_file.h b/core/io/config_file.h index 3b07ec52f5..f6209492b7 100644 --- a/core/io/config_file.h +++ b/core/io/config_file.h @@ -68,6 +68,8 @@ public: Error load(const String &p_path); Error parse(const String &p_data); + String encode_to_text() const; // used by exporter + void clear(); Error load_encrypted(const String &p_path, const Vector<uint8_t> &p_key); diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index 2f69c10218..b24c49f58d 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -510,7 +510,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int (*r_len) += sizeof(double) * 16; } } else { - ERR_FAIL_COND_V((size_t)len < sizeof(float) * 62, ERR_INVALID_DATA); + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 16, ERR_INVALID_DATA); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { val.matrix[i][j] = decode_float(&buf[(i * 4 + j) * sizeof(float)]); diff --git a/core/string/ustring.h b/core/string/ustring.h index 31de7cc464..b8ae3c2392 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -376,8 +376,6 @@ public: String path_join(const String &p_file) const; char32_t unicode_at(int p_idx) const; - void erase(int p_pos, int p_chars); - CharString ascii(bool p_allow_extended = false) const; CharString utf8() const; Error parse_utf8(const char *p_utf8, int p_len = -1, bool p_skip_cr = false); diff --git a/doc/classes/AcceptDialog.xml b/doc/classes/AcceptDialog.xml index c83ea8c60a..ee49523c90 100644 --- a/doc/classes/AcceptDialog.xml +++ b/doc/classes/AcceptDialog.xml @@ -99,6 +99,12 @@ </signal> </signals> <theme_items> + <theme_item name="button_margin" data_type="constant" type="int" default="32"> + Offset that is applied to the content of the window on the bottom, effectively moving the button row. + </theme_item> + <theme_item name="margin" data_type="constant" type="int" default="8"> + Offset that is applied to the content of the window on top, left, and right. + </theme_item> <theme_item name="panel" data_type="style" type="StyleBox"> Panel that fills up the background of the window. </theme_item> diff --git a/doc/classes/BitMap.xml b/doc/classes/BitMap.xml index 9323642274..b3fa55f154 100644 --- a/doc/classes/BitMap.xml +++ b/doc/classes/BitMap.xml @@ -17,7 +17,7 @@ </method> <method name="create"> <return type="void" /> - <param index="0" name="size" type="Vector2" /> + <param index="0" name="size" type="Vector2i" /> <description> Creates a bitmap with the specified size, filled with [code]false[/code]. </description> @@ -32,13 +32,21 @@ </method> <method name="get_bit" qualifiers="const"> <return type="bool" /> - <param index="0" name="position" type="Vector2" /> + <param index="0" name="x" type="int" /> + <param index="1" name="y" type="int" /> + <description> + Returns bitmap's value at the specified position. + </description> + </method> + <method name="get_bitv" qualifiers="const"> + <return type="bool" /> + <param index="0" name="position" type="Vector2i" /> <description> Returns bitmap's value at the specified position. </description> </method> <method name="get_size" qualifiers="const"> - <return type="Vector2" /> + <return type="Vector2i" /> <description> Returns bitmap's dimensions. </description> @@ -52,14 +60,14 @@ <method name="grow_mask"> <return type="void" /> <param index="0" name="pixels" type="int" /> - <param index="1" name="rect" type="Rect2" /> + <param index="1" name="rect" type="Rect2i" /> <description> Applies morphological dilation or erosion to the bitmap. If [param pixels] is positive, dilation is applied to the bitmap. If [param pixels] is negative, erosion is applied to the bitmap. [param rect] defines the area where the morphological operation is applied. Pixels located outside the [param rect] are unaffected by [method grow_mask]. </description> </method> <method name="opaque_to_polygons" qualifiers="const"> <return type="PackedVector2Array[]" /> - <param index="0" name="rect" type="Rect2" /> + <param index="0" name="rect" type="Rect2i" /> <param index="1" name="epsilon" type="float" default="2.0" /> <description> Creates an [Array] of polygons covering a rectangular portion of the bitmap. It uses a marching squares algorithm, followed by Ramer-Douglas-Peucker (RDP) reduction of the number of vertices. Each polygon is described as a [PackedVector2Array] of its vertices. @@ -72,26 +80,35 @@ </method> <method name="resize"> <return type="void" /> - <param index="0" name="new_size" type="Vector2" /> + <param index="0" name="new_size" type="Vector2i" /> <description> Resizes the image to [param new_size]. </description> </method> <method name="set_bit"> <return type="void" /> - <param index="0" name="position" type="Vector2" /> - <param index="1" name="bit" type="bool" /> + <param index="0" name="x" type="int" /> + <param index="1" name="y" type="int" /> + <param index="2" name="bit" type="bool" /> <description> Sets the bitmap's element at the specified position, to the specified value. </description> </method> <method name="set_bit_rect"> <return type="void" /> - <param index="0" name="rect" type="Rect2" /> + <param index="0" name="rect" type="Rect2i" /> <param index="1" name="bit" type="bool" /> <description> Sets a rectangular portion of the bitmap to the specified value. </description> </method> + <method name="set_bitv"> + <return type="void" /> + <param index="0" name="position" type="Vector2i" /> + <param index="1" name="bit" type="bool" /> + <description> + Sets the bitmap's element at the specified position, to the specified value. + </description> + </method> </methods> </class> diff --git a/doc/classes/ConfigFile.xml b/doc/classes/ConfigFile.xml index d3ad4e6e4b..7ba53f852b 100644 --- a/doc/classes/ConfigFile.xml +++ b/doc/classes/ConfigFile.xml @@ -98,6 +98,12 @@ Removes the entire contents of the config. </description> </method> + <method name="encode_to_text" qualifiers="const"> + <return type="String" /> + <description> + Obtain the text version of this config file (the same text that would be written to a file). + </description> + </method> <method name="erase_section"> <return type="void" /> <param index="0" name="section" type="String" /> diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml new file mode 100644 index 0000000000..1d63af9233 --- /dev/null +++ b/doc/classes/EditorExportPlatform.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatform" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> +</class> diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index 091bac7d8e..3e8ce10aa5 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -10,6 +10,51 @@ <tutorials> </tutorials> <methods> + <method name="_begin_customize_resources" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="features" type="PackedStringArray" /> + <description> + Return true if this plugin will customize resources based on the platform and features used. + </description> + </method> + <method name="_begin_customize_scenes" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="features" type="PackedStringArray" /> + <description> + Return true if this plugin will customize scenes based on the platform and features used. + </description> + </method> + <method name="_customize_resource" qualifiers="virtual"> + <return type="Resource" /> + <param index="0" name="resource" type="Resource" /> + <param index="1" name="path" type="String" /> + <description> + Customize a resource. If changes are made to it, return the same or a new resource. Otherwise, return [code]null[/code]. + The [i]path[/i] argument is only used when customizing an actual file, otherwise this means that this resource is part of another one and it will be empty. + </description> + </method> + <method name="_customize_scene" qualifiers="virtual"> + <return type="Node" /> + <param index="0" name="scene" type="Node" /> + <param index="1" name="path" type="String" /> + <description> + Customize a scene. If changes are made to it, return the same or a new scene. Otherwise, return [code]null[/code]. If a new scene is returned, it is up to you to dispose of the old one. + </description> + </method> + <method name="_end_customize_resources" qualifiers="virtual"> + <return type="void" /> + <description> + This is called when the customization process for resources ends. + </description> + </method> + <method name="_end_customize_scenes" qualifiers="virtual"> + <return type="void" /> + <description> + This is called when the customization process for scenes ends. + </description> + </method> <method name="_export_begin" qualifiers="virtual"> <return type="void" /> <param index="0" name="features" type="PackedStringArray" /> @@ -36,6 +81,18 @@ Calling [method skip] inside this callback will make the file not included in the export. </description> </method> + <method name="_get_customization_configuration_hash" qualifiers="virtual const"> + <return type="int" /> + <description> + Return a hash based on the configuration passed (for both scenes and resources). This helps keep separate caches for separate export configurations. + </description> + </method> + <method name="_get_name" qualifiers="virtual const"> + <return type="String" /> + <description> + Return the name identifier of this plugin (for future identification by the exporter). + </description> + </method> <method name="add_file"> <return type="void" /> <param index="0" name="path" type="String" /> diff --git a/doc/classes/NavigationLink2D.xml b/doc/classes/NavigationLink2D.xml new file mode 100644 index 0000000000..1e086fb730 --- /dev/null +++ b/doc/classes/NavigationLink2D.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="NavigationLink2D" inherits="Node2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Creates a link between two locations that [NavigationServer2D] can route agents through. + </brief_description> + <description> + Creates a link between two locations that [NavigationServer2D] can route agents through. Links can be used to express navigation methods that aren't just traveling along the surface of the navigation mesh, like zip-lines, teleporters, or jumping across gaps. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_navigation_layer_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer_number" type="int" /> + <description> + Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [code]layer_number[/code] between 1 and 32. + </description> + </method> + <method name="set_navigation_layer_value"> + <return type="void" /> + <param index="0" name="layer_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [code]value[/code], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [code]layer_number[/code] between 1 and 32. + </description> + </method> + </methods> + <members> + <member name="bidirectional" type="bool" setter="set_bidirectional" getter="is_bidirectional" default="true"> + Whether this link can be traveled in both directions or only from [member start_location] to [member end_location]. + </member> + <member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true"> + Whether this link is currently active. If [code]false[/code], [method NavigationServer2D.map_get_path] will ignore this link. + </member> + <member name="end_location" type="Vector2" setter="set_end_location" getter="get_end_location" default="Vector2(0, 0)"> + Ending position of the link. + This position will search out the nearest polygon in the navigation mesh to attach to. + The distance the link will search is controlled by [method NavigationServer2D.map_set_link_connection_radius]. + </member> + <member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0"> + When pathfinding enters this link from another regions navmesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path. + </member> + <member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1"> + A bitfield determining all navigation layers the link belongs to. These navigation layers will be checked when requesting a path with [method NavigationServer2D.map_get_path]. + </member> + <member name="start_location" type="Vector2" setter="set_start_location" getter="get_start_location" default="Vector2(0, 0)"> + Starting position of the link. + This position will search out the nearest polygon in the navigation mesh to attach to. + The distance the link will search is controlled by [method NavigationServer2D.map_set_link_connection_radius]. + </member> + <member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0"> + When pathfinding moves along the link the traveled distance is multiplied with [code]travel_cost[/code] for determining the shortest path. + </member> + </members> +</class> diff --git a/doc/classes/NavigationLink3D.xml b/doc/classes/NavigationLink3D.xml new file mode 100644 index 0000000000..4d5d81bec5 --- /dev/null +++ b/doc/classes/NavigationLink3D.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="NavigationLink3D" inherits="Node3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Creates a link between two locations that [NavigationServer3D] can route agents through. + </brief_description> + <description> + Creates a link between two locations that [NavigationServer3D] can route agents through. Links can be used to express navigation methods that aren't just traveling along the surface of the navigation mesh, like zip-lines, teleporters, or jumping across gaps. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_navigation_layer_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer_number" type="int" /> + <description> + Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [code]layer_number[/code] between 1 and 32. + </description> + </method> + <method name="set_navigation_layer_value"> + <return type="void" /> + <param index="0" name="layer_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [code]value[/code], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [code]layer_number[/code] between 1 and 32. + </description> + </method> + </methods> + <members> + <member name="bidirectional" type="bool" setter="set_bidirectional" getter="is_bidirectional" default="true"> + Whether this link can be traveled in both directions or only from [member start_location] to [member end_location]. + </member> + <member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true"> + Whether this link is currently active. If [code]false[/code], [method NavigationServer3D.map_get_path] will ignore this link. + </member> + <member name="end_location" type="Vector3" setter="set_end_location" getter="get_end_location" default="Vector3(0, 0, 0)"> + Ending position of the link. + This position will search out the nearest polygon in the navigation mesh to attach to. + The distance the link will search is controlled by [method NavigationServer3D.map_set_link_connection_radius]. + </member> + <member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0"> + When pathfinding enters this link from another regions navmesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path. + </member> + <member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1"> + A bitfield determining all navigation layers the link belongs to. These navigation layers will be checked when requesting a path with [method NavigationServer3D.map_get_path]. + </member> + <member name="start_location" type="Vector3" setter="set_start_location" getter="get_start_location" default="Vector3(0, 0, 0)"> + Starting position of the link. + This position will search out the nearest polygon in the navigation mesh to attach to. + The distance the link will search is controlled by [method NavigationServer3D.map_set_link_connection_radius]. + </member> + <member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0"> + When pathfinding moves along the link the traveled distance is multiplied with [code]travel_cost[/code] for determining the shortest path. + </member> + </members> +</class> diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index b85c1c6649..0874e183e4 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -133,6 +133,117 @@ Returns all created navigation map [RID]s on the NavigationServer. This returns both 2D and 3D created navigation maps as there is technically no distinction between them. </description> </method> + <method name="link_create" qualifiers="const"> + <return type="RID" /> + <description> + Create a new link between two locations on a map. + </description> + </method> + <method name="link_get_end_location" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the ending location of this [code]link[/code]. + </description> + </method> + <method name="link_get_enter_cost" qualifiers="const"> + <return type="float" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the [code]enter_cost[/code] of this [code]link[/code]. + </description> + </method> + <method name="link_get_map" qualifiers="const"> + <return type="RID" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the navigation map [RID] the requested [code]link[/code] is currently assigned to. + </description> + </method> + <method name="link_get_navigation_layers" qualifiers="const"> + <return type="int" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the navigation layers for this [code]link[/code]. + </description> + </method> + <method name="link_get_start_location" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the starting location of this [code]link[/code]. + </description> + </method> + <method name="link_get_travel_cost" qualifiers="const"> + <return type="float" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the [code]travel_cost[/code] of this [code]link[/code]. + </description> + </method> + <method name="link_is_bidirectional" qualifiers="const"> + <return type="bool" /> + <param index="0" name="link" type="RID" /> + <description> + Returns whether this [code]link[/code] can be travelled in both directions. + </description> + </method> + <method name="link_set_bidirectional" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="bidirectional" type="bool" /> + <description> + Sets whether this [code]link[/code] can be travelled in both directions. + </description> + </method> + <method name="link_set_end_location" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="location" type="Vector2" /> + <description> + Sets the exit location for the [code]link[/code]. + </description> + </method> + <method name="link_set_enter_cost" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="enter_cost" type="float" /> + <description> + Sets the [code]enter_cost[/code] for this [code]link[/code]. + </description> + </method> + <method name="link_set_map" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="map" type="RID" /> + <description> + Sets the navigation map [RID] for the link. + </description> + </method> + <method name="link_set_navigation_layers" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="navigation_layers" type="int" /> + <description> + Set the links's navigation layers. This allows selecting links from a path request (when using [method NavigationServer2D.map_get_path]). + </description> + </method> + <method name="link_set_start_location" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="location" type="Vector2" /> + <description> + Sets the entry location for this [code]link[/code]. + </description> + </method> + <method name="link_set_travel_cost" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="travel_cost" type="float" /> + <description> + Sets the [code]travel_cost[/code] for this [code]link[/code]. + </description> + </method> <method name="map_create" qualifiers="const"> <return type="RID" /> <description> @@ -186,6 +297,20 @@ Returns the edge connection margin of the map. The edge connection margin is a distance used to connect two regions. </description> </method> + <method name="map_get_link_connection_radius" qualifiers="const"> + <return type="float" /> + <param index="0" name="map" type="RID" /> + <description> + Returns the link connection radius of the map. This distance is the maximum range any link will search for navigation mesh polygons to connect to. + </description> + </method> + <method name="map_get_links" qualifiers="const"> + <return type="RID[]" /> + <param index="0" name="map" type="RID" /> + <description> + Returns all navigation link [RID]s that are currently assigned to the requested navigation [code]map[/code]. + </description> + </method> <method name="map_get_path" qualifiers="const"> <return type="PackedVector2Array" /> <param index="0" name="map" type="RID" /> @@ -235,6 +360,14 @@ Set the map edge connection margin used to weld the compatible region edges. </description> </method> + <method name="map_set_link_connection_radius" qualifiers="const"> + <return type="void" /> + <param index="0" name="map" type="RID" /> + <param index="1" name="radius" type="float" /> + <description> + Set the map's link connection radius used to connect links to navigation polygons. + </description> + </method> <method name="region_create" qualifiers="const"> <return type="RID" /> <description> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index 5b2a8fc08b..255f2a902c 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -133,6 +133,117 @@ Returns all created navigation map [RID]s on the NavigationServer. This returns both 2D and 3D created navigation maps as there is technically no distinction between them. </description> </method> + <method name="link_create" qualifiers="const"> + <return type="RID" /> + <description> + Create a new link between two locations on a map. + </description> + </method> + <method name="link_get_end_location" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the ending location of this [code]link[/code]. + </description> + </method> + <method name="link_get_enter_cost" qualifiers="const"> + <return type="float" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the [code]enter_cost[/code] of this [code]link[/code]. + </description> + </method> + <method name="link_get_map" qualifiers="const"> + <return type="RID" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the navigation map [RID] the requested [code]link[/code] is currently assigned to. + </description> + </method> + <method name="link_get_navigation_layers" qualifiers="const"> + <return type="int" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the navigation layers for this [code]link[/code]. + </description> + </method> + <method name="link_get_start_location" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the starting location of this [code]link[/code]. + </description> + </method> + <method name="link_get_travel_cost" qualifiers="const"> + <return type="float" /> + <param index="0" name="link" type="RID" /> + <description> + Returns the [code]travel_cost[/code] of this [code]link[/code]. + </description> + </method> + <method name="link_is_bidirectional" qualifiers="const"> + <return type="bool" /> + <param index="0" name="link" type="RID" /> + <description> + Returns whether this [code]link[/code] can be travelled in both directions. + </description> + </method> + <method name="link_set_bidirectional" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="bidirectional" type="bool" /> + <description> + Sets whether this [code]link[/code] can be travelled in both directions. + </description> + </method> + <method name="link_set_end_location" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="location" type="Vector3" /> + <description> + Sets the exit location for the [code]link[/code]. + </description> + </method> + <method name="link_set_enter_cost" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="enter_cost" type="float" /> + <description> + Sets the [code]enter_cost[/code] for this [code]link[/code]. + </description> + </method> + <method name="link_set_map" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="map" type="RID" /> + <description> + Sets the navigation map [RID] for the link. + </description> + </method> + <method name="link_set_navigation_layers" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="navigation_layers" type="int" /> + <description> + Set the links's navigation layers. This allows selecting links from a path request (when using [method NavigationServer3D.map_get_path]). + </description> + </method> + <method name="link_set_start_location" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="location" type="Vector3" /> + <description> + Sets the entry location for this [code]link[/code]. + </description> + </method> + <method name="link_set_travel_cost" qualifiers="const"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="travel_cost" type="float" /> + <description> + Sets the [code]travel_cost[/code] for this [code]link[/code]. + </description> + </method> <method name="map_create" qualifiers="const"> <return type="RID" /> <description> @@ -204,6 +315,20 @@ Returns the edge connection margin of the map. This distance is the minimum vertex distance needed to connect two edges from different regions. </description> </method> + <method name="map_get_link_connection_radius" qualifiers="const"> + <return type="float" /> + <param index="0" name="map" type="RID" /> + <description> + Returns the link connection radius of the map. This distance is the maximum range any link will search for navigation mesh polygons to connect to. + </description> + </method> + <method name="map_get_links" qualifiers="const"> + <return type="RID[]" /> + <param index="0" name="map" type="RID" /> + <description> + Returns all navigation link [RID]s that are currently assigned to the requested navigation [code]map[/code]. + </description> + </method> <method name="map_get_path" qualifiers="const"> <return type="PackedVector3Array" /> <param index="0" name="map" type="RID" /> @@ -260,6 +385,14 @@ Set the map edge connection margin used to weld the compatible region edges. </description> </method> + <method name="map_set_link_connection_radius" qualifiers="const"> + <return type="void" /> + <param index="0" name="map" type="RID" /> + <param index="1" name="radius" type="float" /> + <description> + Set the map's link connection radius used to connect links to navigation polygons. + </description> + </method> <method name="map_set_up" qualifiers="const"> <return type="void" /> <param index="0" name="map" type="RID" /> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ffafff0ff4..e62c0433b4 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -497,6 +497,12 @@ <member name="debug/shapes/navigation/enable_geometry_face_random_color" type="bool" setter="" getter="" default="true"> If enabled, colorizes each navigation mesh polygon face with a random color when "Visible Navigation" is enabled in the Debug menu. </member> + <member name="debug/shapes/navigation/enable_link_connections" type="bool" setter="" getter="" default="true"> + If enabled, displays navigation link connections when "Visible Navigation" is enabled in the Debug menu. + </member> + <member name="debug/shapes/navigation/enable_link_connections_xray" type="bool" setter="" getter="" default="true"> + If enabled, displays navigation link connections through geometry when "Visible Navigation" is enabled in the Debug menu. + </member> <member name="debug/shapes/navigation/geometry_color" type="Color" setter="" getter="" default="Color(0.1, 1, 0.7, 0.4)"> Color of the navigation geometry, visible when "Visible Navigation" is enabled in the Debug menu. </member> @@ -512,6 +518,12 @@ <member name="debug/shapes/navigation/geometry_face_disabled_color" type="Color" setter="" getter="" default="Color(0.5, 0.5, 0.5, 0.4)"> Color to display disabled navigation mesh polygon faces, visible when "Visible Navigation" is enabled in the Debug menu. </member> + <member name="debug/shapes/navigation/link_connection_color" type="Color" setter="" getter="" default="Color(1, 0.5, 1, 1)"> + Color to use to display navigation link connections, visible when "Visible Navigation" is enabled in the Debug menu. + </member> + <member name="debug/shapes/navigation/link_connection_disabled_color" type="Color" setter="" getter="" default="Color(0.5, 0.5, 0.5, 1)"> + Color to use to display disabled navigation link connections, visible when "Visible Navigation" is enabled in the Debug menu. + </member> <member name="debug/shapes/paths/geometry_color" type="Color" setter="" getter="" default="Color(0.1, 1, 0.7, 0.4)"> Color of the curve path geometry, visible when "Visible Paths" is enabled in the Debug menu. </member> @@ -1439,12 +1451,18 @@ <member name="navigation/2d/default_edge_connection_margin" type="int" setter="" getter="" default="1"> Default edge connection margin for 2D navigation maps. See [method NavigationServer2D.map_set_edge_connection_margin]. </member> + <member name="navigation/2d/default_link_connection_radius" type="int" setter="" getter="" default="4"> + Default link connection radius for 2D navigation maps. See [method NavigationServer2D.map_set_link_connection_radius]. + </member> <member name="navigation/3d/default_cell_size" type="float" setter="" getter="" default="0.25"> Default cell size for 3D navigation maps. See [method NavigationServer3D.map_set_cell_size]. </member> <member name="navigation/3d/default_edge_connection_margin" type="float" setter="" getter="" default="0.25"> Default edge connection margin for 3D navigation maps. See [method NavigationServer3D.map_set_edge_connection_margin]. </member> + <member name="navigation/3d/default_link_connection_radius" type="float" setter="" getter="" default="1.0"> + Default link connection radius for 3D navigation maps. See [method NavigationServer3D.map_set_link_connection_radius]. + </member> <member name="network/limits/debugger/max_chars_per_second" type="int" setter="" getter="" default="32768"> Maximum number of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. </member> diff --git a/editor/editor_atlas_packer.cpp b/editor/editor_atlas_packer.cpp index 9c6bcd769a..7f4bd8cc89 100644 --- a/editor/editor_atlas_packer.cpp +++ b/editor/editor_atlas_packer.cpp @@ -81,7 +81,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h int l = k == 0 ? 2 : k - 1; Vector<Point2i> points = Geometry2D::bresenham_line(v[k], v[l]); for (Point2i point : points) { - src_bitmap->set_bit(point, true); + src_bitmap->set_bitv(point, true); } } } @@ -128,7 +128,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h continue; } - if (src_bitmap->get_bit(Vector2(px, py))) { + if (src_bitmap->get_bit(px, py)) { found_pixel = true; } } diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 6aa0bd3f99..bdf1c314f8 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3949,16 +3949,22 @@ void EditorInspector::_add_meta_confirm() { undo_redo->commit_action(); } -void EditorInspector::_check_meta_name(String name) { +void EditorInspector::_check_meta_name(const String &p_name) { String error; - if (name == "") { - error = TTR("Metadata can't be empty."); - } else if (!name.is_valid_identifier()) { - error = TTR("Invalid metadata identifier."); - } else if (object->has_meta(name)) { - error = TTR("Metadata already exists."); - } else if (name[0] == '_') { + if (p_name == "") { + error = TTR("Metadata name can't be empty."); + } else if (!p_name.is_valid_identifier()) { + error = TTR("Metadata name must be a valid identifier."); + } else if (object->has_meta(p_name)) { + Node *node = Object::cast_to<Node>(object); + if (node) { + error = vformat(TTR("Metadata with name \"%s\" already exists on \"%s\"."), p_name, node->get_name()); + } else { + // This should normally never be reached, but the error is set just in case. + error = vformat(TTR("Metadata with name \"%s\" already exists."), p_name, node->get_name()); + } + } else if (p_name[0] == '_') { error = TTR("Names starting with _ are reserved for editor-only metadata."); } @@ -3976,7 +3982,15 @@ void EditorInspector::_check_meta_name(String name) { void EditorInspector::_show_add_meta_dialog() { if (!add_meta_dialog) { add_meta_dialog = memnew(ConfirmationDialog); - add_meta_dialog->set_title(TTR("Add Metadata Property")); + + Node *node = Object::cast_to<Node>(object); + if (node) { + add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\""), node->get_name())); + } else { + // This should normally never be reached, but the title is set just in case. + add_meta_dialog->set_title(vformat(TTR("Add Metadata Property"), node->get_name())); + } + VBoxContainer *vbc = memnew(VBoxContainer); add_meta_dialog->add_child(vbc); HBoxContainer *hbc = memnew(HBoxContainer); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 474078853a..d634eae23f 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -538,7 +538,7 @@ class EditorInspector : public ScrollContainer { void _add_meta_confirm(); void _show_add_meta_dialog(); - void _check_meta_name(String name); + void _check_meta_name(const String &p_name); protected: static void _bind_methods(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 689a13ab5c..c2820389c6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -170,6 +170,7 @@ #include "editor/plugins/mesh_instance_3d_editor_plugin.h" #include "editor/plugins/mesh_library_editor_plugin.h" #include "editor/plugins/multimesh_editor_plugin.h" +#include "editor/plugins/navigation_link_2d_editor_plugin.h" #include "editor/plugins/navigation_polygon_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/occluder_instance_3d_editor_plugin.h" @@ -4110,6 +4111,7 @@ void EditorNode::register_editor_types() { GDREGISTER_CLASS(EditorSyntaxHighlighter); GDREGISTER_ABSTRACT_CLASS(EditorInterface); GDREGISTER_CLASS(EditorExportPlugin); + GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform); GDREGISTER_CLASS(EditorResourceConversionPlugin); GDREGISTER_CLASS(EditorSceneFormatImporter); GDREGISTER_CLASS(EditorScenePostImportPlugin); @@ -7348,6 +7350,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(GPUParticles2DEditorPlugin)); add_editor_plugin(memnew(LightOccluder2DEditorPlugin)); add_editor_plugin(memnew(Line2DEditorPlugin)); + add_editor_plugin(memnew(NavigationLink2DEditorPlugin)); add_editor_plugin(memnew(NavigationPolygonEditorPlugin)); add_editor_plugin(memnew(Path2DEditorPlugin)); add_editor_plugin(memnew(Polygon2DEditorPlugin)); @@ -7421,11 +7424,6 @@ EditorNode::EditorNode() { editor_plugins_force_over = memnew(EditorPluginList); editor_plugins_force_input_forwarding = memnew(EditorPluginList); - Ref<EditorExportTextSceneToBinaryPlugin> export_text_to_binary_plugin; - export_text_to_binary_plugin.instantiate(); - - EditorExport::get_singleton()->add_export_plugin(export_text_to_binary_plugin); - Ref<GDExtensionExportPlugin> gdextension_export_plugin; gdextension_export_plugin.instantiate(); diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index d291040bd2..29b6a5e546 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -351,6 +351,8 @@ EditorExport::EditorExport() { singleton = this; set_process(true); + + GLOBAL_DEF("editor/export/convert_text_resources_to_binary", true); } EditorExport::~EditorExport() { diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index e07b5e4cdb..525a962222 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -44,6 +44,7 @@ #include "editor/editor_settings.h" #include "editor/plugins/script_editor_plugin.h" #include "editor_export_plugin.h" +#include "scene/resources/packed_scene.h" static int _get_pad(int p_alignment, int p_n) { int rest = p_n % p_alignment; @@ -488,6 +489,295 @@ EditorExportPlatform::ExportNotifier::~ExportNotifier() { } } +bool EditorExportPlatform::_export_customize_dictionary(Dictionary &dict, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + List<Variant> keys; + dict.get_key_list(&keys); + for (const Variant &K : keys) { + Variant v = dict[K]; + switch (v.get_type()) { + case Variant::OBJECT: { + Ref<Resource> res = v; + if (res.is_valid()) { + for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) { + Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, ""); + if (new_res.is_valid()) { + changed = true; + if (new_res != res) { + dict[K] = new_res; + res = new_res; + } + break; + } + } + + // If it was not replaced, go through and see if there is something to replace. + if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) { + changed = true; + } + } + + } break; + case Variant::DICTIONARY: { + Dictionary d = v; + if (_export_customize_dictionary(d, customize_resources_plugins)) { + changed = true; + } + } break; + case Variant::ARRAY: { + Array a = v; + if (_export_customize_array(a, customize_resources_plugins)) { + changed = true; + } + } break; + default: { + } + } + } + return changed; +} + +bool EditorExportPlatform::_export_customize_array(Array &arr, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + for (int i = 0; i < arr.size(); i++) { + Variant v = arr.get(i); + switch (v.get_type()) { + case Variant::OBJECT: { + Ref<Resource> res = v; + if (res.is_valid()) { + for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) { + Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, ""); + if (new_res.is_valid()) { + changed = true; + if (new_res != res) { + arr.set(i, new_res); + res = new_res; + } + break; + } + } + + // If it was not replaced, go through and see if there is something to replace. + if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) { + changed = true; + } + } + } break; + case Variant::DICTIONARY: { + Dictionary d = v; + if (_export_customize_dictionary(d, customize_resources_plugins)) { + changed = true; + } + } break; + case Variant::ARRAY: { + Array a = v; + if (_export_customize_array(a, customize_resources_plugins)) { + changed = true; + } + } break; + default: { + } + } + } + return changed; +} + +bool EditorExportPlatform::_export_customize_object(Object *p_object, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + List<PropertyInfo> props; + p_object->get_property_list(&props); + for (const PropertyInfo &E : props) { + switch (E.type) { + case Variant::OBJECT: { + Ref<Resource> res = p_object->get(E.name); + if (res.is_valid()) { + for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) { + Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, ""); + if (new_res.is_valid()) { + changed = true; + if (new_res != res) { + p_object->set(E.name, new_res); + res = new_res; + } + break; + } + } + + // If it was not replaced, go through and see if there is something to replace. + if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) { + changed = true; + } + } + + } break; + case Variant::DICTIONARY: { + Dictionary d = p_object->get(E.name); + if (_export_customize_dictionary(d, customize_resources_plugins)) { + // May have been generated, so set back just in case + p_object->set(E.name, d); + changed = true; + } + } break; + case Variant::ARRAY: { + Array a = p_object->get(E.name); + if (_export_customize_array(a, customize_resources_plugins)) { + // May have been generated, so set back just in case + p_object->set(E.name, a); + changed = true; + } + } break; + default: { + } + } + } + return changed; +} + +bool EditorExportPlatform::_export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + if (p_node == p_root || p_node->get_owner() == p_root) { + if (_export_customize_object(p_node, customize_resources_plugins)) { + changed = true; + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + if (_export_customize_scene_resources(p_root, p_node->get_child(i), customize_resources_plugins)) { + changed = true; + } + } + + return changed; +} + +String EditorExportPlatform::_export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save) { + if (!p_force_save && customize_resources_plugins.is_empty() && customize_scenes_plugins.is_empty()) { + return p_path; // do none + } + + // Check if a cache exists + if (export_cache.has(p_path)) { + FileExportCache &fec = export_cache[p_path]; + + if (fec.saved_path.is_empty() || FileAccess::exists(fec.saved_path)) { + // Destination file exists (was not erased) or not needed + + uint64_t mod_time = FileAccess::get_modified_time(p_path); + if (fec.source_modified_time == mod_time) { + // Cached (modified time matches). + fec.used = true; + return fec.saved_path.is_empty() ? p_path : fec.saved_path; + } + + String md5 = FileAccess::get_md5(p_path); + if (FileAccess::exists(p_path + ".import")) { + // Also consider the import file in the string + md5 += FileAccess::get_md5(p_path + ".import"); + } + if (fec.source_md5 == md5) { + // Cached (md5 matches). + fec.source_modified_time = mod_time; + fec.used = true; + return fec.saved_path.is_empty() ? p_path : fec.saved_path; + } + } + } + + FileExportCache fec; + fec.used = true; + fec.source_modified_time = FileAccess::get_modified_time(p_path); + + String md5 = FileAccess::get_md5(p_path); + if (FileAccess::exists(p_path + ".import")) { + // Also consider the import file in the string + md5 += FileAccess::get_md5(p_path + ".import"); + } + + fec.source_md5 = md5; + + // Check if it should convert + + String type = ResourceLoader::get_resource_type(p_path); + + bool modified = false; + + String save_path; + + if (type == "PackedScene") { // Its a scene. + Ref<PackedScene> ps = ResourceLoader::load(p_path, "PackedScene", ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND_V(ps.is_null(), p_path); + Node *node = ps->instantiate(); + ERR_FAIL_COND_V(node == nullptr, p_path); + if (customize_scenes_plugins.size()) { + for (uint32_t i = 0; i < customize_scenes_plugins.size(); i++) { + Node *customized = customize_scenes_plugins[i]->_customize_scene(node, p_path); + if (customized != nullptr) { + node = customized; + modified = true; + } + } + } + if (customize_resources_plugins.size()) { + if (_export_customize_scene_resources(node, node, customize_resources_plugins)) { + modified = true; + } + } + + if (modified || p_force_save) { + // If modified, save it again. This is also used for TSCN -> SCN conversion on export. + + String base_file = p_path.get_file().get_basename() + ".scn"; // use SCN for saving (binary) and repack (If conversting, TSCN PackedScene representation is inefficient, so repacking is also desired). + save_path = export_base_path.path_join("export-" + p_path.md5_text() + "-" + base_file); + + Ref<PackedScene> s; + s.instantiate(); + s->pack(node); + Error err = ResourceSaver::save(s, save_path); + ERR_FAIL_COND_V_MSG(err != OK, p_path, "Unable to save export scene file to: " + save_path); + } + } else { + Ref<Resource> res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND_V(res.is_null(), p_path); + + if (customize_resources_plugins.size()) { + for (uint32_t i = 0; i < customize_resources_plugins.size(); i++) { + Ref<Resource> new_res = customize_resources_plugins[i]->_customize_resource(res, p_path); + if (new_res.is_valid()) { + modified = true; + if (new_res != res) { + res = new_res; + } + break; + } + } + + if (_export_customize_object(res.ptr(), customize_resources_plugins)) { + modified = true; + } + } + + if (modified || p_force_save) { + // If modified, save it again. This is also used for TRES -> RES conversion on export. + + String base_file = p_path.get_file().get_basename() + ".res"; // use RES for saving (binary) + save_path = export_base_path.path_join("export-" + p_path.md5_text() + "-" + base_file); + + Error err = ResourceSaver::save(res, save_path); + ERR_FAIL_COND_V_MSG(err != OK, p_path, "Unable to save export resource file to: " + save_path); + } + } + + fec.saved_path = save_path; + + export_cache[p_path] = fec; + + return save_path.is_empty() ? p_path : save_path; +} + Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { //figure out paths of files that will be exported HashSet<String> paths; @@ -601,6 +891,15 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & Error err = OK; Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + struct SortByName { + bool operator()(const Ref<EditorExportPlugin> &left, const Ref<EditorExportPlugin> &right) const { + return left->_get_name() < right->_get_name(); + } + }; + + // Always sort by name, to so if for some reason theya are re-arranged, it still works. + export_plugins.sort_custom<SortByName>(); + for (int i = 0; i < export_plugins.size(); i++) { export_plugins.write[i]->set_export_preset(p_preset); @@ -623,6 +922,65 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } HashSet<String> features = get_features(p_preset, p_debug); + PackedStringArray features_psa; + for (const String &feature : features) { + features_psa.push_back(feature); + } + + // Check if custom processing is needed + uint32_t custom_resources_hash = HASH_MURMUR3_SEED; + uint32_t custom_scene_hash = HASH_MURMUR3_SEED; + + LocalVector<Ref<EditorExportPlugin>> customize_resources_plugins; + LocalVector<Ref<EditorExportPlugin>> customize_scenes_plugins; + + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->_begin_customize_resources(Ref<EditorExportPlatform>(this), features_psa)) { + customize_resources_plugins.push_back(export_plugins[i]); + + custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); + uint64_t hash = export_plugins[i]->_get_customization_configuration_hash(); + custom_resources_hash = hash_murmur3_one_64(hash, custom_resources_hash); + } + if (export_plugins[i]->_begin_customize_scenes(Ref<EditorExportPlatform>(this), features_psa)) { + customize_scenes_plugins.push_back(export_plugins[i]); + + custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); + uint64_t hash = export_plugins[i]->_get_customization_configuration_hash(); + custom_scene_hash = hash_murmur3_one_64(hash, custom_scene_hash); + } + } + + HashMap<String, FileExportCache> export_cache; + String export_base_path = ProjectSettings::get_singleton()->get_project_data_path().path_join("exported/") + itos(custom_resources_hash); + + bool convert_text_to_binary = GLOBAL_GET("editor/export/convert_text_resources_to_binary"); + + if (convert_text_to_binary || customize_resources_plugins.size() || customize_scenes_plugins.size()) { + // See if we have something to open + Ref<FileAccess> f = FileAccess::open(export_base_path.path_join("file_cache"), FileAccess::READ); + if (f.is_valid()) { + String l = f->get_line(); + while (l != String()) { + Vector<String> fields = l.split("::"); + if (fields.size() == 4) { + FileExportCache fec; + String path = fields[0]; + fec.source_md5 = fields[1].strip_edges(); + fec.source_modified_time = fields[2].strip_edges().to_int(); + fec.saved_path = fields[3]; + fec.used = false; // Assume unused until used. + export_cache[path] = fec; + } + l = f->get_line(); + } + } else { + // create the path + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); + d->change_dir(ProjectSettings::get_singleton()->get_project_data_path()); + d->make_dir_recursive("exported/" + itos(custom_resources_hash)); + } + } //store everything in the export medium int idx = 0; @@ -633,85 +991,133 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String type = ResourceLoader::get_resource_type(path); if (FileAccess::exists(path + ".import")) { - //file is imported, replace by what it imports - Ref<ConfigFile> config; - config.instantiate(); - err = config->load(path + ".import"); - if (err != OK) { - ERR_PRINT("Could not parse: '" + path + "', not exported."); - continue; - } + // Before doing this, try to see if it can be customized - String importer_type = config->get_value("remap", "importer"); + String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, false); - if (importer_type == "keep") { - //just keep file as-is - Vector<uint8_t> array = FileAccess::get_file_as_array(path); - err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); + if (export_path != path) { + // It was actually customized.. + // Since the original file is likely not recognized, just use the import system + Ref<ConfigFile> config; + config.instantiate(); + err = config->load(path + ".import"); + if (err != OK) { + ERR_PRINT("Could not parse: '" + path + "', not exported."); + continue; + } + config->set_value("remap", "type", ResourceLoader::get_resource_type(export_path)); + + // Erase all PAths + List<String> keys; + config->get_section_keys("remap", &keys); + for (const String &K : keys) { + if (E.begins_with("path")) { + config->erase_section_key("remap", K); + } + } + // Set actual converted path. + config->set_value("remap", "path", export_path); + + // erase useless sections + config->erase_section("deps"); + config->erase_section("params"); + + String import_text = config->encode_to_text(); + CharString cs = import_text.utf8(); + Vector<uint8_t> sarr; + sarr.resize(cs.size()); + memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); + + err = p_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; } + // Now actual remapped file: + sarr = FileAccess::get_file_as_array(export_path); + err = p_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key); + if (err != OK) { + return err; + } + } else { + // file is imported and not customized, replace by what it imports + Ref<ConfigFile> config; + config.instantiate(); + err = config->load(path + ".import"); + if (err != OK) { + ERR_PRINT("Could not parse: '" + path + "', not exported."); + continue; + } - continue; - } + String importer_type = config->get_value("remap", "importer"); - List<String> remaps; - config->get_section_keys("remap", &remaps); + if (importer_type == "keep") { + //just keep file as-is + Vector<uint8_t> array = FileAccess::get_file_as_array(path); + err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); - HashSet<String> remap_features; + if (err != OK) { + return err; + } - for (const String &F : remaps) { - String remap = F; - String feature = remap.get_slice(".", 1); - if (features.has(feature)) { - remap_features.insert(feature); + continue; } - } - if (remap_features.size() > 1) { - this->resolve_platform_feature_priorities(p_preset, remap_features); - } + List<String> remaps; + config->get_section_keys("remap", &remaps); - err = OK; + HashSet<String> remap_features; - for (const String &F : remaps) { - String remap = F; - if (remap == "path") { - String remapped_path = config->get_value("remap", remap); - Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); - err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); - } else if (remap.begins_with("path.")) { + for (const String &F : remaps) { + String remap = F; String feature = remap.get_slice(".", 1); + if (features.has(feature)) { + remap_features.insert(feature); + } + } - if (remap_features.has(feature)) { + if (remap_features.size() > 1) { + this->resolve_platform_feature_priorities(p_preset, remap_features); + } + + err = OK; + + for (const String &F : remaps) { + String remap = F; + if (remap == "path") { String remapped_path = config->get_value("remap", remap); Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + } else if (remap.begins_with("path.")) { + String feature = remap.get_slice(".", 1); + + if (remap_features.has(feature)) { + String remapped_path = config->get_value("remap", remap); + Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); + err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + } } } - } - if (err != OK) { - return err; - } + if (err != OK) { + return err; + } - //also save the .import file - Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import"); - err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key); + //also save the .import file + Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import"); + err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key); - if (err != OK) { - return err; + if (err != OK) { + return err; + } } } else { + // Customize + bool do_export = true; for (int i = 0; i < export_plugins.size(); i++) { if (export_plugins[i]->get_script_instance()) { //script based - PackedStringArray features_psa; - for (const String &feature : features) { - features_psa.push_back(feature); - } export_plugins.write[i]->_export_file_script(path, type, features_psa); } else { export_plugins.write[i]->_export_file(path, type, features); @@ -748,8 +1154,18 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } //just store it as it comes if (do_export) { - Vector<uint8_t> array = FileAccess::get_file_as_array(path); - err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); + // Customization only happens if plugins did not take care of it before + bool force_binary = convert_text_to_binary && (path.get_extension().to_lower() == "tres" || path.get_extension().to_lower() == "tscn"); + String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, force_binary); + + if (export_path != path) { + // Add a remap entry + path_remaps.push_back(path); + path_remaps.push_back(export_path); + } + + Vector<uint8_t> array = FileAccess::get_file_as_array(export_path); + err = p_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; } @@ -759,6 +1175,31 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & idx++; } + if (convert_text_to_binary || customize_resources_plugins.size() || customize_scenes_plugins.size()) { + // End scene customization + + String fcache = export_base_path.path_join("file_cache"); + Ref<FileAccess> f = FileAccess::open(fcache, FileAccess::WRITE); + + if (f.is_valid()) { + for (const KeyValue<String, FileExportCache> &E : export_cache) { + if (E.value.used) { // May be old, unused + String l = E.key + "::" + E.value.source_md5 + "::" + itos(E.value.source_modified_time) + "::" + E.value.saved_path; + f->store_line(l); + } + } + } else { + ERR_PRINT("Error opening export file cache: " + fcache); + } + + for (uint32_t i = 0; i < customize_resources_plugins.size(); i++) { + customize_resources_plugins[i]->_end_customize_resources(); + } + + for (uint32_t i = 0; i < customize_scenes_plugins.size(); i++) { + customize_scenes_plugins[i]->_end_customize_scenes(); + } + } //save config! Vector<String> custom_list; diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index bbdb47e041..93bc54284f 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -40,6 +40,8 @@ struct EditorProgress; #include "scene/gui/rich_text_label.h" #include "scene/main/node.h" +class EditorExportPlugin; + class EditorExportPlatform : public RefCounted { GDCLASS(EditorExportPlatform, RefCounted); @@ -99,6 +101,20 @@ private: static Error _add_shared_object(void *p_userdata, const SharedObject &p_so); + struct FileExportCache { + uint64_t source_modified_time = 0; + String source_md5; + String saved_path; + bool used = false; + }; + + bool _export_customize_dictionary(Dictionary &dict, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + bool _export_customize_array(Array &array, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + bool _export_customize_object(Object *p_object, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + bool _export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + + String _export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save); + protected: struct ExportNotifier { ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 27a671a919..971ea579cc 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -138,6 +138,64 @@ void EditorExportPlugin::_export_end_script() { GDVIRTUAL_CALL(_export_end); } +// Customization + +bool EditorExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const { + bool ret = false; + if (GDVIRTUAL_CALL(_begin_customize_resources, p_platform, p_features, ret)) { + return ret; + } + return false; +} + +Ref<Resource> EditorExportPlugin::_customize_resource(const Ref<Resource> &p_resource, const String &p_path) { + Ref<Resource> ret; + if (GDVIRTUAL_REQUIRED_CALL(_customize_resource, p_resource, p_path, ret)) { + return ret; + } + return Ref<Resource>(); +} + +bool EditorExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const { + bool ret = false; + if (GDVIRTUAL_CALL(_begin_customize_scenes, p_platform, p_features, ret)) { + return ret; + } + return false; +} + +Node *EditorExportPlugin::_customize_scene(Node *p_root, const String &p_path) { + Node *ret = nullptr; + if (GDVIRTUAL_REQUIRED_CALL(_customize_scene, p_root, p_path, ret)) { + return ret; + } + return nullptr; +} + +uint64_t EditorExportPlugin::_get_customization_configuration_hash() const { + uint64_t ret = 0; + if (GDVIRTUAL_REQUIRED_CALL(_get_customization_configuration_hash, ret)) { + return ret; + } + return 0; +} + +void EditorExportPlugin::_end_customize_scenes() { + GDVIRTUAL_CALL(_end_customize_scenes); +} + +void EditorExportPlugin::_end_customize_resources() { + GDVIRTUAL_CALL(_end_customize_resources); +} + +String EditorExportPlugin::_get_name() const { + String ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_name, ret)) { + return ret; + } + return ""; +} + void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { } @@ -164,38 +222,20 @@ void EditorExportPlugin::_bind_methods() { GDVIRTUAL_BIND(_export_file, "path", "type", "features"); GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags"); GDVIRTUAL_BIND(_export_end); -} -EditorExportPlugin::EditorExportPlugin() { -} + GDVIRTUAL_BIND(_begin_customize_resources, "platform", "features"); + GDVIRTUAL_BIND(_customize_resource, "resource", "path"); -/////////////////////// + GDVIRTUAL_BIND(_begin_customize_scenes, "platform", "features"); + GDVIRTUAL_BIND(_customize_scene, "scene", "path"); -void EditorExportTextSceneToBinaryPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { - String extension = p_path.get_extension().to_lower(); - if (extension != "tres" && extension != "tscn") { - return; - } + GDVIRTUAL_BIND(_get_customization_configuration_hash); - bool convert = GLOBAL_GET("editor/export/convert_text_resources_to_binary"); - if (!convert) { - return; - } - String tmp_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpfile.res"); - Error err = ResourceFormatLoaderText::convert_file_to_binary(p_path, tmp_path); - if (err != OK) { - DirAccess::remove_file_or_error(tmp_path); - ERR_FAIL(); - } - Vector<uint8_t> data = FileAccess::get_file_as_array(tmp_path); - if (data.size() == 0) { - DirAccess::remove_file_or_error(tmp_path); - ERR_FAIL(); - } - DirAccess::remove_file_or_error(tmp_path); - add_file(p_path + ".converted.res", data, true); + GDVIRTUAL_BIND(_end_customize_scenes); + GDVIRTUAL_BIND(_end_customize_resources); + + GDVIRTUAL_BIND(_get_name); } -EditorExportTextSceneToBinaryPlugin::EditorExportTextSceneToBinaryPlugin() { - GLOBAL_DEF("editor/export/convert_text_resources_to_binary", false); +EditorExportPlugin::EditorExportPlugin() { } diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 04ebc1dfed..3f37ed40be 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -34,6 +34,7 @@ #include "core/extension/native_extension.h" #include "editor_export_preset.h" #include "editor_export_shared_object.h" +#include "scene/main/node.h" class EditorExportPlugin : public RefCounted { GDCLASS(EditorExportPlugin, RefCounted); @@ -77,6 +78,7 @@ class EditorExportPlugin : public RefCounted { macos_plugin_files.clear(); } + // Export void _export_file_script(const String &p_path, const String &p_type, const Vector<String> &p_features); void _export_begin_script(const Vector<String> &p_features, bool p_debug, const String &p_path, int p_flags); void _export_end_script(); @@ -108,6 +110,31 @@ protected: GDVIRTUAL4(_export_begin, Vector<String>, bool, String, uint32_t) GDVIRTUAL0(_export_end) + GDVIRTUAL2RC(bool, _begin_customize_resources, const Ref<EditorExportPlatform> &, const Vector<String> &) + GDVIRTUAL2R(Ref<Resource>, _customize_resource, const Ref<Resource> &, String) + + GDVIRTUAL2RC(bool, _begin_customize_scenes, const Ref<EditorExportPlatform> &, const Vector<String> &) + GDVIRTUAL2R(Node *, _customize_scene, Node *, String) + GDVIRTUAL0RC(uint64_t, _get_customization_configuration_hash) + + GDVIRTUAL0(_end_customize_scenes) + GDVIRTUAL0(_end_customize_resources) + + GDVIRTUAL0RC(String, _get_name) + + bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization + Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path); // If nothing is returned, it means do not touch (nothing changed). If something is returned (either the same or a different resource) it means changes are made. + + bool _begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization + Node *_customize_scene(Node *p_root, const String &p_path); // Return true if a change was made + + uint64_t _get_customization_configuration_hash() const; // Hash used for caching customized resources and scenes. + + void _end_customize_scenes(); + void _end_customize_resources(); + + virtual String _get_name() const; + public: Vector<String> get_ios_frameworks() const; Vector<String> get_ios_embedded_frameworks() const; @@ -121,12 +148,4 @@ public: EditorExportPlugin(); }; -class EditorExportTextSceneToBinaryPlugin : public EditorExportPlugin { - GDCLASS(EditorExportTextSceneToBinaryPlugin, EditorExportPlugin); - -public: - virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) override; - EditorExportTextSceneToBinaryPlugin(); -}; - #endif // EDITOR_EXPORT_PLUGIN_H diff --git a/editor/icons/NavigationLink2D.svg b/editor/icons/NavigationLink2D.svg new file mode 100644 index 0000000000..6c5f17e256 --- /dev/null +++ b/editor/icons/NavigationLink2D.svg @@ -0,0 +1,4 @@ +<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m12.386 5.3097c-0.69157-0.021112-1.3071 0.36382-1.7492 0.86685-0.58 0.58-1.16 1.16-1.74 1.74 0.4588-0.28502 1.0599-0.064948 1.4771-0.037996 0.45549-0.44357 0.89024-0.91006 1.3596-1.3383 0.56256-0.44564 1.4906-0.15731 1.7028 0.52802 0.18967 0.4871-0.049221 1.0098-0.43284 1.3208-0.70048 0.68896-1.3789 1.4022-2.0935 2.0755-0.47999 0.3725-1.2044 0.226-1.5679-0.24034-0.38763-0.38194-1.0641 0.16031-0.78317 0.6241 0.6767 0.94379 2.1573 1.1282 3.0411 0.36751 0.80287-0.7704 1.5793-1.5696 2.3665-2.3564 0.79925-0.83719 0.70104-2.3112-0.19552-3.0393-0.38108-0.32877-0.8822-0.5119-1.385-0.51049zm-3.051 3.051c-0.69157-0.021106-1.3071 0.36382-1.7492 0.86685-0.67513 0.68452-1.37 1.3506-2.0319 2.0474-0.75433 0.87744-0.58087 2.3428 0.34933 3.0252 0.84748 0.68613 2.192 0.54839 2.8998-0.27341 0.63032-0.63031 1.2606-1.2606 1.8909-1.8909-0.4587 0.28554-1.0602 0.0659-1.477 0.038069-0.45445 0.44348-0.88773 0.91034-1.3564 1.3383-0.56256 0.44565-1.4906 0.15731-1.7028-0.52802-0.18967-0.4871 0.049229-1.0098 0.43284-1.3208 0.70048-0.68896 1.3789-1.4022 2.0935-2.0755 0.48-0.3725 1.2044-0.22601 1.5679 0.24036 0.38733 0.38325 1.064-0.16067 0.78313-0.6241-0.39353-0.52481-1.0429-0.84871-1.7002-0.8434z" fill="#8ea6f4" fill-opacity=".99608" stroke-linecap="round" stroke-linejoin="round" stroke-width=".013911"/> +<path d="m2 1c-0.61942-0.0066969-1.0877 0.60314-1 1.198 0.00345 3.968-0.006897 7.9364 0.00517 11.904 0.043388 0.62851 0.69346 0.98513 1.272 0.89776h2.5896c-0.77174-0.5015-1.2078-1.2613-1.3143-2.3356-0.11601-1.1701 0.63729-2.024 1.6748-3.1566 0.65335-0.71326 1.4757-1.5822 2.3587-2.3316 0.76308-0.64765 1.7509-1.679 2.9376-2.578 0.91259-0.69136 2.2893-0.74691 3.1014-0.33143 0.91184 0.46649 1.2635 1.1209 1.4067 1.3826-0.0052-2.335-0.02135-1.3526-0.03955-3.6863 5e-3 -0.64349-0.67497-1.0568-1.2694-0.96289z" fill="#8ea6f4" fill-opacity=".99608"/> +</svg> diff --git a/editor/icons/NavigationLink3D.svg b/editor/icons/NavigationLink3D.svg new file mode 100644 index 0000000000..ea4092c2c7 --- /dev/null +++ b/editor/icons/NavigationLink3D.svg @@ -0,0 +1,4 @@ +<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m12.386 5.3097c-0.69157-0.021112-1.3071 0.36382-1.7492 0.86685-0.58 0.58-1.16 1.16-1.74 1.74 0.4588-0.28502 1.0599-0.064948 1.4771-0.037996 0.45549-0.44357 0.89024-0.91006 1.3596-1.3383 0.56256-0.44564 1.4906-0.15731 1.7028 0.52802 0.18967 0.4871-0.049221 1.0098-0.43284 1.3208-0.70048 0.68896-1.3789 1.4022-2.0935 2.0755-0.47999 0.3725-1.2044 0.226-1.5679-0.24034-0.38763-0.38194-1.0641 0.16031-0.78317 0.6241 0.6767 0.94379 2.1573 1.1282 3.0411 0.36751 0.80287-0.7704 1.5793-1.5696 2.3665-2.3564 0.79925-0.83719 0.70104-2.3112-0.19552-3.0393-0.38108-0.32877-0.8822-0.5119-1.385-0.51049zm-3.051 3.051c-0.69157-0.021106-1.3071 0.36382-1.7492 0.86685-0.67513 0.68452-1.37 1.3506-2.0319 2.0474-0.75433 0.87744-0.58087 2.3428 0.34933 3.0252 0.84748 0.68613 2.192 0.54839 2.8998-0.27341 0.63032-0.63031 1.2606-1.2606 1.8909-1.8909-0.4587 0.28554-1.0602 0.0659-1.477 0.038069-0.45445 0.44348-0.88773 0.91034-1.3564 1.3383-0.56256 0.44565-1.4906 0.15731-1.7028-0.52802-0.18967-0.4871 0.049229-1.0098 0.43284-1.3208 0.70048-0.68896 1.3789-1.4022 2.0935-2.0755 0.48-0.3725 1.2044-0.22601 1.5679 0.24036 0.38733 0.38325 1.064-0.16067 0.78313-0.6241-0.39353-0.52481-1.0429-0.84871-1.7002-0.8434z" fill="#fc7e7e" fill-opacity=".99608" stroke-linecap="round" stroke-linejoin="round" stroke-width=".013911"/> +<path d="m2 1c-0.61942-0.0066969-1.0877 0.60314-1 1.198 0.00345 3.968-0.006897 7.9364 0.00517 11.904 0.043388 0.62851 0.69346 0.98513 1.272 0.89776h2.5896c-0.77174-0.5015-1.2078-1.2613-1.3143-2.3356-0.11601-1.1701 0.63729-2.024 1.6748-3.1566 0.65335-0.71326 1.4757-1.5822 2.3587-2.3316 0.76308-0.64765 1.7509-1.679 2.9376-2.578 0.91259-0.69136 2.2893-0.74691 3.1014-0.33143 0.91184 0.46649 1.2635 1.1209 1.4067 1.3826-0.0052-2.335-0.02135-1.3526-0.03955-3.6863 5e-3 -0.64349-0.67497-1.0568-1.2694-0.96289z" fill="#fc7d7d" fill-opacity=".99608"/> +</svg> diff --git a/editor/import/resource_importer_bitmask.cpp b/editor/import/resource_importer_bitmask.cpp index c03962b8a4..577a4c32b3 100644 --- a/editor/import/resource_importer_bitmask.cpp +++ b/editor/import/resource_importer_bitmask.cpp @@ -99,7 +99,7 @@ Error ResourceImporterBitMap::import(const String &p_source_file, const String & bit = c.a > threshold; } - bitmap->set_bit(Vector2(j, i), bit); + bitmap->set_bit(j, i, bit); } } diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 369ab0745e..0ec36736bd 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -201,7 +201,7 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, for (int i = 0; i < bm->get_size().width; i++) { for (int j = 0; j < bm->get_size().height; j++) { - if (bm->get_bit(Point2i(i, j))) { + if (bm->get_bit(i, j)) { w[j * (int)bm->get_size().width + i] = 255; } else { w[j * (int)bm->get_size().width + i] = 0; diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index b5eca46ad3..e1d68d97b5 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -36,6 +36,7 @@ class GDExtensionExportPlugin : public EditorExportPlugin { protected: virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); + virtual String _get_name() const { return "GDExtension"; } }; void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { diff --git a/editor/plugins/navigation_link_2d_editor_plugin.cpp b/editor/plugins/navigation_link_2d_editor_plugin.cpp new file mode 100644 index 0000000000..b72f639fbf --- /dev/null +++ b/editor/plugins/navigation_link_2d_editor_plugin.cpp @@ -0,0 +1,191 @@ +/*************************************************************************/ +/* navigation_link_2d_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "navigation_link_2d_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "servers/navigation_server_3d.h" + +void NavigationLink2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &NavigationLink2DEditor::_node_removed)); + } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &NavigationLink2DEditor::_node_removed)); + } break; + } +} + +void NavigationLink2DEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + } +} + +bool NavigationLink2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + if (!node || !node->is_visible_in_tree()) { + return false; + } + + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + // Start location + if (xform.xform(node->get_start_location()).distance_to(mb->get_position()) < grab_threshold) { + start_grabbed = true; + original_start_location = node->get_start_location(); + + return true; + } else { + start_grabbed = false; + } + + // End location + if (xform.xform(node->get_end_location()).distance_to(mb->get_position()) < grab_threshold) { + end_grabbed = true; + original_end_location = node->get_end_location(); + + return true; + } else { + end_grabbed = false; + } + } else { + if (start_grabbed) { + undo_redo->create_action(TTR("Set start_location")); + undo_redo->add_do_method(node, "set_start_location", node->get_start_location()); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(node, "set_start_location", original_start_location); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + start_grabbed = false; + + return true; + } + + if (end_grabbed) { + undo_redo->create_action(TTR("Set end_location")); + undo_redo->add_do_method(node, "set_end_location", node->get_end_location()); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(node, "set_end_location", original_end_location); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + end_grabbed = false; + + return true; + } + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + Vector2 point = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())); + point = node->get_global_transform().affine_inverse().xform(point); + + if (start_grabbed) { + node->set_start_location(point); + canvas_item_editor->update_viewport(); + + return true; + } + + if (end_grabbed) { + node->set_end_location(point); + canvas_item_editor->update_viewport(); + + return true; + } + } + + return false; +} + +void NavigationLink2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { + if (!node || !node->is_visible_in_tree()) { + return; + } + + Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + Vector2 global_start_location = gt.xform(node->get_start_location()); + Vector2 global_end_location = gt.xform(node->get_end_location()); + + // Only drawing the handles here, since the debug rendering will fill in the rest. + const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + p_overlay->draw_texture(handle, global_start_location - handle->get_size() / 2); + p_overlay->draw_texture(handle, global_end_location - handle->get_size() / 2); +} + +void NavigationLink2DEditor::edit(NavigationLink2D *p_node) { + if (!canvas_item_editor) { + canvas_item_editor = CanvasItemEditor::get_singleton(); + } + + if (p_node) { + node = p_node; + } else { + node = nullptr; + } + + canvas_item_editor->update_viewport(); +} + +NavigationLink2DEditor::NavigationLink2DEditor() { + undo_redo = EditorNode::get_undo_redo(); +} + +/////////////////////// + +void NavigationLink2DEditorPlugin::edit(Object *p_object) { + editor->edit(Object::cast_to<NavigationLink2D>(p_object)); +} + +bool NavigationLink2DEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<NavigationLink2D>(p_object) != nullptr; +} + +void NavigationLink2DEditorPlugin::make_visible(bool p_visible) { + if (!p_visible) { + edit(nullptr); + } +} + +NavigationLink2DEditorPlugin::NavigationLink2DEditorPlugin() { + editor = memnew(NavigationLink2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(editor); +} diff --git a/editor/plugins/navigation_link_2d_editor_plugin.h b/editor/plugins/navigation_link_2d_editor_plugin.h new file mode 100644 index 0000000000..1c1251bec7 --- /dev/null +++ b/editor/plugins/navigation_link_2d_editor_plugin.h @@ -0,0 +1,83 @@ +/*************************************************************************/ +/* navigation_link_2d_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 NAVIGATION_LINK_2D_EDITOR_PLUGIN_H +#define NAVIGATION_LINK_2D_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "scene/2d/navigation_link_2d.h" + +class CanvasItemEditor; +class EditorUndoRedoManager; + +class NavigationLink2DEditor : public Control { + GDCLASS(NavigationLink2DEditor, Control); + + Ref<EditorUndoRedoManager> undo_redo; + CanvasItemEditor *canvas_item_editor = nullptr; + NavigationLink2D *node; + + bool start_grabbed = false; + Vector2 original_start_location; + + bool end_grabbed = false; + Vector2 original_end_location; + +protected: + void _notification(int p_what); + void _node_removed(Node *p_node); + +public: + bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); + void forward_canvas_draw_over_viewport(Control *p_overlay); + void edit(NavigationLink2D *p_node); + + NavigationLink2DEditor(); +}; + +class NavigationLink2DEditorPlugin : public EditorPlugin { + GDCLASS(NavigationLink2DEditorPlugin, EditorPlugin); + + NavigationLink2DEditor *editor = nullptr; + +public: + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return editor->forward_canvas_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { editor->forward_canvas_draw_over_viewport(p_overlay); } + + virtual String get_name() const override { return "NavigationLink2D"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + NavigationLink2DEditorPlugin(); +}; + +#endif // NAVIGATION_LINK_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 0c27ed46c5..ec6ea7f39b 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -54,6 +54,7 @@ #include "scene/3d/lightmap_probe.h" #include "scene/3d/marker_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/navigation_link_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/occluder_instance_3d.h" #include "scene/3d/ray_cast_3d.h" @@ -4999,6 +5000,175 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } +//// + +NavigationLink3DGizmoPlugin::NavigationLink3DGizmoPlugin() { + create_material("navigation_link_material", NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_color()); + create_material("navigation_link_material_disabled", NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_disabled_color()); + create_handle_material("handles"); +} + +bool NavigationLink3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<NavigationLink3D>(p_spatial) != nullptr; +} + +String NavigationLink3DGizmoPlugin::get_gizmo_name() const { + return "NavigationLink3D"; +} + +int NavigationLink3DGizmoPlugin::get_priority() const { + return -1; +} + +void NavigationLink3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + + RID nav_map = link->get_world_3d()->get_navigation_map(); + real_t search_radius = NavigationServer3D::get_singleton()->map_get_link_connection_radius(nav_map); + Vector3 up_vector = NavigationServer3D::get_singleton()->map_get_up(nav_map); + Vector3::Axis up_axis = up_vector.max_axis_index(); + + Vector3 start_location = link->get_start_location(); + Vector3 end_location = link->get_end_location(); + + Ref<Material> link_material = get_material("navigation_link_material", p_gizmo); + Ref<Material> link_material_disabled = get_material("navigation_link_material_disabled", p_gizmo); + Ref<Material> handles_material = get_material("handles"); + + p_gizmo->clear(); + + // Draw line between the points. + Vector<Vector3> lines; + lines.append(start_location); + lines.append(end_location); + + // Draw start location search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(start_location + Vector3(0, a.x, a.y)); + lines.append(start_location + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(start_location + Vector3(a.x, 0, a.y)); + lines.append(start_location + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(start_location + Vector3(a.x, a.y, 0)); + lines.append(start_location + Vector3(b.x, b.y, 0)); + break; + } + } + + // Draw end location search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(end_location + Vector3(0, a.x, a.y)); + lines.append(end_location + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(end_location + Vector3(a.x, 0, a.y)); + lines.append(end_location + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(end_location + Vector3(a.x, a.y, 0)); + lines.append(end_location + Vector3(b.x, b.y, 0)); + break; + } + } + + p_gizmo->add_lines(lines, link->is_enabled() ? link_material : link_material_disabled); + p_gizmo->add_collision_segments(lines); + + Vector<Vector3> handles; + handles.append(start_location); + handles.append(end_location); + p_gizmo->add_handles(handles, handles_material); +} + +String NavigationLink3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return p_id == 0 ? TTR("Start Location") : TTR("End Location"); +} + +Variant NavigationLink3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + return p_id == 0 ? link->get_start_location() : link->get_end_location(); +} + +void NavigationLink3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + + Transform3D gt = link->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + + Transform3D ct = p_camera->get_global_transform(); + Vector3 cam_dir = ct.basis.get_column(Vector3::AXIS_Z); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 location = p_id == 0 ? link->get_start_location() : link->get_end_location(); + Plane move_plane = Plane(cam_dir, gt.xform(location)); + + Vector3 intersection; + if (!move_plane.intersects_ray(ray_from, ray_dir, &intersection)) { + return; + } + + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + double snap = Node3DEditor::get_singleton()->get_translate_snap(); + intersection.snap(Vector3(snap, snap, snap)); + } + + location = gi.xform(intersection); + if (p_id == 0) { + link->set_start_location(location); + } else if (p_id == 1) { + link->set_end_location(location); + } +} + +void NavigationLink3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + + if (p_cancel) { + if (p_id == 0) { + link->set_start_location(p_restore); + } else { + link->set_end_location(p_restore); + } + return; + } + + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); + if (p_id == 0) { + ur->create_action(TTR("Change Start Location")); + ur->add_do_method(link, "set_start_location", link->get_start_location()); + ur->add_undo_method(link, "set_start_location", p_restore); + } else { + ur->create_action(TTR("Change End Location")); + ur->add_do_method(link, "set_end_location", link->get_end_location()); + ur->add_undo_method(link, "set_end_location", p_restore); + } + + ur->commit_action(); +} + ////// #define BODY_A_RADIUS 0.25 diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 1b6485ac4e..5924f8571a 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -631,6 +631,23 @@ public: NavigationRegion3DGizmoPlugin(); }; +class NavigationLink3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(NavigationLink3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + + NavigationLink3DGizmoPlugin(); +}; + class JointGizmosDrawer { public: static Basis look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 1a704a5777..0bb044e679 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -7523,6 +7523,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin))); add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin))); add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin))); + add_gizmo_plugin(Ref<NavigationLink3DGizmoPlugin>(memnew(NavigationLink3DGizmoPlugin))); add_gizmo_plugin(Ref<NavigationRegion3DGizmoPlugin>(memnew(NavigationRegion3DGizmoPlugin))); add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin))); add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin))); diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 059ca703ab..19a8b59c6f 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -88,6 +88,8 @@ public: // TODO: Re-add compiled GDScript on export. return; } + + virtual String _get_name() const override { return "GDScript"; } }; static void _editor_init() { diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 4191e46f62..9e5d666a51 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -210,6 +210,20 @@ real_t GodotNavigationServer::map_get_edge_connection_margin(RID p_map) const { return map->get_edge_connection_margin(); } +COMMAND_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius) { + NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND(map == nullptr); + + map->set_link_connection_radius(p_connection_radius); +} + +real_t GodotNavigationServer::map_get_link_connection_radius(RID p_map) const { + const NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND_V(map == nullptr, 0); + + return map->get_link_connection_radius(); +} + Vector<Vector3> GodotNavigationServer::map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers) const { const NavMap *map = map_owner.get_or_null(p_map); ERR_FAIL_COND_V(map == nullptr, Vector<Vector3>()); @@ -245,6 +259,20 @@ RID GodotNavigationServer::map_get_closest_point_owner(RID p_map, const Vector3 return map->get_closest_point_owner(p_point); } +TypedArray<RID> GodotNavigationServer::map_get_links(RID p_map) const { + TypedArray<RID> link_rids; + const NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND_V(map == nullptr, link_rids); + + const LocalVector<NavLink *> links = map->get_links(); + link_rids.resize(links.size()); + + for (uint32_t i = 0; i < links.size(); i++) { + link_rids[i] = links[i]->get_self(); + } + return link_rids; +} + TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const { TypedArray<RID> regions_rids; const NavMap *map = map_owner.get_or_null(p_map); @@ -417,6 +445,131 @@ Vector3 GodotNavigationServer::region_get_connection_pathway_end(RID p_region, i return region->get_connection_pathway_end(p_connection_id); } +RID GodotNavigationServer::link_create() const { + GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this); + MutexLock lock(mut_this->operations_mutex); + RID rid = link_owner.make_rid(); + NavLink *link = link_owner.get_or_null(rid); + link->set_self(rid); + return rid; +} + +COMMAND_2(link_set_map, RID, p_link, RID, p_map) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + if (link->get_map() != nullptr) { + if (link->get_map()->get_self() == p_map) { + return; // Pointless + } + + link->get_map()->remove_link(link); + link->set_map(nullptr); + } + + if (p_map.is_valid()) { + NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND(map == nullptr); + + map->add_link(link); + link->set_map(map); + } +} + +RID GodotNavigationServer::link_get_map(const RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, RID()); + + if (link->get_map()) { + return link->get_map()->get_self(); + } + return RID(); +} + +COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_bidirectional(p_bidirectional); +} + +bool GodotNavigationServer::link_is_bidirectional(RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, false); + + return link->is_bidirectional(); +} + +COMMAND_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_navigation_layers(p_navigation_layers); +} + +uint32_t GodotNavigationServer::link_get_navigation_layers(const RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, 0); + + return link->get_navigation_layers(); +} + +COMMAND_2(link_set_start_location, RID, p_link, Vector3, p_location) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_start_location(p_location); +} + +Vector3 GodotNavigationServer::link_get_start_location(RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, Vector3()); + + return link->get_start_location(); +} + +COMMAND_2(link_set_end_location, RID, p_link, Vector3, p_location) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_end_location(p_location); +} + +Vector3 GodotNavigationServer::link_get_end_location(RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, Vector3()); + + return link->get_end_location(); +} + +COMMAND_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_enter_cost(p_enter_cost); +} + +real_t GodotNavigationServer::link_get_enter_cost(const RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, 0); + + return link->get_enter_cost(); +} + +COMMAND_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_travel_cost(p_travel_cost); +} + +real_t GodotNavigationServer::link_get_travel_cost(const RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, 0); + + return link->get_travel_cost(); +} + RID GodotNavigationServer::agent_create() const { GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this); MutexLock lock(mut_this->operations_mutex); @@ -549,6 +702,13 @@ COMMAND_1(free, RID, p_object) { regions[i]->set_map(nullptr); } + // Removes any assigned links + LocalVector<NavLink *> links = map->get_links(); + for (uint32_t i = 0; i < links.size(); i++) { + map->remove_link(links[i]); + links[i]->set_map(nullptr); + } + // Remove any assigned agent LocalVector<RvoAgent *> agents = map->get_agents(); for (uint32_t i = 0; i < agents.size(); i++) { @@ -572,6 +732,17 @@ COMMAND_1(free, RID, p_object) { region_owner.free(p_object); + } else if (link_owner.owns(p_object)) { + NavLink *link = link_owner.get_or_null(p_object); + + // Removes this link from the map if assigned + if (link->get_map() != nullptr) { + link->get_map()->remove_link(link); + link->set_map(nullptr); + } + + link_owner.free(p_object); + } else if (agent_owner.owns(p_object)) { RvoAgent *agent = agent_owner.get_or_null(p_object); diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 05ba46ede1..e6ef7e3bb1 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -36,6 +36,7 @@ #include "core/templates/rid_owner.h" #include "servers/navigation_server_3d.h" +#include "nav_link.h" #include "nav_map.h" #include "nav_region.h" #include "rvo_agent.h" @@ -71,6 +72,7 @@ class GodotNavigationServer : public NavigationServer3D { LocalVector<SetCommand *> commands; + mutable RID_Owner<NavLink> link_owner; mutable RID_Owner<NavMap> map_owner; mutable RID_Owner<NavRegion> region_owner; mutable RID_Owner<RvoAgent> agent_owner; @@ -100,6 +102,9 @@ public: COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin); virtual real_t map_get_edge_connection_margin(RID p_map) const override; + COMMAND_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius); + virtual real_t map_get_link_connection_radius(RID p_map) const override; + virtual Vector<Vector3> map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override; virtual Vector3 map_get_closest_point_to_segment(RID p_map, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision = false) const override; @@ -107,6 +112,7 @@ public: virtual Vector3 map_get_closest_point_normal(RID p_map, const Vector3 &p_point) const override; virtual RID map_get_closest_point_owner(RID p_map, const Vector3 &p_point) const override; + virtual TypedArray<RID> map_get_links(RID p_map) const override; virtual TypedArray<RID> map_get_regions(RID p_map) const override; virtual TypedArray<RID> map_get_agents(RID p_map) const override; @@ -132,6 +138,22 @@ public: virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override; virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; + virtual RID link_create() const override; + COMMAND_2(link_set_map, RID, p_link, RID, p_map); + virtual RID link_get_map(RID p_link) const override; + COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional); + virtual bool link_is_bidirectional(RID p_link) const override; + COMMAND_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers); + virtual uint32_t link_get_navigation_layers(RID p_link) const override; + COMMAND_2(link_set_start_location, RID, p_link, Vector3, p_location); + virtual Vector3 link_get_start_location(RID p_link) const override; + COMMAND_2(link_set_end_location, RID, p_link, Vector3, p_location); + virtual Vector3 link_get_end_location(RID p_link) const override; + COMMAND_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost); + virtual real_t link_get_enter_cost(RID p_link) const override; + COMMAND_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost); + virtual real_t link_get_travel_cost(RID p_link) const override; + virtual RID agent_create() const override; COMMAND_2(agent_set_map, RID, p_agent, RID, p_map); virtual RID agent_get_map(RID p_agent) const override; diff --git a/modules/navigation/nav_base.h b/modules/navigation/nav_base.h new file mode 100644 index 0000000000..6dfaaf9af4 --- /dev/null +++ b/modules/navigation/nav_base.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* nav_base.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 NAV_BASE_H +#define NAV_BASE_H + +#include "nav_rid.h" +#include "nav_utils.h" + +class NavMap; + +class NavBase : public NavRid { +protected: + uint32_t navigation_layers = 1; + float enter_cost = 0.0; + float travel_cost = 1.0; + +public: + void set_navigation_layers(uint32_t p_navigation_layers) { navigation_layers = p_navigation_layers; } + uint32_t get_navigation_layers() const { return navigation_layers; } + + void set_enter_cost(float p_enter_cost) { enter_cost = MAX(p_enter_cost, 0.0); } + float get_enter_cost() const { return enter_cost; } + + void set_travel_cost(float p_travel_cost) { travel_cost = MAX(p_travel_cost, 0.0); } + float get_travel_cost() const { return travel_cost; } +}; + +#endif // NAV_BASE_H diff --git a/modules/navigation/nav_link.cpp b/modules/navigation/nav_link.cpp new file mode 100644 index 0000000000..828b131ec6 --- /dev/null +++ b/modules/navigation/nav_link.cpp @@ -0,0 +1,60 @@ +/*************************************************************************/ +/* nav_link.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "nav_link.h" + +#include "nav_map.h" + +void NavLink::set_map(NavMap *p_map) { + map = p_map; + link_dirty = true; +} + +void NavLink::set_bidirectional(bool p_bidirectional) { + bidirectional = p_bidirectional; + link_dirty = true; +} + +void NavLink::set_start_location(const Vector3 p_location) { + start_location = p_location; + link_dirty = true; +} + +void NavLink::set_end_location(const Vector3 p_location) { + end_location = p_location; + link_dirty = true; +} + +bool NavLink::check_dirty() { + const bool was_dirty = link_dirty; + + link_dirty = false; + return was_dirty; +} diff --git a/modules/navigation/nav_link.h b/modules/navigation/nav_link.h new file mode 100644 index 0000000000..8d57f076c0 --- /dev/null +++ b/modules/navigation/nav_link.h @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* nav_link.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 NAV_LINK_H +#define NAV_LINK_H + +#include "nav_base.h" +#include "nav_utils.h" + +class NavLink : public NavBase { + NavMap *map = nullptr; + bool bidirectional = true; + Vector3 start_location = Vector3(); + Vector3 end_location = Vector3(); + + bool link_dirty = true; + +public: + void set_map(NavMap *p_map); + NavMap *get_map() const { + return map; + } + + void set_bidirectional(bool p_bidirectional); + bool is_bidirectional() const { + return bidirectional; + } + + void set_start_location(Vector3 p_location); + Vector3 get_start_location() const { + return start_location; + } + + void set_end_location(Vector3 p_location); + Vector3 get_end_location() const { + return end_location; + } + + bool check_dirty(); +}; + +#endif // NAV_LINK_H diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 49029b5513..100db9bc82 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -31,6 +31,7 @@ #include "nav_map.h" #include "core/object/worker_thread_pool.h" +#include "nav_link.h" #include "nav_region.h" #include "rvo_agent.h" #include <algorithm> @@ -52,6 +53,11 @@ void NavMap::set_edge_connection_margin(float p_edge_connection_margin) { regenerate_links = true; } +void NavMap::set_link_connection_radius(float p_link_connection_radius) { + link_connection_radius = p_link_connection_radius; + regenerate_links = true; +} + gd::PointKey NavMap::get_point_key(const Vector3 &p_pos) const { const int x = int(Math::floor(p_pos.x / cell_size)); const int y = int(Math::floor(p_pos.y / cell_size)); @@ -158,17 +164,17 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p continue; } - float region_enter_cost = 0.0; - float region_travel_cost = least_cost_poly->poly->owner->get_travel_cost(); + float poly_enter_cost = 0.0; + float poly_travel_cost = least_cost_poly->poly->owner->get_travel_cost(); - if (prev_least_cost_poly != nullptr && !(prev_least_cost_poly->poly->owner->get_self() == least_cost_poly->poly->owner->get_self())) { - region_enter_cost = least_cost_poly->poly->owner->get_enter_cost(); + if (prev_least_cost_poly != nullptr && (prev_least_cost_poly->poly->owner->get_self() != least_cost_poly->poly->owner->get_self())) { + poly_enter_cost = least_cost_poly->poly->owner->get_enter_cost(); } prev_least_cost_poly = least_cost_poly; Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end }; const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly->entry, pathway); - const float new_distance = (least_cost_poly->entry.distance_to(new_entry) * region_travel_cost) + region_enter_cost + least_cost_poly->traveled_distance; + const float new_distance = (least_cost_poly->entry.distance_to(new_entry) * poly_travel_cost) + poly_enter_cost + least_cost_poly->traveled_distance; int64_t already_visited_polygon_index = navigation_polys.find(gd::NavigationPoly(connection.polygon)); @@ -360,10 +366,15 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p // Add mid points int np_id = least_cost_id; while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) { - int prev = navigation_polys[np_id].back_navigation_edge; - int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size(); - Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5; - path.push_back(point); + if (navigation_polys[np_id].back_navigation_edge != -1) { + int prev = navigation_polys[np_id].back_navigation_edge; + int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size(); + Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5; + path.push_back(point); + } else { + path.push_back(navigation_polys[np_id].entry); + } + np_id = navigation_polys[np_id].back_navigation_poly_id; } @@ -475,6 +486,19 @@ void NavMap::remove_region(NavRegion *p_region) { } } +void NavMap::add_link(NavLink *p_link) { + links.push_back(p_link); + regenerate_links = true; +} + +void NavMap::remove_link(NavLink *p_link) { + int64_t link_index = links.find(p_link); + if (link_index != -1) { + links.remove_at_unordered(link_index); + regenerate_links = true; + } +} + bool NavMap::has_agent(RvoAgent *agent) const { return (agents.find(agent) != -1); } @@ -526,6 +550,12 @@ void NavMap::sync() { } } + for (uint32_t l = 0; l < links.size(); l++) { + if (links[l]->check_dirty()) { + regenerate_links = true; + } + } + if (regenerate_links) { // Remove regions connections. for (uint32_t r = 0; r < regions.size(); r++) { @@ -651,7 +681,121 @@ void NavMap::sync() { free_edge.polygon->edges[free_edge.edge].connections.push_back(new_connection); // Add the connection to the region_connection map. - free_edge.polygon->owner->get_connections().push_back(new_connection); + ((NavRegion *)free_edge.polygon->owner)->get_connections().push_back(new_connection); + } + } + + uint32_t link_poly_idx = 0; + link_polygons.resize(links.size()); + + // Search for polygons within range of a nav link. + for (uint32_t l = 0; l < links.size(); l++) { + const NavLink *link = links[l]; + const Vector3 start = link->get_start_location(); + const Vector3 end = link->get_end_location(); + + gd::Polygon *closest_start_polygon = nullptr; + real_t closest_start_distance = link_connection_radius; + Vector3 closest_start_point; + + gd::Polygon *closest_end_polygon = nullptr; + real_t closest_end_distance = link_connection_radius; + Vector3 closest_end_point; + + // Create link to any polygons within the search radius of the start point. + for (uint32_t start_index = 0; start_index < polygons.size(); start_index++) { + gd::Polygon &start_poly = polygons[start_index]; + + // For each face check the distance to the start + for (uint32_t start_point_id = 2; start_point_id < start_poly.points.size(); start_point_id += 1) { + const Face3 start_face(start_poly.points[0].pos, start_poly.points[start_point_id - 1].pos, start_poly.points[start_point_id].pos); + const Vector3 start_point = start_face.get_closest_point_to(start); + const real_t start_distance = start_point.distance_to(start); + + // Pick the polygon that is within our radius and is closer than anything we've seen yet. + if (start_distance <= link_connection_radius && start_distance < closest_start_distance) { + closest_start_distance = start_distance; + closest_start_point = start_point; + closest_start_polygon = &start_poly; + } + } + } + + // Find any polygons within the search radius of the end point. + for (uint32_t end_index = 0; end_index < polygons.size(); end_index++) { + gd::Polygon &end_poly = polygons[end_index]; + + // For each face check the distance to the end + for (uint32_t end_point_id = 2; end_point_id < end_poly.points.size(); end_point_id += 1) { + const Face3 end_face(end_poly.points[0].pos, end_poly.points[end_point_id - 1].pos, end_poly.points[end_point_id].pos); + const Vector3 end_point = end_face.get_closest_point_to(end); + const real_t end_distance = end_point.distance_to(end); + + // Pick the polygon that is within our radius and is closer than anything we've seen yet. + if (end_distance <= link_connection_radius && end_distance < closest_end_distance) { + closest_end_distance = end_distance; + closest_end_point = end_point; + closest_end_polygon = &end_poly; + } + } + } + + // If we have both a start and end point, then create a synthetic polygon to route through. + if (closest_start_polygon && closest_end_polygon) { + gd::Polygon &new_polygon = link_polygons[link_poly_idx++]; + new_polygon.owner = link; + + new_polygon.edges.clear(); + new_polygon.edges.resize(4); + new_polygon.points.clear(); + new_polygon.points.reserve(4); + + // Build a set of vertices that create a thin polygon going from the start to the end point. + new_polygon.points.push_back({ closest_start_point, get_point_key(closest_start_point) }); + new_polygon.points.push_back({ closest_start_point, get_point_key(closest_start_point) }); + new_polygon.points.push_back({ closest_end_point, get_point_key(closest_end_point) }); + new_polygon.points.push_back({ closest_end_point, get_point_key(closest_end_point) }); + + Vector3 center; + for (int p = 0; p < 4; ++p) { + center += new_polygon.points[p].pos; + } + new_polygon.center = center / real_t(new_polygon.points.size()); + new_polygon.clockwise = true; + + // Setup connections to go forward in the link. + { + gd::Edge::Connection entry_connection; + entry_connection.polygon = &new_polygon; + entry_connection.edge = -1; + entry_connection.pathway_start = new_polygon.points[0].pos; + entry_connection.pathway_end = new_polygon.points[1].pos; + closest_start_polygon->edges[0].connections.push_back(entry_connection); + + gd::Edge::Connection exit_connection; + exit_connection.polygon = closest_end_polygon; + exit_connection.edge = -1; + exit_connection.pathway_start = new_polygon.points[2].pos; + exit_connection.pathway_end = new_polygon.points[3].pos; + new_polygon.edges[2].connections.push_back(exit_connection); + } + + // If the link is bi-directional, create connections from the end to the start. + if (link->is_bidirectional()) { + gd::Edge::Connection entry_connection; + entry_connection.polygon = &new_polygon; + entry_connection.edge = -1; + entry_connection.pathway_start = new_polygon.points[2].pos; + entry_connection.pathway_end = new_polygon.points[3].pos; + closest_end_polygon->edges[0].connections.push_back(entry_connection); + + gd::Edge::Connection exit_connection; + exit_connection.polygon = closest_start_polygon; + exit_connection.edge = -1; + exit_connection.pathway_start = new_polygon.points[0].pos; + exit_connection.pathway_end = new_polygon.points[1].pos; + new_polygon.edges[0].connections.push_back(exit_connection); + } } } diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index e50a1afbe9..a3da9fa727 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -40,9 +40,9 @@ #include <KdTree.h> +class NavLink; class NavRegion; class RvoAgent; -class NavRegion; class NavMap : public NavRid { /// Map Up @@ -55,11 +55,19 @@ class NavMap : public NavRid { /// This value is used to detect the near edges to connect. real_t edge_connection_margin = 0.25; + /// This value is used to limit how far links search to find polygons to connect to. + real_t link_connection_radius = 1.0; + bool regenerate_polygons = true; bool regenerate_links = true; + /// Map regions LocalVector<NavRegion *> regions; + /// Map links + LocalVector<NavLink *> links; + LocalVector<gd::Polygon> link_polygons; + /// Map polygons LocalVector<gd::Polygon> polygons; @@ -100,6 +108,11 @@ public: return edge_connection_margin; } + void set_link_connection_radius(float p_link_connection_radius); + float get_link_connection_radius() const { + return link_connection_radius; + } + gd::PointKey get_point_key(const Vector3 &p_pos) const; Vector<Vector3> get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const; @@ -115,6 +128,12 @@ public: return regions; } + void add_link(NavLink *p_link); + void remove_link(NavLink *p_link); + const LocalVector<NavLink *> &get_links() const { + return links; + } + bool has_agent(RvoAgent *agent) const; void add_agent(RvoAgent *agent); void remove_agent(RvoAgent *agent); diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index 88740807eb..d43f53d1c0 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -40,14 +40,6 @@ void NavRegion::set_map(NavMap *p_map) { } } -void NavRegion::set_navigation_layers(uint32_t p_navigation_layers) { - navigation_layers = p_navigation_layers; -} - -uint32_t NavRegion::get_navigation_layers() const { - return navigation_layers; -} - void NavRegion::set_transform(Transform3D p_transform) { transform = p_transform; polygons_dirty = true; diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h index c9d2d80f6c..8d2b5aa9eb 100644 --- a/modules/navigation/nav_region.h +++ b/modules/navigation/nav_region.h @@ -33,21 +33,13 @@ #include "scene/resources/navigation_mesh.h" -#include "nav_rid.h" +#include "nav_base.h" #include "nav_utils.h" -#include <vector> - -class NavMap; -class NavRegion; - -class NavRegion : public NavRid { +class NavRegion : public NavBase { NavMap *map = nullptr; Transform3D transform; Ref<NavigationMesh> mesh; - uint32_t navigation_layers = 1; - float enter_cost = 0.0; - float travel_cost = 1.0; Vector<gd::Edge::Connection> connections; bool polygons_dirty = true; @@ -67,15 +59,6 @@ public: return map; } - void set_enter_cost(float p_enter_cost) { enter_cost = MAX(p_enter_cost, 0.0); } - float get_enter_cost() const { return enter_cost; } - - void set_travel_cost(float p_travel_cost) { travel_cost = MAX(p_travel_cost, 0.0); } - float get_travel_cost() const { return travel_cost; } - - void set_navigation_layers(uint32_t p_navigation_layers); - uint32_t get_navigation_layers() const; - void set_transform(Transform3D transform); const Transform3D &get_transform() const { return transform; diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h index 47f04b6a75..16b96dcfe9 100644 --- a/modules/navigation/nav_utils.h +++ b/modules/navigation/nav_utils.h @@ -35,9 +35,8 @@ #include "core/templates/hash_map.h" #include "core/templates/hashfuncs.h" #include "core/templates/local_vector.h" -#include <vector> -class NavRegion; +class NavBase; namespace gd { struct Polygon; @@ -79,26 +78,33 @@ struct Point { }; struct Edge { - /// This edge ID - int this_edge = -1; - /// The gateway in the edge, as, in some case, the whole edge might not be navigable. struct Connection { + /// Polygon that this connection leads to. Polygon *polygon = nullptr; + + /// Edge of the source polygon where this connection starts from. int edge = -1; + + /// Point on the edge where the gateway leading to the poly starts. Vector3 pathway_start; + + /// Point on the edge where the gateway leading to the poly ends. Vector3 pathway_end; }; + + /// Connections from this edge to other polygons. Vector<Connection> connections; }; struct Polygon { - NavRegion *owner = nullptr; + /// Navigation region or link that contains this polygon. + const NavBase *owner = nullptr; /// The points of this `Polygon` LocalVector<Point> points; - /// Are the points clockwise ? + /// Are the points clockwise? bool clockwise; /// The edges of this `Polygon` @@ -115,7 +121,7 @@ struct NavigationPoly { /// Those 4 variables are used to travel the path backwards. int back_navigation_poly_id = -1; - uint32_t back_navigation_edge = UINT32_MAX; + int back_navigation_edge = -1; Vector3 back_navigation_edge_pathway_start; Vector3 back_navigation_edge_pathway_end; diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp new file mode 100644 index 0000000000..d1aeea3efa --- /dev/null +++ b/scene/2d/navigation_link_2d.cpp @@ -0,0 +1,284 @@ +/*************************************************************************/ +/* navigation_link_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "navigation_link_2d.h" + +#include "core/math/geometry_2d.h" +#include "scene/resources/world_2d.h" +#include "servers/navigation_server_2d.h" +#include "servers/navigation_server_3d.h" + +void NavigationLink2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink2D::set_enabled); + ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink2D::is_enabled); + + ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink2D::set_bidirectional); + ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink2D::is_bidirectional); + + ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationLink2D::set_navigation_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationLink2D::get_navigation_layers); + + ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationLink2D::set_navigation_layer_value); + ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationLink2D::get_navigation_layer_value); + + ClassDB::bind_method(D_METHOD("set_start_location", "location"), &NavigationLink2D::set_start_location); + ClassDB::bind_method(D_METHOD("get_start_location"), &NavigationLink2D::get_start_location); + + ClassDB::bind_method(D_METHOD("set_end_location", "location"), &NavigationLink2D::set_end_location); + ClassDB::bind_method(D_METHOD("get_end_location"), &NavigationLink2D::get_end_location); + + ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationLink2D::set_enter_cost); + ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationLink2D::get_enter_cost); + + ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationLink2D::set_travel_cost); + ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationLink2D::get_travel_cost); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bidirectional"), "set_bidirectional", "is_bidirectional"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "start_location"), "set_start_location", "get_start_location"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "end_location"), "set_end_location", "get_end_location"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); +} + +void NavigationLink2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (enabled) { + NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map()); + + // Update global positions for the link. + Transform2D gt = get_global_transform(); + NavigationServer2D::get_singleton()->link_set_start_location(link, gt.xform(start_location)); + NavigationServer2D::get_singleton()->link_set_end_location(link, gt.xform(end_location)); + } + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + // Update global positions for the link. + Transform2D gt = get_global_transform(); + NavigationServer2D::get_singleton()->link_set_start_location(link, gt.xform(start_location)); + NavigationServer2D::get_singleton()->link_set_end_location(link, gt.xform(end_location)); + } break; + case NOTIFICATION_EXIT_TREE: { + NavigationServer2D::get_singleton()->link_set_map(link, RID()); + } break; + case NOTIFICATION_DRAW: { +#ifdef DEBUG_ENABLED + if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled())) { + Color color; + if (enabled) { + color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_color(); + } else { + color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_disabled_color(); + } + + real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map()); + + draw_line(get_start_location(), get_end_location(), color); + draw_arc(get_start_location(), radius, 0, Math_TAU, 10, color); + draw_arc(get_end_location(), radius, 0, Math_TAU, 10, color); + } +#endif // DEBUG_ENABLED + } break; + } +} + +#ifdef TOOLS_ENABLED +Rect2 NavigationLink2D::_edit_get_rect() const { + real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map()); + + Rect2 rect(get_start_location(), Size2()); + rect.expand_to(get_end_location()); + rect.grow_by(radius); + return rect; +} + +bool NavigationLink2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + Point2 segment[2] = { get_start_location(), get_end_location() }; + + Vector2 closest_point = Geometry2D::get_closest_point_to_segment(p_point, segment); + return p_point.distance_to(closest_point) < p_tolerance; +} +#endif // TOOLS_ENABLED + +void NavigationLink2D::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + return; + } + + enabled = p_enabled; + + if (!is_inside_tree()) { + return; + } + + if (!enabled) { + NavigationServer2D::get_singleton()->link_set_map(link, RID()); + } else { + NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map()); + } + +#ifdef DEBUG_ENABLED + if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) { + update(); + } +#endif // DEBUG_ENABLED +} + +void NavigationLink2D::set_bidirectional(bool p_bidirectional) { + if (bidirectional == p_bidirectional) { + return; + } + + bidirectional = p_bidirectional; + + NavigationServer2D::get_singleton()->link_set_bidirectional(link, bidirectional); +} + +void NavigationLink2D::set_navigation_layers(uint32_t p_navigation_layers) { + if (navigation_layers == p_navigation_layers) { + return; + } + + navigation_layers = p_navigation_layers; + + NavigationServer2D::get_singleton()->link_set_navigation_layers(link, navigation_layers); +} + +void NavigationLink2D::set_navigation_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + + uint32_t _navigation_layers = get_navigation_layers(); + + if (p_value) { + _navigation_layers |= 1 << (p_layer_number - 1); + } else { + _navigation_layers &= ~(1 << (p_layer_number - 1)); + } + + set_navigation_layers(_navigation_layers); +} + +bool NavigationLink2D::get_navigation_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + + return get_navigation_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationLink2D::set_start_location(Vector2 p_location) { + if (start_location.is_equal_approx(p_location)) { + return; + } + + start_location = p_location; + + if (!is_inside_tree()) { + return; + } + + Transform2D gt = get_global_transform(); + NavigationServer2D::get_singleton()->link_set_start_location(link, gt.xform(start_location)); + + update_configuration_warnings(); + +#ifdef DEBUG_ENABLED + if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) { + update(); + } +#endif // DEBUG_ENABLED +} + +void NavigationLink2D::set_end_location(Vector2 p_location) { + if (end_location.is_equal_approx(p_location)) { + return; + } + + end_location = p_location; + + if (!is_inside_tree()) { + return; + } + + Transform2D gt = get_global_transform(); + NavigationServer2D::get_singleton()->link_set_end_location(link, gt.xform(end_location)); + + update_configuration_warnings(); + +#ifdef DEBUG_ENABLED + if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) { + update(); + } +#endif // DEBUG_ENABLED +} + +void NavigationLink2D::set_enter_cost(real_t p_enter_cost) { + ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive."); + if (Math::is_equal_approx(enter_cost, p_enter_cost)) { + return; + } + + enter_cost = p_enter_cost; + + NavigationServer2D::get_singleton()->link_set_enter_cost(link, enter_cost); +} + +void NavigationLink2D::set_travel_cost(real_t p_travel_cost) { + ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive."); + if (Math::is_equal_approx(travel_cost, p_travel_cost)) { + return; + } + + travel_cost = p_travel_cost; + + NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost); +} + +TypedArray<String> NavigationLink2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (start_location.is_equal_approx(end_location)) { + warnings.push_back(RTR("NavigationLink2D start location should be different than the end location to be useful.")); + } + + return warnings; +} + +NavigationLink2D::NavigationLink2D() { + link = NavigationServer2D::get_singleton()->link_create(); + set_notify_transform(true); +} + +NavigationLink2D::~NavigationLink2D() { + NavigationServer2D::get_singleton()->free(link); + link = RID(); +} diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h new file mode 100644 index 0000000000..5990ea082c --- /dev/null +++ b/scene/2d/navigation_link_2d.h @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* navigation_link_2d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 NAVIGATION_LINK_2D_H +#define NAVIGATION_LINK_2D_H + +#include "scene/2d/node_2d.h" + +class NavigationLink2D : public Node2D { + GDCLASS(NavigationLink2D, Node2D); + + bool enabled = true; + RID link = RID(); + bool bidirectional = true; + uint32_t navigation_layers = 1; + Vector2 end_location = Vector2(); + Vector2 start_location = Vector2(); + real_t enter_cost = 0.0; + real_t travel_cost = 1.0; + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: +#ifdef TOOLS_ENABLED + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; +#endif + + void set_enabled(bool p_enabled); + bool is_enabled() const { return enabled; } + + void set_bidirectional(bool p_bidirectional); + bool is_bidirectional() const { return bidirectional; } + + void set_navigation_layers(uint32_t p_navigation_layers); + uint32_t get_navigation_layers() const { return navigation_layers; } + + void set_navigation_layer_value(int p_layer_number, bool p_value); + bool get_navigation_layer_value(int p_layer_number) const; + + void set_start_location(Vector2 p_location); + Vector2 get_start_location() const { return start_location; } + + void set_end_location(Vector2 p_location); + Vector2 get_end_location() const { return end_location; } + + void set_enter_cost(real_t p_enter_cost); + real_t get_enter_cost() const { return enter_cost; } + + void set_travel_cost(real_t p_travel_cost); + real_t get_travel_cost() const { return travel_cost; } + + TypedArray<String> get_configuration_warnings() const override; + + NavigationLink2D(); + ~NavigationLink2D(); +}; + +#endif // NAVIGATION_LINK_2D_H diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index b620f58ed7..a02f322ef1 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -264,7 +264,7 @@ bool TouchScreenButton::_is_point_inside(const Point2 &p_point) { if (bitmask.is_valid()) { check_rect = false; if (!touched && Rect2(Point2(), bitmask->get_size()).has_point(coord)) { - if (bitmask->get_bit(coord)) { + if (bitmask->get_bitv(coord)) { touched = true; } } diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp new file mode 100644 index 0000000000..47b602c966 --- /dev/null +++ b/scene/3d/navigation_link_3d.cpp @@ -0,0 +1,389 @@ +/*************************************************************************/ +/* navigation_link_3d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "navigation_link_3d.h" + +#include "mesh_instance_3d.h" +#include "servers/navigation_server_3d.h" + +#ifdef DEBUG_ENABLED +void NavigationLink3D::_update_debug_mesh() { + if (!is_inside_tree()) { + return; + } + + if (Engine::get_singleton()->is_editor_hint()) { + // don't update inside Editor as node 3d gizmo takes care of this + // as collisions and selections for Editor Viewport need to be updated + return; + } + + if (!NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (debug_instance.is_valid()) { + RS::get_singleton()->instance_set_visible(debug_instance, false); + } + return; + } + + if (!debug_instance.is_valid()) { + debug_instance = RenderingServer::get_singleton()->instance_create(); + } + + if (!debug_mesh.is_valid()) { + debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + } + + RID nav_map = get_world_3d()->get_navigation_map(); + real_t search_radius = NavigationServer3D::get_singleton()->map_get_link_connection_radius(nav_map); + Vector3 up_vector = NavigationServer3D::get_singleton()->map_get_up(nav_map); + Vector3::Axis up_axis = up_vector.max_axis_index(); + + debug_mesh->clear_surfaces(); + + Vector<Vector3> lines; + + // Draw line between the points. + lines.push_back(start_location); + lines.push_back(end_location); + + // Draw start location search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(start_location + Vector3(0, a.x, a.y)); + lines.append(start_location + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(start_location + Vector3(a.x, 0, a.y)); + lines.append(start_location + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(start_location + Vector3(a.x, a.y, 0)); + lines.append(start_location + Vector3(b.x, b.y, 0)); + break; + } + } + + // Draw end location search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(end_location + Vector3(0, a.x, a.y)); + lines.append(end_location + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(end_location + Vector3(a.x, 0, a.y)); + lines.append(end_location + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(end_location + Vector3(a.x, a.y, 0)); + lines.append(end_location + Vector3(b.x, b.y, 0)); + break; + } + } + + Array mesh_array; + mesh_array.resize(Mesh::ARRAY_MAX); + mesh_array[Mesh::ARRAY_VERTEX] = lines; + + debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, mesh_array); + + RS::get_singleton()->instance_set_base(debug_instance, debug_mesh->get_rid()); + RS::get_singleton()->instance_set_scenario(debug_instance, get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_visible(debug_instance, is_visible_in_tree()); + + Ref<StandardMaterial3D> link_material = NavigationServer3D::get_singleton_mut()->get_debug_navigation_link_connections_material(); + Ref<StandardMaterial3D> disabled_link_material = NavigationServer3D::get_singleton_mut()->get_debug_navigation_link_connections_disabled_material(); + + if (enabled) { + RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, link_material->get_rid()); + } else { + RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, disabled_link_material->get_rid()); + } +} +#endif // DEBUG_ENABLED + +void NavigationLink3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink3D::set_enabled); + ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink3D::is_enabled); + + ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink3D::set_bidirectional); + ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink3D::is_bidirectional); + + ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationLink3D::set_navigation_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationLink3D::get_navigation_layers); + + ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationLink3D::set_navigation_layer_value); + ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationLink3D::get_navigation_layer_value); + + ClassDB::bind_method(D_METHOD("set_start_location", "location"), &NavigationLink3D::set_start_location); + ClassDB::bind_method(D_METHOD("get_start_location"), &NavigationLink3D::get_start_location); + + ClassDB::bind_method(D_METHOD("set_end_location", "location"), &NavigationLink3D::set_end_location); + ClassDB::bind_method(D_METHOD("get_end_location"), &NavigationLink3D::get_end_location); + + ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationLink3D::set_enter_cost); + ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationLink3D::get_enter_cost); + + ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationLink3D::set_travel_cost); + ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationLink3D::get_travel_cost); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bidirectional"), "set_bidirectional", "is_bidirectional"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "start_location"), "set_start_location", "get_start_location"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "end_location"), "set_end_location", "get_end_location"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); +} + +void NavigationLink3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (enabled) { + NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map()); + + // Update global positions for the link. + Transform3D gt = get_global_transform(); + NavigationServer3D::get_singleton()->link_set_start_location(link, gt.xform(start_location)); + NavigationServer3D::get_singleton()->link_set_end_location(link, gt.xform(end_location)); + } + +#ifdef DEBUG_ENABLED + _update_debug_mesh(); +#endif // DEBUG_ENABLED + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + // Update global positions for the link. + Transform3D gt = get_global_transform(); + NavigationServer3D::get_singleton()->link_set_start_location(link, gt.xform(start_location)); + NavigationServer3D::get_singleton()->link_set_end_location(link, gt.xform(end_location)); + +#ifdef DEBUG_ENABLED + if (is_inside_tree() && debug_instance.is_valid()) { + RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform()); + } +#endif // DEBUG_ENABLED + } break; + case NOTIFICATION_EXIT_TREE: { + NavigationServer3D::get_singleton()->link_set_map(link, RID()); + +#ifdef DEBUG_ENABLED + if (debug_instance.is_valid()) { + RS::get_singleton()->instance_set_scenario(debug_instance, RID()); + RS::get_singleton()->instance_set_visible(debug_instance, false); + } +#endif // DEBUG_ENABLED + } break; + } +} + +NavigationLink3D::NavigationLink3D() { + link = NavigationServer3D::get_singleton()->link_create(); + set_notify_transform(true); +} + +NavigationLink3D::~NavigationLink3D() { + NavigationServer3D::get_singleton()->free(link); + link = RID(); + +#ifdef DEBUG_ENABLED + if (debug_instance.is_valid()) { + RenderingServer::get_singleton()->free(debug_instance); + } + if (debug_mesh.is_valid()) { + RenderingServer::get_singleton()->free(debug_mesh->get_rid()); + } +#endif // DEBUG_ENABLED +} + +void NavigationLink3D::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + return; + } + + enabled = p_enabled; + + if (!is_inside_tree()) { + return; + } + + if (enabled) { + NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map()); + } else { + NavigationServer3D::get_singleton()->link_set_map(link, RID()); + } + +#ifdef DEBUG_ENABLED + if (debug_instance.is_valid() && debug_mesh.is_valid()) { + if (enabled) { + Ref<StandardMaterial3D> link_material = NavigationServer3D::get_singleton_mut()->get_debug_navigation_link_connections_material(); + RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, link_material->get_rid()); + } else { + Ref<StandardMaterial3D> disabled_link_material = NavigationServer3D::get_singleton_mut()->get_debug_navigation_link_connections_disabled_material(); + RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, disabled_link_material->get_rid()); + } + } +#endif // DEBUG_ENABLED + + update_gizmos(); +} + +void NavigationLink3D::set_bidirectional(bool p_bidirectional) { + if (bidirectional == p_bidirectional) { + return; + } + + bidirectional = p_bidirectional; + + NavigationServer3D::get_singleton()->link_set_bidirectional(link, bidirectional); +} + +void NavigationLink3D::set_navigation_layers(uint32_t p_navigation_layers) { + if (navigation_layers == p_navigation_layers) { + return; + } + + navigation_layers = p_navigation_layers; + + NavigationServer3D::get_singleton()->link_set_navigation_layers(link, navigation_layers); +} + +void NavigationLink3D::set_navigation_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + + uint32_t _navigation_layers = get_navigation_layers(); + + if (p_value) { + _navigation_layers |= 1 << (p_layer_number - 1); + } else { + _navigation_layers &= ~(1 << (p_layer_number - 1)); + } + + set_navigation_layers(_navigation_layers); +} + +bool NavigationLink3D::get_navigation_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + + return get_navigation_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationLink3D::set_start_location(Vector3 p_location) { + if (start_location.is_equal_approx(p_location)) { + return; + } + + start_location = p_location; + + if (!is_inside_tree()) { + return; + } + + Transform3D gt = get_global_transform(); + NavigationServer3D::get_singleton()->link_set_start_location(link, gt.xform(start_location)); + +#ifdef DEBUG_ENABLED + _update_debug_mesh(); +#endif // DEBUG_ENABLED + + update_gizmos(); + update_configuration_warnings(); +} + +void NavigationLink3D::set_end_location(Vector3 p_location) { + if (end_location.is_equal_approx(p_location)) { + return; + } + + end_location = p_location; + + if (!is_inside_tree()) { + return; + } + + Transform3D gt = get_global_transform(); + NavigationServer3D::get_singleton()->link_set_end_location(link, gt.xform(end_location)); + +#ifdef DEBUG_ENABLED + _update_debug_mesh(); +#endif // DEBUG_ENABLED + + update_gizmos(); + update_configuration_warnings(); +} + +void NavigationLink3D::set_enter_cost(real_t p_enter_cost) { + ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive."); + if (Math::is_equal_approx(enter_cost, p_enter_cost)) { + return; + } + + enter_cost = p_enter_cost; + + NavigationServer3D::get_singleton()->link_set_enter_cost(link, enter_cost); +} + +void NavigationLink3D::set_travel_cost(real_t p_travel_cost) { + ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive."); + if (Math::is_equal_approx(travel_cost, p_travel_cost)) { + return; + } + + travel_cost = p_travel_cost; + + NavigationServer3D::get_singleton()->link_set_travel_cost(link, travel_cost); +} + +TypedArray<String> NavigationLink3D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (start_location.is_equal_approx(end_location)) { + warnings.push_back(RTR("NavigationLink3D start location should be different than the end location to be useful.")); + } + + return warnings; +} diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h new file mode 100644 index 0000000000..1f88075527 --- /dev/null +++ b/scene/3d/navigation_link_3d.h @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* navigation_link_3d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 NAVIGATION_LINK_3D_H +#define NAVIGATION_LINK_3D_H + +#include "scene/3d/node_3d.h" + +class NavigationLink3D : public Node3D { + GDCLASS(NavigationLink3D, Node3D); + + bool enabled = true; + RID link = RID(); + bool bidirectional = true; + uint32_t navigation_layers = 1; + Vector3 end_location = Vector3(); + Vector3 start_location = Vector3(); + real_t enter_cost = 0.0; + real_t travel_cost = 1.0; + +#ifdef DEBUG_ENABLED + RID debug_instance; + Ref<ArrayMesh> debug_mesh; + + void _update_debug_mesh(); +#endif // DEBUG_ENABLED + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + NavigationLink3D(); + ~NavigationLink3D(); + + void set_enabled(bool p_enabled); + bool is_enabled() const { return enabled; } + + void set_bidirectional(bool p_bidirectional); + bool is_bidirectional() const { return bidirectional; } + + void set_navigation_layers(uint32_t p_navigation_layers); + uint32_t get_navigation_layers() const { return navigation_layers; } + + void set_navigation_layer_value(int p_layer_number, bool p_value); + bool get_navigation_layer_value(int p_layer_number) const; + + void set_start_location(Vector3 p_location); + Vector3 get_start_location() const { return start_location; } + + void set_end_location(Vector3 p_location); + Vector3 get_end_location() const { return end_location; } + + void set_enter_cost(real_t p_enter_cost); + real_t get_enter_cost() const { return enter_cost; } + + void set_travel_cost(real_t p_travel_cost); + real_t get_travel_cost() const { return travel_cost; } + + TypedArray<String> get_configuration_warnings() const override; +}; + +#endif // NAVIGATION_LINK_3D_H diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index a56a51a547..22c8f2cd4e 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -44,7 +44,6 @@ void BoxContainer::_resort() { Size2i new_size = get_size(); - int sep = get_theme_constant(SNAME("separation")); //,vertical?"VBoxContainer":"HBoxContainer"); bool rtl = is_layout_rtl(); bool first = true; @@ -90,7 +89,7 @@ void BoxContainer::_resort() { return; } - int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * sep; + int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * theme_cache.separation; int stretch_diff = stretch_max - stretch_min; if (stretch_diff < 0) { //avoid negative stretch space @@ -214,7 +213,7 @@ void BoxContainer::_resort() { if (first) { first = false; } else { - ofs += sep; + ofs += theme_cache.separation; } int from = ofs; @@ -248,7 +247,6 @@ Size2 BoxContainer::get_minimum_size() const { /* Calculate MINIMUM SIZE */ Size2i minimum; - int sep = get_theme_constant(SNAME("separation")); //,vertical?"VBoxContainer":"HBoxContainer"); bool first = true; @@ -273,7 +271,7 @@ Size2 BoxContainer::get_minimum_size() const { minimum.width = size.width; } - minimum.height += size.height + (first ? 0 : sep); + minimum.height += size.height + (first ? 0 : theme_cache.separation); } else { /* HORIZONTAL */ @@ -281,7 +279,7 @@ Size2 BoxContainer::get_minimum_size() const { minimum.height = size.height; } - minimum.width += size.width + (first ? 0 : sep); + minimum.width += size.width + (first ? 0 : theme_cache.separation); } first = false; @@ -290,6 +288,12 @@ Size2 BoxContainer::get_minimum_size() const { return minimum; } +void BoxContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.separation = get_theme_constant(SNAME("separation")); //,vertical?"VBoxContainer":"HBoxContainer"); +} + void BoxContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h index 3043c3ea45..55dfb2ada7 100644 --- a/scene/gui/box_container.h +++ b/scene/gui/box_container.h @@ -47,11 +47,16 @@ private: bool vertical = false; AlignmentMode alignment = ALIGNMENT_BEGIN; + struct ThemeCache { + int separation = 0; + } theme_cache; + void _resort(); protected: - void _notification(int p_what); + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); public: diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index c7b64ba6c6..c2b82e01d1 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -36,7 +36,7 @@ Size2 Button::get_minimum_size() const { Ref<Texture2D> _icon = icon; if (_icon.is_null() && has_theme_icon(SNAME("icon"))) { - _icon = Control::get_theme_icon(SNAME("icon")); + _icon = theme_cache.icon; } return get_minimum_size_for_text_and_icon("", _icon); @@ -46,6 +46,45 @@ void Button::_set_internal_margin(Side p_side, float p_value) { _internal_margin[p_side] = p_value; } +void Button::_update_theme_item_cache() { + BaseButton::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + theme_cache.normal_mirrored = get_theme_stylebox(SNAME("normal_mirrored")); + theme_cache.pressed = get_theme_stylebox(SNAME("pressed")); + theme_cache.pressed_mirrored = get_theme_stylebox(SNAME("pressed_mirrored")); + theme_cache.hover = get_theme_stylebox(SNAME("hover")); + theme_cache.hover_mirrored = get_theme_stylebox(SNAME("hover_mirrored")); + theme_cache.hover_pressed = get_theme_stylebox(SNAME("hover_pressed")); + theme_cache.hover_pressed_mirrored = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + theme_cache.disabled = get_theme_stylebox(SNAME("disabled")); + theme_cache.disabled_mirrored = get_theme_stylebox(SNAME("disabled_mirrored")); + theme_cache.focus = get_theme_stylebox(SNAME("focus")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.icon_normal_color = get_theme_color(SNAME("icon_normal_color")); + theme_cache.icon_focus_color = get_theme_color(SNAME("icon_focus_color")); + theme_cache.icon_pressed_color = get_theme_color(SNAME("icon_pressed_color")); + theme_cache.icon_hover_color = get_theme_color(SNAME("icon_hover_color")); + theme_cache.icon_hover_pressed_color = get_theme_color(SNAME("icon_hover_pressed_color")); + theme_cache.icon_disabled_color = get_theme_color(SNAME("icon_disabled_color")); + + theme_cache.icon = get_theme_icon(SNAME("icon")); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); +} + void Button::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { @@ -73,15 +112,15 @@ void Button::_notification(int p_what) { Color color; Color color_icon(1, 1, 1, 1); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); switch (get_draw_mode()) { case DRAW_NORMAL: { if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) { - style = get_theme_stylebox(SNAME("normal_mirrored")); + style = theme_cache.normal_mirrored; } else { - style = get_theme_stylebox(SNAME("normal")); + style = theme_cache.normal; } if (!flat) { @@ -90,14 +129,14 @@ void Button::_notification(int p_what) { // Focus colors only take precedence over normal state. if (has_focus()) { - color = get_theme_color(SNAME("font_focus_color")); + color = theme_cache.font_focus_color; if (has_theme_color(SNAME("icon_focus_color"))) { - color_icon = get_theme_color(SNAME("icon_focus_color")); + color_icon = theme_cache.icon_focus_color; } } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; if (has_theme_color(SNAME("icon_normal_color"))) { - color_icon = get_theme_color(SNAME("icon_normal_color")); + color_icon = theme_cache.icon_normal_color; } } } break; @@ -105,19 +144,19 @@ void Button::_notification(int p_what) { // Edge case for CheckButton and CheckBox. if (has_theme_stylebox("hover_pressed")) { if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) { - style = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + style = theme_cache.hover_pressed_mirrored; } else { - style = get_theme_stylebox(SNAME("hover_pressed")); + style = theme_cache.hover_pressed; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } if (has_theme_color(SNAME("font_hover_pressed_color"))) { - color = get_theme_color(SNAME("font_hover_pressed_color")); + color = theme_cache.font_hover_pressed_color; } if (has_theme_color(SNAME("icon_hover_pressed_color"))) { - color_icon = get_theme_color(SNAME("icon_hover_pressed_color")); + color_icon = theme_cache.icon_hover_pressed_color; } break; @@ -126,53 +165,53 @@ void Button::_notification(int p_what) { } case DRAW_PRESSED: { if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) { - style = get_theme_stylebox(SNAME("pressed_mirrored")); + style = theme_cache.pressed_mirrored; } else { - style = get_theme_stylebox(SNAME("pressed")); + style = theme_cache.pressed; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } if (has_theme_color(SNAME("font_pressed_color"))) { - color = get_theme_color(SNAME("font_pressed_color")); + color = theme_cache.font_pressed_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } if (has_theme_color(SNAME("icon_pressed_color"))) { - color_icon = get_theme_color(SNAME("icon_pressed_color")); + color_icon = theme_cache.icon_pressed_color; } } break; case DRAW_HOVER: { if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) { - style = get_theme_stylebox(SNAME("hover_mirrored")); + style = theme_cache.hover_mirrored; } else { - style = get_theme_stylebox(SNAME("hover")); + style = theme_cache.hover; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - color = get_theme_color(SNAME("font_hover_color")); + color = theme_cache.font_hover_color; if (has_theme_color(SNAME("icon_hover_color"))) { - color_icon = get_theme_color(SNAME("icon_hover_color")); + color_icon = theme_cache.icon_hover_color; } } break; case DRAW_DISABLED: { if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) { - style = get_theme_stylebox(SNAME("disabled_mirrored")); + style = theme_cache.disabled_mirrored; } else { - style = get_theme_stylebox(SNAME("disabled")); + style = theme_cache.disabled; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - color = get_theme_color(SNAME("font_disabled_color")); + color = theme_cache.font_disabled_color; if (has_theme_color(SNAME("icon_disabled_color"))) { - color_icon = get_theme_color(SNAME("icon_disabled_color")); + color_icon = theme_cache.icon_disabled_color; } else { color_icon.a = 0.4; } @@ -181,13 +220,13 @@ void Button::_notification(int p_what) { } if (has_focus()) { - Ref<StyleBox> style2 = get_theme_stylebox(SNAME("focus")); + Ref<StyleBox> style2 = theme_cache.focus; style2->draw(ci, Rect2(Point2(), size)); } Ref<Texture2D> _icon; if (icon.is_null() && has_theme_icon(SNAME("icon"))) { - _icon = Control::get_theme_icon(SNAME("icon")); + _icon = theme_cache.icon; } else { _icon = icon; } @@ -217,21 +256,21 @@ void Button::_notification(int p_what) { if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { style_offset.x = style->get_margin(SIDE_LEFT); if (_internal_margin[SIDE_LEFT] > 0) { - icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); + icon_ofs_region = _internal_margin[SIDE_LEFT] + theme_cache.h_separation; } } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { style_offset.x = 0.0; } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_RIGHT) { style_offset.x = -style->get_margin(SIDE_RIGHT); if (_internal_margin[SIDE_RIGHT] > 0) { - icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); + icon_ofs_region = -_internal_margin[SIDE_RIGHT] - theme_cache.h_separation; } } style_offset.y = style->get_margin(SIDE_TOP); if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; - int icon_text_separation = text.is_empty() ? 0 : get_theme_constant(SNAME("h_separation")); + int icon_text_separation = text.is_empty() ? 0 : theme_cache.h_separation; _size.width -= icon_text_separation + icon_ofs_region; if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) { _size.width -= text_buf->get_size().width; @@ -261,7 +300,7 @@ void Button::_notification(int p_what) { } } - Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("h_separation")), 0) : Point2(); + Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + theme_cache.h_separation, 0) : Point2(); if (align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER && icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { icon_ofs.x = 0.0; } @@ -271,10 +310,10 @@ void Button::_notification(int p_what) { int text_width = MAX(1, (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x); if (_internal_margin[SIDE_LEFT] > 0) { - text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); + text_clip -= _internal_margin[SIDE_LEFT] + theme_cache.h_separation; } if (_internal_margin[SIDE_RIGHT] > 0) { - text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("h_separation")); + text_clip -= _internal_margin[SIDE_RIGHT] + theme_cache.h_separation; } Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0; @@ -288,7 +327,7 @@ void Button::_notification(int p_what) { icon_ofs.x = 0.0; } if (_internal_margin[SIDE_LEFT] > 0) { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); + text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + theme_cache.h_separation; } else { text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; } @@ -305,7 +344,7 @@ void Button::_notification(int p_what) { } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (_internal_margin[SIDE_RIGHT] > 0) { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); + text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - theme_cache.h_separation; } else { text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; } @@ -316,8 +355,8 @@ void Button::_notification(int p_what) { } break; } - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); } @@ -346,7 +385,7 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) { minsize.width += p_icon->get_width(); if (!xl_text.is_empty() || !p_text.is_empty()) { - minsize.width += MAX(0, get_theme_constant(SNAME("h_separation"))); + minsize.width += MAX(0, theme_cache.h_separation); } } else { minsize.width = MAX(minsize.width, p_icon->get_width()); @@ -354,12 +393,12 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu } if (!xl_text.is_empty() || !p_text.is_empty()) { - Ref<Font> font = get_theme_font(SNAME("font")); - float font_height = font->get_height(get_theme_font_size(SNAME("font_size"))); + Ref<Font> font = theme_cache.font; + float font_height = font->get_height(theme_cache.font_size); minsize.height = MAX(font_height, minsize.height); } - return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize; + return theme_cache.normal->get_minimum_size() + minsize; } void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) { @@ -371,10 +410,15 @@ void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) { p_text = xl_text; } - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - p_paragraph->clear(); + + Ref<Font> font = theme_cache.font; + int font_size = theme_cache.font_size; + if (font.is_null() || font_size == 0) { + // Can't shape without a valid font and a non-zero size. + return; + } + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { p_paragraph->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { diff --git a/scene/gui/button.h b/scene/gui/button.h index 23b5c78166..9d9f9763db 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -54,10 +54,48 @@ private: HorizontalAlignment icon_alignment = HORIZONTAL_ALIGNMENT_LEFT; float _internal_margin[4] = {}; + struct ThemeCache { + Ref<StyleBox> normal; + Ref<StyleBox> normal_mirrored; + Ref<StyleBox> pressed; + Ref<StyleBox> pressed_mirrored; + Ref<StyleBox> hover; + Ref<StyleBox> hover_mirrored; + Ref<StyleBox> hover_pressed; + Ref<StyleBox> hover_pressed_mirrored; + Ref<StyleBox> disabled; + Ref<StyleBox> disabled_mirrored; + Ref<StyleBox> focus; + + Color font_color; + Color font_focus_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_disabled_color; + + Ref<Font> font; + int font_size = 0; + int outline_size = 0; + Color font_outline_color; + + Color icon_normal_color; + Color icon_focus_color; + Color icon_pressed_color; + Color icon_hover_color; + Color icon_hover_pressed_color; + Color icon_disabled_color; + + Ref<Texture2D> icon; + + int h_separation = 0; + } theme_cache; + void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = ""); protected: void _set_internal_margin(Side p_side, float p_value); + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index 26edc1f1b0..37db7d53f0 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -33,39 +33,30 @@ #include "servers/rendering_server.h" Size2 CheckBox::get_icon_size() const { - Ref<Texture2D> checked = Control::get_theme_icon(SNAME("checked")); - Ref<Texture2D> unchecked = Control::get_theme_icon(SNAME("unchecked")); - Ref<Texture2D> radio_checked = Control::get_theme_icon(SNAME("radio_checked")); - Ref<Texture2D> radio_unchecked = Control::get_theme_icon(SNAME("radio_unchecked")); - Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); - Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); - Ref<Texture2D> radio_checked_disabled = Control::get_theme_icon(SNAME("radio_checked_disabled")); - Ref<Texture2D> radio_unchecked_disabled = Control::get_theme_icon(SNAME("radio_unchecked_disabled")); - Size2 tex_size = Size2(0, 0); - if (!checked.is_null()) { - tex_size = Size2(checked->get_width(), checked->get_height()); + if (!theme_cache.checked.is_null()) { + tex_size = Size2(theme_cache.checked->get_width(), theme_cache.checked->get_height()); } - if (!unchecked.is_null()) { - tex_size = Size2(MAX(tex_size.width, unchecked->get_width()), MAX(tex_size.height, unchecked->get_height())); + if (!theme_cache.unchecked.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked->get_width()), MAX(tex_size.height, theme_cache.unchecked->get_height())); } - if (!radio_checked.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_checked->get_width()), MAX(tex_size.height, radio_checked->get_height())); + if (!theme_cache.radio_checked.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked->get_width()), MAX(tex_size.height, theme_cache.radio_checked->get_height())); } - if (!radio_unchecked.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_unchecked->get_width()), MAX(tex_size.height, radio_unchecked->get_height())); + if (!theme_cache.radio_unchecked.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked->get_height())); } - if (!checked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, checked_disabled->get_width()), MAX(tex_size.height, checked_disabled->get_height())); + if (!theme_cache.checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.checked_disabled->get_width()), MAX(tex_size.height, theme_cache.checked_disabled->get_height())); } - if (!unchecked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, unchecked_disabled->get_width()), MAX(tex_size.height, unchecked_disabled->get_height())); + if (!theme_cache.unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.unchecked_disabled->get_height())); } - if (!radio_checked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_checked_disabled->get_width()), MAX(tex_size.height, radio_checked_disabled->get_height())); + if (!theme_cache.radio_checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_checked_disabled->get_height())); } - if (!radio_unchecked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_unchecked_disabled->get_width()), MAX(tex_size.height, radio_unchecked_disabled->get_height())); + if (!theme_cache.radio_unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked_disabled->get_height())); } return tex_size; } @@ -75,14 +66,30 @@ Size2 CheckBox::get_minimum_size() const { Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; if (get_text().length() > 0) { - minsize.width += MAX(0, get_theme_constant(SNAME("h_separation"))); + minsize.width += MAX(0, theme_cache.h_separation); } - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); - minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM)); + minsize.height = MAX(minsize.height, tex_size.height + theme_cache.normal_style->get_margin(SIDE_TOP) + theme_cache.normal_style->get_margin(SIDE_BOTTOM)); return minsize; } +void CheckBox::_update_theme_item_cache() { + Button::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.check_v_adjust = get_theme_constant(SNAME("check_v_adjust")); + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + + theme_cache.checked = get_theme_icon(SNAME("checked")); + theme_cache.unchecked = get_theme_icon(SNAME("unchecked")); + theme_cache.radio_checked = get_theme_icon(SNAME("radio_checked")); + theme_cache.radio_unchecked = get_theme_icon(SNAME("radio_unchecked")); + theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled")); + theme_cache.unchecked_disabled = get_theme_icon(SNAME("unchecked_disabled")); + theme_cache.radio_checked_disabled = get_theme_icon(SNAME("radio_checked_disabled")); + theme_cache.radio_unchecked_disabled = get_theme_icon(SNAME("radio_unchecked_disabled")); +} + void CheckBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -100,22 +107,39 @@ void CheckBox::_notification(int p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); - Ref<Texture2D> on = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_checked" : "checked", is_disabled() ? "_disabled" : "")); - Ref<Texture2D> off = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_unchecked" : "unchecked", is_disabled() ? "_disabled" : "")); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); + Ref<Texture2D> on_tex; + Ref<Texture2D> off_tex; + + if (is_radio()) { + if (is_disabled()) { + on_tex = theme_cache.radio_checked_disabled; + off_tex = theme_cache.radio_unchecked_disabled; + } else { + on_tex = theme_cache.radio_checked; + off_tex = theme_cache.radio_unchecked; + } + } else { + if (is_disabled()) { + on_tex = theme_cache.checked_disabled; + off_tex = theme_cache.unchecked_disabled; + } else { + on_tex = theme_cache.checked; + off_tex = theme_cache.unchecked; + } + } Vector2 ofs; if (is_layout_rtl()) { - ofs.x = get_size().x - sb->get_margin(SIDE_RIGHT) - get_icon_size().width; + ofs.x = get_size().x - theme_cache.normal_style->get_margin(SIDE_RIGHT) - get_icon_size().width; } else { - ofs.x = sb->get_margin(SIDE_LEFT); + ofs.x = theme_cache.normal_style->get_margin(SIDE_LEFT); } - ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_v_adjust")); + ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_adjust; if (is_pressed()) { - on->draw(ci, ofs); + on_tex->draw(ci, ofs); } else { - off->draw(ci, ofs); + off_tex->draw(ci, ofs); } } break; } diff --git a/scene/gui/check_box.h b/scene/gui/check_box.h index fcdb2ce08c..beafece3dc 100644 --- a/scene/gui/check_box.h +++ b/scene/gui/check_box.h @@ -36,9 +36,26 @@ class CheckBox : public Button { GDCLASS(CheckBox, Button); + struct ThemeCache { + int h_separation = 0; + int check_v_adjust = 0; + Ref<StyleBox> normal_style; + + Ref<Texture2D> checked; + Ref<Texture2D> unchecked; + Ref<Texture2D> radio_checked; + Ref<Texture2D> radio_unchecked; + Ref<Texture2D> checked_disabled; + Ref<Texture2D> unchecked_disabled; + Ref<Texture2D> radio_checked_disabled; + Ref<Texture2D> radio_unchecked_disabled; + } theme_cache; + protected: Size2 get_icon_size() const; Size2 get_minimum_size() const override; + + virtual void _update_theme_item_cache() override; void _notification(int p_what); bool is_radio(); diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index b9674ca41e..b01081e31b 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -34,14 +34,33 @@ #include "servers/rendering_server.h" Size2 CheckButton::get_icon_size() const { - Ref<Texture2D> on = Control::get_theme_icon(is_disabled() ? "on_disabled" : "on"); - Ref<Texture2D> off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off"); + Ref<Texture2D> on_tex; + Ref<Texture2D> off_tex; + + if (is_layout_rtl()) { + if (is_disabled()) { + on_tex = theme_cache.checked_disabled_mirrored; + off_tex = theme_cache.unchecked_disabled_mirrored; + } else { + on_tex = theme_cache.checked_mirrored; + off_tex = theme_cache.unchecked_mirrored; + } + } else { + if (is_disabled()) { + on_tex = theme_cache.checked_disabled; + off_tex = theme_cache.unchecked_disabled; + } else { + on_tex = theme_cache.checked; + off_tex = theme_cache.unchecked; + } + } + Size2 tex_size = Size2(0, 0); - if (!on.is_null()) { - tex_size = Size2(on->get_width(), on->get_height()); + if (!on_tex.is_null()) { + tex_size = Size2(on_tex->get_width(), on_tex->get_height()); } - if (!off.is_null()) { - tex_size = Size2(MAX(tex_size.width, off->get_width()), MAX(tex_size.height, off->get_height())); + if (!off_tex.is_null()) { + tex_size = Size2(MAX(tex_size.width, off_tex->get_width()), MAX(tex_size.height, off_tex->get_height())); } return tex_size; @@ -52,14 +71,30 @@ Size2 CheckButton::get_minimum_size() const { Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; if (get_text().length() > 0) { - minsize.width += MAX(0, get_theme_constant(SNAME("h_separation"))); + minsize.width += MAX(0, theme_cache.h_separation); } - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); - minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM)); + minsize.height = MAX(minsize.height, tex_size.height + theme_cache.normal_style->get_margin(SIDE_TOP) + theme_cache.normal_style->get_margin(SIDE_BOTTOM)); return minsize; } +void CheckButton::_update_theme_item_cache() { + Button::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.check_v_adjust = get_theme_constant(SNAME("check_v_adjust")); + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + + theme_cache.checked = get_theme_icon(SNAME("on")); + theme_cache.unchecked = get_theme_icon(SNAME("off")); + theme_cache.checked_disabled = get_theme_icon(SNAME("on_disabled")); + theme_cache.unchecked_disabled = get_theme_icon(SNAME("off_disabled")); + theme_cache.checked_mirrored = get_theme_icon(SNAME("on_mirrored")); + theme_cache.unchecked_mirrored = get_theme_icon(SNAME("off_mirrored")); + theme_cache.checked_disabled_mirrored = get_theme_icon(SNAME("on_disabled_mirrored")); + theme_cache.unchecked_disabled_mirrored = get_theme_icon(SNAME("off_disabled_mirrored")); +} + void CheckButton::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -78,34 +113,41 @@ void CheckButton::_notification(int p_what) { RID ci = get_canvas_item(); bool rtl = is_layout_rtl(); - Ref<Texture2D> on; - if (rtl) { - on = Control::get_theme_icon(is_disabled() ? "on_disabled_mirrored" : "on_mirrored"); - } else { - on = Control::get_theme_icon(is_disabled() ? "on_disabled" : "on"); - } - Ref<Texture2D> off; + Ref<Texture2D> on_tex; + Ref<Texture2D> off_tex; + if (rtl) { - off = Control::get_theme_icon(is_disabled() ? "off_disabled_mirrored" : "off_mirrored"); + if (is_disabled()) { + on_tex = theme_cache.checked_disabled_mirrored; + off_tex = theme_cache.unchecked_disabled_mirrored; + } else { + on_tex = theme_cache.checked_mirrored; + off_tex = theme_cache.unchecked_mirrored; + } } else { - off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off"); + if (is_disabled()) { + on_tex = theme_cache.checked_disabled; + off_tex = theme_cache.unchecked_disabled; + } else { + on_tex = theme_cache.checked; + off_tex = theme_cache.unchecked; + } } - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); Vector2 ofs; Size2 tex_size = get_icon_size(); if (rtl) { - ofs.x = sb->get_margin(SIDE_LEFT); + ofs.x = theme_cache.normal_style->get_margin(SIDE_LEFT); } else { - ofs.x = get_size().width - (tex_size.width + sb->get_margin(SIDE_RIGHT)); + ofs.x = get_size().width - (tex_size.width + theme_cache.normal_style->get_margin(SIDE_RIGHT)); } - ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_v_adjust")); + ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_adjust; if (is_pressed()) { - on->draw(ci, ofs); + on_tex->draw(ci, ofs); } else { - off->draw(ci, ofs); + off_tex->draw(ci, ofs); } } break; } diff --git a/scene/gui/check_button.h b/scene/gui/check_button.h index 7d4bb8bdfc..e71472c870 100644 --- a/scene/gui/check_button.h +++ b/scene/gui/check_button.h @@ -36,9 +36,26 @@ class CheckButton : public Button { GDCLASS(CheckButton, Button); + struct ThemeCache { + int h_separation = 0; + int check_v_adjust = 0; + Ref<StyleBox> normal_style; + + Ref<Texture2D> checked; + Ref<Texture2D> unchecked; + Ref<Texture2D> checked_disabled; + Ref<Texture2D> unchecked_disabled; + Ref<Texture2D> checked_mirrored; + Ref<Texture2D> unchecked_mirrored; + Ref<Texture2D> checked_disabled_mirrored; + Ref<Texture2D> unchecked_disabled_mirrored; + } theme_cache; + protected: Size2 get_icon_size() const; virtual Size2 get_minimum_size() const override; + + virtual void _update_theme_item_cache() override; void _notification(int p_what); public: diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 5e8d5a567f..06819283fa 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2330,6 +2330,9 @@ void Control::_invalidate_theme_cache() { data.theme_constant_cache.clear(); } +void Control::_update_theme_item_cache() { +} + void Control::set_theme(const Ref<Theme> &p_theme) { if (data.theme == p_theme) { return; @@ -3103,6 +3106,11 @@ void Control::remove_child_notify(Node *p_child) { void Control::_notification(int p_notification) { switch (p_notification) { + case NOTIFICATION_POSTINITIALIZE: { + _invalidate_theme_cache(); + _update_theme_item_cache(); + } break; + case NOTIFICATION_ENTER_TREE: { // Need to defer here, because theme owner information might be set in // add_child_notify, which doesn't get called until right after this. @@ -3236,6 +3244,7 @@ void Control::_notification(int p_notification) { case NOTIFICATION_THEME_CHANGED: { emit_signal(SceneStringNames::get_singleton()->theme_changed); _invalidate_theme_cache(); + _update_theme_item_cache(); update_minimum_size(); queue_redraw(); } break; @@ -3257,6 +3266,7 @@ void Control::_notification(int p_notification) { if (is_inside_tree()) { data.is_rtl_dirty = true; _invalidate_theme_cache(); + _update_theme_item_cache(); _size_changed(); } } break; diff --git a/scene/gui/control.h b/scene/gui/control.h index 6215594ae0..ac5d481f3a 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -325,6 +325,10 @@ protected: bool _property_can_revert(const StringName &p_name) const; bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + // Theming. + + virtual void _update_theme_item_cache(); + // Internationalization. virtual TypedArray<Vector2i> structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index b4e0747ab8..0a1aeeda71 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -50,6 +50,14 @@ void AcceptDialog::_parent_focused() { } } +void AcceptDialog::_update_theme_item_cache() { + Window::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.margin = get_theme_constant(SNAME("margin")); + theme_cache.button_margin = get_theme_constant(SNAME("button_margin")); +} + void AcceptDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { @@ -69,7 +77,10 @@ void AcceptDialog::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { - bg->add_theme_style_override("panel", bg->get_theme_stylebox(SNAME("panel"), SNAME("AcceptDialog"))); + bg->add_theme_style_override("panel", theme_cache.panel_style); + + label->set_begin(Point2(theme_cache.margin, theme_cache.margin)); + label->set_end(Point2(-theme_cache.margin, -theme_cache.button_margin - 10)); } break; case NOTIFICATION_EXIT_TREE: { @@ -185,12 +196,12 @@ void AcceptDialog::_update_child_rects() { if (label->get_text().is_empty()) { label_size.height = 0; } - int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); + Size2 size = get_size(); Size2 hminsize = hbc->get_combined_minimum_size(); - Vector2 cpos(margin, margin + label_size.height); - Vector2 csize(size.x - margin * 2, size.y - margin * 3 - hminsize.y - label_size.height); + Vector2 cpos(theme_cache.margin, theme_cache.margin + label_size.height); + Vector2 csize(size.x - theme_cache.margin * 2, size.y - theme_cache.margin * 3 - hminsize.y - label_size.height); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -206,7 +217,7 @@ void AcceptDialog::_update_child_rects() { c->set_size(csize); } - cpos.y += csize.y + margin; + cpos.y += csize.y + theme_cache.margin; csize.y = hminsize.y; hbc->set_position(cpos); @@ -217,7 +228,6 @@ void AcceptDialog::_update_child_rects() { } Size2 AcceptDialog::_get_contents_minimum_size() const { - int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); Size2 minsize = label->get_combined_minimum_size(); for (int i = 0; i < get_child_count(); i++) { @@ -238,8 +248,8 @@ Size2 AcceptDialog::_get_contents_minimum_size() const { Size2 hminsize = hbc->get_combined_minimum_size(); minsize.x = MAX(hminsize.x, minsize.x); minsize.y += hminsize.y; - minsize.x += margin * 2; - minsize.y += margin * 3; //one as separation between hbc and child + minsize.x += theme_cache.margin * 2; + minsize.y += theme_cache.margin * 3; //one as separation between hbc and child Size2 wmsize = get_min_size(); minsize.x = MAX(wmsize.x, minsize.x); @@ -350,14 +360,9 @@ AcceptDialog::AcceptDialog() { hbc = memnew(HBoxContainer); - int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); - int button_margin = hbc->get_theme_constant(SNAME("button_margin"), SNAME("Dialogs")); - label = memnew(Label); label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END); label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END); - label->set_begin(Point2(margin, margin)); - label->set_end(Point2(-margin, -button_margin - 10)); add_child(label, false, INTERNAL_MODE_FRONT); add_child(hbc, false, INTERNAL_MODE_FRONT); diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 9ebf5ddfb2..8ba9c93861 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -52,6 +52,12 @@ class AcceptDialog : public Window { bool hide_on_ok = true; bool close_on_escape = true; + struct ThemeCache { + Ref<StyleBox> panel_style; + int margin = 0; + int button_margin = 0; + } theme_cache; + void _custom_action(const String &p_action); void _update_child_rects(); @@ -62,6 +68,7 @@ class AcceptDialog : public Window { protected: virtual Size2 _get_contents_minimum_size() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 2a56d6d222..a0cf5f5970 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -59,36 +59,26 @@ VBoxContainer *FileDialog::get_vbox() { return vbox; } -void FileDialog::_theme_changed() { - Color font_color = vbox->get_theme_color(SNAME("font_color"), SNAME("Button")); - Color font_hover_color = vbox->get_theme_color(SNAME("font_hover_color"), SNAME("Button")); - Color font_focus_color = vbox->get_theme_color(SNAME("font_focus_color"), SNAME("Button")); - Color font_pressed_color = vbox->get_theme_color(SNAME("font_pressed_color"), SNAME("Button")); - - dir_up->add_theme_color_override("icon_normal_color", font_color); - dir_up->add_theme_color_override("icon_hover_color", font_hover_color); - dir_up->add_theme_color_override("icon_focus_color", font_focus_color); - dir_up->add_theme_color_override("icon_pressed_color", font_pressed_color); - - dir_prev->add_theme_color_override("icon_color_normal", font_color); - dir_prev->add_theme_color_override("icon_color_hover", font_hover_color); - dir_prev->add_theme_color_override("icon_focus_color", font_focus_color); - dir_prev->add_theme_color_override("icon_color_pressed", font_pressed_color); - - dir_next->add_theme_color_override("icon_color_normal", font_color); - dir_next->add_theme_color_override("icon_color_hover", font_hover_color); - dir_next->add_theme_color_override("icon_focus_color", font_focus_color); - dir_next->add_theme_color_override("icon_color_pressed", font_pressed_color); - - refresh->add_theme_color_override("icon_normal_color", font_color); - refresh->add_theme_color_override("icon_hover_color", font_hover_color); - refresh->add_theme_color_override("icon_focus_color", font_focus_color); - refresh->add_theme_color_override("icon_pressed_color", font_pressed_color); - - show_hidden->add_theme_color_override("icon_normal_color", font_color); - show_hidden->add_theme_color_override("icon_hover_color", font_hover_color); - show_hidden->add_theme_color_override("icon_focus_color", font_focus_color); - show_hidden->add_theme_color_override("icon_pressed_color", font_pressed_color); +void FileDialog::_update_theme_item_cache() { + ConfirmationDialog::_update_theme_item_cache(); + + theme_cache.parent_folder = get_theme_icon(SNAME("parent_folder")); + theme_cache.forward_folder = get_theme_icon(SNAME("forward_folder")); + theme_cache.back_folder = get_theme_icon(SNAME("back_folder")); + theme_cache.reload = get_theme_icon(SNAME("reload")); + theme_cache.toggle_hidden = get_theme_icon(SNAME("toggle_hidden")); + theme_cache.folder = get_theme_icon(SNAME("folder")); + theme_cache.file = get_theme_icon(SNAME("file")); + + theme_cache.folder_icon_modulate = get_theme_color(SNAME("folder_icon_modulate")); + theme_cache.file_icon_modulate = get_theme_color(SNAME("file_icon_modulate")); + theme_cache.files_disabled = get_theme_color(SNAME("files_disabled")); + + // TODO: Define own colors? + theme_cache.icon_normal_color = get_theme_color(SNAME("font_color"), SNAME("Button")); + theme_cache.icon_hover_color = get_theme_color(SNAME("font_hover_color"), SNAME("Button")); + theme_cache.icon_focus_color = get_theme_color(SNAME("font_focus_color"), SNAME("Button")); + theme_cache.icon_pressed_color = get_theme_color(SNAME("font_pressed_color"), SNAME("Button")); } void FileDialog::_notification(int p_what) { @@ -99,18 +89,42 @@ void FileDialog::_notification(int p_what) { } } break; - case NOTIFICATION_ENTER_TREE: { - dir_up->set_icon(vbox->get_theme_icon(SNAME("parent_folder"), SNAME("FileDialog"))); + case NOTIFICATION_THEME_CHANGED: { + dir_up->set_icon(theme_cache.parent_folder); if (vbox->is_layout_rtl()) { - dir_prev->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog"))); - dir_next->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog"))); + dir_prev->set_icon(theme_cache.forward_folder); + dir_next->set_icon(theme_cache.back_folder); } else { - dir_prev->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog"))); - dir_next->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog"))); + dir_prev->set_icon(theme_cache.back_folder); + dir_next->set_icon(theme_cache.forward_folder); } - refresh->set_icon(vbox->get_theme_icon(SNAME("reload"), SNAME("FileDialog"))); - show_hidden->set_icon(vbox->get_theme_icon(SNAME("toggle_hidden"), SNAME("FileDialog"))); - _theme_changed(); + refresh->set_icon(theme_cache.reload); + show_hidden->set_icon(theme_cache.toggle_hidden); + + dir_up->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + dir_up->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); + dir_up->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + dir_up->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); + + dir_prev->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); + dir_prev->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_prev->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + dir_prev->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); + + dir_next->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); + dir_next->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_next->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + dir_next->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); + + refresh->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + refresh->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); + refresh->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + refresh->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); + + show_hidden->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + show_hidden->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); + show_hidden->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + show_hidden->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); } break; case NOTIFICATION_TRANSLATION_CHANGED: { @@ -506,10 +520,6 @@ void FileDialog::update_file_list() { } TreeItem *root = tree->create_item(); - Ref<Texture2D> folder = vbox->get_theme_icon(SNAME("folder"), SNAME("FileDialog")); - Ref<Texture2D> file_icon = vbox->get_theme_icon(SNAME("file"), SNAME("FileDialog")); - const Color folder_color = vbox->get_theme_color(SNAME("folder_icon_modulate"), SNAME("FileDialog")); - const Color file_color = vbox->get_theme_color(SNAME("file_icon_modulate"), SNAME("FileDialog")); List<String> files; List<String> dirs; @@ -541,8 +551,8 @@ void FileDialog::update_file_list() { String &dir_name = dirs.front()->get(); TreeItem *ti = tree->create_item(root); ti->set_text(0, dir_name); - ti->set_icon(0, folder); - ti->set_icon_modulate(0, folder_color); + ti->set_icon(0, theme_cache.folder); + ti->set_icon_modulate(0, theme_cache.folder_icon_modulate); Dictionary d; d["name"] = dir_name; @@ -601,12 +611,12 @@ void FileDialog::update_file_list() { Ref<Texture2D> icon = get_icon_func(base_dir.path_join(files.front()->get())); ti->set_icon(0, icon); } else { - ti->set_icon(0, file_icon); + ti->set_icon(0, theme_cache.file); } - ti->set_icon_modulate(0, file_color); + ti->set_icon_modulate(0, theme_cache.file_icon_modulate); if (mode == FILE_MODE_OPEN_DIR) { - ti->set_custom_color(0, vbox->get_theme_color(SNAME("files_disabled"), SNAME("FileDialog"))); + ti->set_custom_color(0, theme_cache.files_disabled); ti->set_selectable(0, false); } Dictionary d; @@ -1006,7 +1016,6 @@ FileDialog::FileDialog() { vbox = memnew(VBoxContainer); add_child(vbox, false, INTERNAL_MODE_FRONT); - vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed)); mode = FILE_MODE_SAVE_FILE; set_title(TTRC("Save a File")); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 4945094086..5c892288b5 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -109,6 +109,25 @@ private: bool invalidated = true; + struct ThemeCache { + Ref<Texture2D> parent_folder; + Ref<Texture2D> forward_folder; + Ref<Texture2D> back_folder; + Ref<Texture2D> reload; + Ref<Texture2D> toggle_hidden; + Ref<Texture2D> folder; + Ref<Texture2D> file; + + Color folder_icon_modulate; + Color file_icon_modulate; + Color files_disabled; + + Color icon_normal_color; + Color icon_hover_color; + Color icon_focus_color; + Color icon_pressed_color; + } theme_cache; + void update_dir(); void update_file_name(); void update_file_list(); @@ -143,7 +162,7 @@ private: virtual void _post_popup() override; protected: - void _theme_changed(); + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index 30b694da76..ca230b8e81 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -44,9 +44,6 @@ void FlowContainer::_resort() { return; } - int separation_horizontal = get_theme_constant(SNAME("h_separation")); - int separation_vertical = get_theme_constant(SNAME("v_separation")); - bool rtl = is_layout_rtl(); HashMap<Control *, Size2i> children_minsize_cache; @@ -74,14 +71,14 @@ void FlowContainer::_resort() { if (vertical) { /* VERTICAL */ if (children_in_current_line > 0) { - ofs.y += separation_vertical; + ofs.y += theme_cache.v_separation; } if (ofs.y + child_msc.y > current_container_size) { - line_length = ofs.y - separation_vertical; + line_length = ofs.y - theme_cache.v_separation; lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); // Move in new column (vertical line). - ofs.x += line_height + separation_horizontal; + ofs.x += line_height + theme_cache.h_separation; ofs.y = 0; line_height = 0; line_stretch_ratio_total = 0; @@ -96,14 +93,14 @@ void FlowContainer::_resort() { } else { /* HORIZONTAL */ if (children_in_current_line > 0) { - ofs.x += separation_horizontal; + ofs.x += theme_cache.h_separation; } if (ofs.x + child_msc.x > current_container_size) { - line_length = ofs.x - separation_horizontal; + line_length = ofs.x - theme_cache.h_separation; lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); // Move in new line. - ofs.y += line_height + separation_vertical; + ofs.y += line_height + theme_cache.v_separation; ofs.x = 0; line_height = 0; line_stretch_ratio_total = 0; @@ -146,11 +143,11 @@ void FlowContainer::_resort() { current_line_idx++; child_idx_in_line = 0; if (vertical) { - ofs.x += line_data.min_line_height + separation_horizontal; + ofs.x += line_data.min_line_height + theme_cache.h_separation; ofs.y = 0; } else { ofs.x = 0; - ofs.y += line_data.min_line_height + separation_vertical; + ofs.y += line_data.min_line_height + theme_cache.v_separation; } line_data = lines_data[current_line_idx]; } @@ -184,9 +181,9 @@ void FlowContainer::_resort() { fit_child_in_rect(child, child_rect); if (vertical) { /* VERTICAL */ - ofs.y += child_size.height + separation_vertical; + ofs.y += child_size.height + theme_cache.v_separation; } else { /* HORIZONTAL */ - ofs.x += child_size.width + separation_horizontal; + ofs.x += child_size.width + theme_cache.h_separation; } child_idx_in_line++; @@ -250,6 +247,13 @@ Vector<int> FlowContainer::get_allowed_size_flags_vertical() const { return flags; } +void FlowContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); +} + void FlowContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index a2da43e071..2cdf7d7c37 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -42,11 +42,17 @@ private: bool vertical = false; + struct ThemeCache { + int h_separation = 0; + int v_separation = 0; + } theme_cache; + void _resort(); protected: - void _notification(int p_what); + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); public: diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index 3163d17846..8dc890eccb 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -31,6 +31,13 @@ #include "grid_container.h" #include "core/templates/rb_set.h" +void GridContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); +} + void GridContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { @@ -39,9 +46,6 @@ void GridContainer::_notification(int p_what) { RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. - int hsep = get_theme_constant(SNAME("h_separation")); - int vsep = get_theme_constant(SNAME("v_separation")); - // Compute the per-column/per-row data. int valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { @@ -98,8 +102,8 @@ void GridContainer::_notification(int p_what) { remaining_space.height -= E.value; } } - remaining_space.height -= vsep * MAX(max_row - 1, 0); - remaining_space.width -= hsep * MAX(max_col - 1, 0); + remaining_space.height -= theme_cache.v_separation * MAX(max_row - 1, 0); + remaining_space.width -= theme_cache.h_separation * MAX(max_col - 1, 0); bool can_fit = false; while (!can_fit && col_expanded.size() > 0) { @@ -202,7 +206,7 @@ void GridContainer::_notification(int p_what) { col_ofs = 0; } if (row > 0) { - row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + vsep; + row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + theme_cache.v_separation; if (row_expanded.has(row - 1) && row - 1 < row_remaining_pixel_index) { // Apply the remaining pixel of the previous row. @@ -224,11 +228,11 @@ void GridContainer::_notification(int p_what) { if (rtl) { Point2 p(col_ofs - s.width, row_ofs); fit_child_in_rect(c, Rect2(p, s)); - col_ofs -= s.width + hsep; + col_ofs -= s.width + theme_cache.h_separation; } else { Point2 p(col_ofs, row_ofs); fit_child_in_rect(c, Rect2(p, s)); - col_ofs += s.width + hsep; + col_ofs += s.width + theme_cache.h_separation; } } } break; @@ -271,9 +275,6 @@ Size2 GridContainer::get_minimum_size() const { RBMap<int, int> col_minw; RBMap<int, int> row_minh; - int hsep = get_theme_constant(SNAME("h_separation")); - int vsep = get_theme_constant(SNAME("v_separation")); - int max_row = 0; int max_col = 0; @@ -313,8 +314,8 @@ Size2 GridContainer::get_minimum_size() const { ms.height += E.value; } - ms.height += vsep * max_row; - ms.width += hsep * max_col; + ms.height += theme_cache.v_separation * max_row; + ms.width += theme_cache.h_separation * max_col; return ms; } diff --git a/scene/gui/grid_container.h b/scene/gui/grid_container.h index 9d77f90ab3..522046f694 100644 --- a/scene/gui/grid_container.h +++ b/scene/gui/grid_container.h @@ -38,7 +38,14 @@ class GridContainer : public Container { int columns = 1; + struct ThemeCache { + int h_separation = 0; + int v_separation = 0; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 308dbe33f2..8c49353105 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -43,7 +43,7 @@ void ItemList::_shape(int p_idx) { } else { item.text_buf->set_direction((TextServer::Direction)item.text_direction); } - item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.language); + item.text_buf->add_string(item.text, theme_cache.font, theme_cache.font_size, item.language); if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); } else { @@ -655,8 +655,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - pos -= bg->get_offset(); + pos -= theme_cache.bg_style->get_offset(); pos.y += scroll_bar->get_value(); if (is_layout_rtl()) { @@ -980,6 +979,31 @@ static Rect2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) { return Rect2(ofs_x, ofs_y, tex_width, tex_height); } +void ItemList::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); + + theme_cache.bg_style = get_theme_stylebox(SNAME("bg")); + theme_cache.bg_focus_style = get_theme_stylebox(SNAME("bg_focus")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.line_separation = get_theme_constant(SNAME("line_separation")); + theme_cache.icon_margin = get_theme_constant(SNAME("icon_margin")); + theme_cache.selected_style = get_theme_stylebox(SNAME("selected")); + theme_cache.selected_focus_style = get_theme_stylebox(SNAME("selected_focus")); + theme_cache.cursor_style = get_theme_stylebox(SNAME("cursor_unfocused")); + theme_cache.cursor_focus_style = get_theme_stylebox(SNAME("cursor")); + theme_cache.guide_color = get_theme_color(SNAME("guide_color")); +} + void ItemList::_notification(int p_what) { switch (p_what) { case NOTIFICATION_RESIZED: { @@ -998,37 +1022,32 @@ void ItemList::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - int mw = scroll_bar->get_minimum_size().x; scroll_bar->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -mw); scroll_bar->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); - scroll_bar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, bg->get_margin(SIDE_TOP)); - scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -bg->get_margin(SIDE_BOTTOM)); + scroll_bar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, theme_cache.bg_style->get_margin(SIDE_TOP)); + scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.bg_style->get_margin(SIDE_BOTTOM)); Size2 size = get_size(); - int width = size.width - bg->get_minimum_size().width; + int width = size.width - theme_cache.bg_style->get_minimum_size().width; - draw_style_box(bg, Rect2(Point2(), size)); + draw_style_box(theme_cache.bg_style, Rect2(Point2(), size)); - int hseparation = get_theme_constant(SNAME("h_separation")); - int vseparation = get_theme_constant(SNAME("v_separation")); - int icon_margin = get_theme_constant(SNAME("icon_margin")); - int line_separation = get_theme_constant(SNAME("line_separation")); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Ref<StyleBox> sbsel; + Ref<StyleBox> cursor; - Ref<StyleBox> sbsel = has_focus() ? get_theme_stylebox(SNAME("selected_focus")) : get_theme_stylebox(SNAME("selected")); - Ref<StyleBox> cursor = has_focus() ? get_theme_stylebox(SNAME("cursor")) : get_theme_stylebox(SNAME("cursor_unfocused")); + if (has_focus()) { + sbsel = theme_cache.selected_focus_style; + cursor = theme_cache.cursor_focus_style; + } else { + sbsel = theme_cache.selected_style; + cursor = theme_cache.cursor_style; + } bool rtl = is_layout_rtl(); - Color guide_color = get_theme_color(SNAME("guide_color")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_selected_color = get_theme_color(SNAME("font_selected_color")); - if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true); - draw_style_box(get_theme_stylebox(SNAME("bg_focus")), Rect2(Point2(), size)); + draw_style_box(theme_cache.bg_focus_style, Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), false); } @@ -1047,9 +1066,9 @@ void ItemList::_notification(int p_what) { if (!items[i].text.is_empty()) { if (icon_mode == ICON_MODE_TOP) { - minsize.y += icon_margin; + minsize.y += theme_cache.icon_margin; } else { - minsize.x += icon_margin; + minsize.x += theme_cache.icon_margin; } } } @@ -1067,7 +1086,7 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { minsize.x = MAX(minsize.x, s.width); if (max_text_lines > 0) { - minsize.y += s.height + line_separation * max_text_lines; + minsize.y += s.height + theme_cache.line_separation * max_text_lines; } else { minsize.y += s.height; } @@ -1084,13 +1103,13 @@ void ItemList::_notification(int p_what) { max_column_width = MAX(max_column_width, minsize.x); // elements need to adapt to the selected size - minsize.y += vseparation; - minsize.x += hseparation; + minsize.y += theme_cache.v_separation; + minsize.x += theme_cache.h_separation; items.write[i].rect_cache.size = minsize; items.write[i].min_rect_cache.size = minsize; } - int fit_size = size.x - bg->get_minimum_size().width - mw; + int fit_size = size.x - theme_cache.bg_style->get_minimum_size().width - mw; //2-attempt best fit current_columns = 0x7FFFFFFF; @@ -1118,11 +1137,11 @@ void ItemList::_notification(int p_what) { } items.write[i].rect_cache.position = ofs; max_h = MAX(max_h, items[i].rect_cache.size.y); - ofs.x += items[i].rect_cache.size.x + hseparation; + ofs.x += items[i].rect_cache.size.x + theme_cache.h_separation; col++; if (col == current_columns) { if (i < items.size() - 1) { - separators.push_back(ofs.y + max_h + vseparation / 2); + separators.push_back(ofs.y + max_h + theme_cache.v_separation / 2); } for (int j = i; j >= 0 && col > 0; j--, col--) { @@ -1130,7 +1149,7 @@ void ItemList::_notification(int p_what) { } ofs.x = 0; - ofs.y += max_h + vseparation; + ofs.y += max_h + theme_cache.v_separation; col = 0; max_h = 0; } @@ -1141,10 +1160,10 @@ void ItemList::_notification(int p_what) { } if (all_fit) { - float page = MAX(0, size.height - bg->get_minimum_size().height); + float page = MAX(0, size.height - theme_cache.bg_style->get_minimum_size().height); float max = MAX(page, ofs.y + max_h); if (auto_height) { - auto_height_value = ofs.y + max_h + bg->get_minimum_size().height; + auto_height_value = ofs.y + max_h + theme_cache.bg_style->get_minimum_size().height; } scroll_bar->set_max(max); scroll_bar->set_page(page); @@ -1185,7 +1204,7 @@ void ItemList::_notification(int p_what) { ensure_selected_visible = false; - Vector2 base_ofs = bg->get_offset(); + Vector2 base_ofs = theme_cache.bg_style->get_offset(); base_ofs.y -= int(scroll_bar->get_value()); const Rect2 clip(-base_ofs, size); // visible frame, don't need to draw outside of there @@ -1229,10 +1248,10 @@ void ItemList::_notification(int p_what) { if (items[i].selected) { Rect2 r = rcache; r.position += base_ofs; - r.position.y -= vseparation / 2; - r.size.y += vseparation; - r.position.x -= hseparation / 2; - r.size.x += hseparation; + r.position.y -= theme_cache.v_separation / 2; + r.size.y += theme_cache.v_separation; + r.position.x -= theme_cache.h_separation / 2; + r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1245,10 +1264,10 @@ void ItemList::_notification(int p_what) { r.position += base_ofs; // Size rect to make the align the temperature colors - r.position.y -= vseparation / 2; - r.size.y += vseparation; - r.position.x -= hseparation / 2; - r.size.x += hseparation; + r.position.y -= theme_cache.v_separation / 2; + r.size.y += theme_cache.v_separation; + r.position.x -= theme_cache.h_separation / 2; + r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1274,11 +1293,11 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width) / 2); - pos.y += icon_margin; - text_ofs.y = icon_size.height + icon_margin * 2; + pos.y += theme_cache.icon_margin; + text_ofs.y = icon_size.height + theme_cache.icon_margin * 2; } else { pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2); - text_ofs.x = icon_size.width + icon_margin; + text_ofs.x = icon_size.width + theme_cache.icon_margin; } Rect2 draw_rect = Rect2(pos, icon_size); @@ -1329,7 +1348,7 @@ void ItemList::_notification(int p_what) { max_len = size2.x; } - Color modulate = items[i].selected ? font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : font_color); + Color modulate = items[i].selected ? theme_cache.font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : theme_cache.font_color); if (items[i].disabled) { modulate.a *= 0.5; } @@ -1344,8 +1363,8 @@ void ItemList::_notification(int p_what) { items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color); } items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); @@ -1375,8 +1394,8 @@ void ItemList::_notification(int p_what) { items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_LEFT); } - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color); } if (width - text_ofs.x > 0) { @@ -1388,10 +1407,10 @@ void ItemList::_notification(int p_what) { if (select_mode == SELECT_MULTI && i == current) { Rect2 r = rcache; r.position += base_ofs; - r.position.y -= vseparation / 2; - r.size.y += vseparation; - r.position.x -= hseparation / 2; - r.size.x += hseparation; + r.position.y -= theme_cache.v_separation / 2; + r.size.y += theme_cache.v_separation; + r.position.x -= theme_cache.h_separation / 2; + r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1423,7 +1442,7 @@ void ItemList::_notification(int p_what) { } const int y = base_ofs.y + separators[i]; - draw_line(Vector2(bg->get_margin(SIDE_LEFT), y), Vector2(width, y), guide_color); + draw_line(Vector2(theme_cache.bg_style->get_margin(SIDE_LEFT), y), Vector2(width, y), theme_cache.guide_color); } } break; } @@ -1435,8 +1454,7 @@ void ItemList::_scroll_changed(double) { int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { Vector2 pos = p_pos; - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - pos -= bg->get_offset(); + pos -= theme_cache.bg_style->get_offset(); pos.y += scroll_bar->get_value(); if (is_layout_rtl()) { @@ -1473,8 +1491,7 @@ bool ItemList::is_pos_at_end_of_items(const Point2 &p_pos) const { } Vector2 pos = p_pos; - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - pos -= bg->get_offset(); + pos -= theme_cache.bg_style->get_offset(); pos.y += scroll_bar->get_value(); if (is_layout_rtl()) { diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index 21bd22759c..63bc771185 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -109,23 +109,45 @@ private: int max_columns = 1; Size2 fixed_icon_size; - Size2 max_item_size_cache; int defer_select_single = -1; - bool allow_rmb_select = false; - bool allow_reselect = false; real_t icon_scale = 1.0; bool do_autoscroll_to_bottom = false; + struct ThemeCache { + int h_separation = 0; + int v_separation = 0; + + Ref<StyleBox> bg_style; + Ref<StyleBox> bg_focus_style; + + Ref<Font> font; + int font_size = 0; + Color font_color; + Color font_selected_color; + int font_outline_size = 0; + Color font_outline_color; + + int line_separation = 0; + int icon_margin = 0; + Ref<StyleBox> selected_style; + Ref<StyleBox> selected_focus_style; + Ref<StyleBox> cursor_style; + Ref<StyleBox> cursor_focus_style; + Color guide_color; + } theme_cache; + void _scroll_changed(double); void _shape(int p_idx); protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index d47629b36b..306ca3d340 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -71,7 +71,7 @@ bool Label::is_uppercase() const { } int Label::get_line_height(int p_line) const { - Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; if (p_line >= 0 && p_line < lines_rid.size()) { return TS->shaped_text_get_size(lines_rid[p_line]).y; } else if (lines_rid.size() > 0) { @@ -81,13 +81,13 @@ int Label::get_line_height(int p_line) const { } return h; } else { - int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; return font->get_height(font_size); } } void Label::_shape() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); + Ref<StyleBox> style = theme_cache.normal_style; int width = (get_size().width - style->get_minimum_size().width); if (dirty || font_dirty) { @@ -99,8 +99,8 @@ void Label::_shape() { } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } - const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); - int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; + int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; ERR_FAIL_COND(font.is_null()); String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { @@ -232,8 +232,8 @@ void Label::_shape() { } void Label::_update_visible() { - int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing"), SNAME("Label")); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing; + Ref<StyleBox> style = theme_cache.normal_style; int lines_visible = lines_rid.size(); if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { @@ -272,6 +272,22 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col } } +void Label::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + theme_cache.font = get_theme_font(SNAME("font")); + + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.line_spacing = get_theme_constant(SNAME("line_spacing")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_shadow_color = get_theme_color(SNAME("font_shadow_color")); + theme_cache.font_shadow_offset = Point2(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); +} + void Label::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { @@ -307,15 +323,15 @@ void Label::_notification(int p_what) { Size2 string_size; Size2 size = get_size(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); - Color font_color = has_settings ? settings->get_font_color() : get_theme_color(SNAME("font_color")); - Color font_shadow_color = has_settings ? settings->get_shadow_color() : get_theme_color(SNAME("font_shadow_color")); - Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : Point2(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - int line_spacing = has_settings ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); - Color font_outline_color = has_settings ? settings->get_outline_color() : get_theme_color(SNAME("font_outline_color")); - int outline_size = has_settings ? settings->get_outline_size() : get_theme_constant(SNAME("outline_size")); - int shadow_outline_size = has_settings ? settings->get_shadow_size() : get_theme_constant(SNAME("shadow_outline_size")); + Ref<StyleBox> style = theme_cache.normal_style; + Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; + Color font_color = has_settings ? settings->get_font_color() : theme_cache.font_color; + Color font_shadow_color = has_settings ? settings->get_shadow_color() : theme_cache.font_shadow_color; + Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : theme_cache.font_shadow_offset; + int line_spacing = has_settings ? settings->get_line_spacing() : theme_cache.line_spacing; + Color font_outline_color = has_settings ? settings->get_outline_color() : theme_cache.font_outline_color; + int outline_size = has_settings ? settings->get_outline_size() : theme_cache.font_outline_size; + int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size; bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL); bool rtl_layout = is_layout_rtl(); @@ -562,12 +578,12 @@ Size2 Label::get_minimum_size() const { Size2 min_size = minsize; - const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); - int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; + int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; min_size.height = MAX(min_size.height, font->get_height(font_size) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); - Size2 min_style = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); + Size2 min_style = theme_cache.normal_style->get_minimum_size(); if (autowrap_mode != TextServer::AUTOWRAP_OFF) { return Size2(1, (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? 1 : min_size.height) + min_style; } else { @@ -590,8 +606,8 @@ int Label::get_line_count() const { } int Label::get_visible_line_count() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); + Ref<StyleBox> style = theme_cache.normal_style; + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing; int lines_visible = 0; float total_h = 0.0; for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { diff --git a/scene/gui/label.h b/scene/gui/label.h index 65831cab6e..b79c94a5ba 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -67,13 +67,28 @@ private: Ref<LabelSettings> settings; + struct ThemeCache { + Ref<StyleBox> normal_style; + Ref<Font> font; + + int font_size = 0; + int line_spacing = 0; + Color font_color; + Color font_shadow_color; + Point2 font_shadow_offset; + Color font_outline_color; + int font_outline_size; + int font_shadow_outline_size; + } theme_cache; + void _update_visible(); void _shape(); void _invalidate(); protected: - void _notification(int p_what); + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); public: diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index aa8a825014..c2ce4bdb83 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -448,7 +448,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { if (context_menu_enabled) { if (k->is_action("ui_menu", true)) { _ensure_menu(); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size")))) / 2); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); menu->set_position(get_screen_position() + pos); menu->reset_size(); menu->popup(); @@ -696,11 +696,38 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { if (!clear_button_enabled || !has_point(p_pos)) { return false; } - Ref<Texture2D> icon = Control::get_theme_icon(SNAME("clear")); - int x_ofs = get_theme_stylebox(SNAME("normal"))->get_margin(SIDE_RIGHT); + Ref<Texture2D> icon = theme_cache.clear_icon; + int x_ofs = theme_cache.normal->get_margin(SIDE_RIGHT); return p_pos.x > get_size().width - icon->get_width() - x_ofs; } +void LineEdit::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + theme_cache.read_only = get_theme_stylebox(SNAME("read_only")); + theme_cache.focus = get_theme_stylebox(SNAME("focus")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_uneditable_color = get_theme_color(SNAME("font_uneditable_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_placeholder_color = get_theme_color(SNAME("font_placeholder_color")); + theme_cache.caret_width = get_theme_constant(SNAME("caret_width")); + theme_cache.caret_color = get_theme_color(SNAME("caret_color")); + theme_cache.minimum_character_width = get_theme_constant(SNAME("minimum_character_width")); + theme_cache.selection_color = get_theme_color(SNAME("selection_color")); + + theme_cache.clear_icon = get_theme_icon(SNAME("clear")); + theme_cache.clear_button_color = get_theme_color(SNAME("clear_button_color")); + theme_cache.clear_button_color_pressed = get_theme_color(SNAME("clear_button_color_pressed")); + + theme_cache.base_scale = get_theme_default_base_scale(); +} + void LineEdit::_notification(int p_what) { switch (p_what) { #ifdef TOOLS_ENABLED @@ -771,19 +798,19 @@ void LineEdit::_notification(int p_what) { RID ci = get_canvas_item(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; if (!is_editable()) { - style = get_theme_stylebox(SNAME("read_only")); + style = theme_cache.read_only; draw_caret = false; } - Ref<Font> font = get_theme_font(SNAME("font")); + Ref<Font> font = theme_cache.font; if (!flat) { style->draw(ci, Rect2(Point2(), size)); } if (has_focus()) { - get_theme_stylebox(SNAME("focus"))->draw(ci, Rect2(Point2(), size)); + theme_cache.focus->draw(ci, Rect2(Point2(), size)); } int x_ofs = 0; @@ -821,25 +848,30 @@ void LineEdit::_notification(int p_what) { int y_area = height - style->get_minimum_size().height; int y_ofs = style->get_offset().y + (y_area - text_height) / 2; - Color selection_color = get_theme_color(SNAME("selection_color")); - Color font_color = get_theme_color(is_editable() ? SNAME("font_color") : SNAME("font_uneditable_color")); - Color font_selected_color = get_theme_color(SNAME("font_selected_color")); - Color caret_color = get_theme_color(SNAME("caret_color")); + Color selection_color = theme_cache.selection_color; + Color font_color; + if (is_editable()) { + font_color = theme_cache.font_color; + } else { + font_color = theme_cache.font_uneditable_color; + } + Color font_selected_color = theme_cache.font_selected_color; + Color caret_color = theme_cache.caret_color; // Draw placeholder color. if (using_placeholder) { - font_color = get_theme_color(SNAME("font_placeholder_color")); + font_color = theme_cache.font_placeholder_color; } bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { - Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; Color color_icon(1, 1, 1, !is_editable() ? .5 * .9 : .9); if (display_clear_icon) { if (clear_button_status.press_attempt && clear_button_status.pressing_inside) { - color_icon = get_theme_color(SNAME("clear_button_color_pressed")); + color_icon = theme_cache.clear_button_color_pressed; } else { - color_icon = get_theme_color(SNAME("clear_button_color")); + color_icon = theme_cache.clear_button_color; } } @@ -879,8 +911,8 @@ void LineEdit::_notification(int p_what) { // Draw text. ofs.y += TS->shaped_text_get_ascent(text_rid); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.font_outline_size; if (outline_size > 0 && font_outline_color.a > 0) { Vector2 oofs = ofs; for (int i = 0; i < gl_size; i++) { @@ -918,7 +950,7 @@ void LineEdit::_notification(int p_what) { ofs.x = x_ofs + scroll_offset; if (draw_caret || drag_caret_force_displayed) { // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1). - const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale()); + const int caret_width = theme_cache.caret_width * MAX(1, theme_cache.base_scale); if (ime_text.length() == 0) { // Normal caret. @@ -926,7 +958,7 @@ void LineEdit::_notification(int p_what) { if (caret.l_caret == Rect2() && caret.t_caret == Rect2()) { // No carets, add one at the start. - int h = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size"))); + int h = theme_cache.font->get_height(theme_cache.font_size); int y = style->get_offset().y + (y_area - h) / 2; if (rtl) { caret.l_dir = TextServer::DIRECTION_RTL; @@ -1193,7 +1225,7 @@ void LineEdit::shift_selection_check_post(bool p_shift) { } void LineEdit::set_caret_at_pixel_pos(int p_x) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1226,7 +1258,7 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { - Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); @@ -1241,7 +1273,7 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { } Vector2 LineEdit::get_caret_pixel_pos() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1274,7 +1306,7 @@ Vector2 LineEdit::get_caret_pixel_pos() { bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { - Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); @@ -1559,7 +1591,7 @@ void LineEdit::set_caret_column(int p_column) { return; } - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1593,7 +1625,7 @@ void LineEdit::set_caret_column(int p_column) { bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { - Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); @@ -1664,15 +1696,15 @@ void LineEdit::clear_internal() { } Size2 LineEdit::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + Ref<StyleBox> style = theme_cache.normal; + Ref<Font> font = theme_cache.font; + int font_size = theme_cache.font_size; Size2 min_size; // Minimum size of text. float em_space_size = font->get_char_size('M', font_size).x; - min_size.width = get_theme_constant(SNAME("minimum_character_width")) * em_space_size; + min_size.width = theme_cache.minimum_character_width * em_space_size; if (expand_to_text_length) { // Add a space because some fonts are too exact, and because caret needs a bit more when at the end. @@ -1688,9 +1720,8 @@ Size2 LineEdit::get_minimum_size() const { icon_max_width = right_icon->get_width(); } if (clear_button_enabled) { - Ref<Texture2D> clear_icon = Control::get_theme_icon(SNAME("clear")); - min_size.height = MAX(min_size.height, clear_icon->get_height()); - icon_max_width = MAX(icon_max_width, clear_icon->get_width()); + min_size.height = MAX(min_size.height, theme_cache.clear_icon->get_height()); + icon_max_width = MAX(icon_max_width, theme_cache.clear_icon->get_width()); } min_size.width += icon_max_width; @@ -2155,8 +2186,8 @@ void LineEdit::_shape() { } TS->shaped_text_set_preserve_control(text_rid, draw_control_chars); - const Ref<Font> &font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = theme_cache.font; + int font_size = theme_cache.font_size; ERR_FAIL_COND(font.is_null()); TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, font->get_opentype_features(), language); for (int i = 0; i < TextServer::SPACING_MAX; i++) { @@ -2176,12 +2207,12 @@ void LineEdit::_shape() { void LineEdit::_fit_to_width() { if (alignment == HORIZONTAL_ALIGNMENT_FILL) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; int t_width = get_size().width - style->get_margin(SIDE_RIGHT) - style->get_margin(SIDE_LEFT); bool using_placeholder = text.is_empty() && ime_text.is_empty(); bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { - Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; t_width -= r_icon->get_width(); } TS->shaped_text_fit_to_width(text_rid, MAX(t_width, full_width)); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index dabdaa3395..38863e805c 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -174,6 +174,31 @@ private: double caret_blink_timer = 0.0; bool caret_blinking = false; + struct ThemeCache { + Ref<StyleBox> normal; + Ref<StyleBox> read_only; + Ref<StyleBox> focus; + + Ref<Font> font; + int font_size = 0; + Color font_color; + Color font_uneditable_color; + Color font_selected_color; + int font_outline_size; + Color font_outline_color; + Color font_placeholder_color; + int caret_width = 0; + Color caret_color; + int minimum_character_width = 0; + Color selection_color; + + Ref<Texture2D> clear_icon; + Color clear_button_color; + Color clear_button_color_pressed; + + int base_scale = 0; + } theme_cache; + bool _is_over_clear_button(const Point2 &p_pos) const; void _clear_undo_stack(); @@ -215,6 +240,7 @@ private: void _ensure_menu(); protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index b0252ac685..7219e86f52 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -33,8 +33,8 @@ #include "core/string/translation.h" void LinkButton::_shape() { - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + Ref<Font> font = theme_cache.font; + int font_size = theme_cache.font_size; text_buf->clear(); if (text_direction == Control::TEXT_DIRECTION_INHERITED) { @@ -125,6 +125,26 @@ Size2 LinkButton::get_minimum_size() const { return text_buf->get_size(); } +void LinkButton::_update_theme_item_cache() { + BaseButton::_update_theme_item_cache(); + + theme_cache.focus = get_theme_stylebox(SNAME("focus")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.underline_spacing = get_theme_constant(SNAME("underline_spacing")); +} + void LinkButton::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { @@ -153,9 +173,9 @@ void LinkButton::_notification(int p_what) { switch (get_draw_mode()) { case DRAW_NORMAL: { if (has_focus()) { - color = get_theme_color(SNAME("font_focus_color")); + color = theme_cache.font_focus_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } do_underline = underline_mode == UNDERLINE_MODE_ALWAYS; @@ -163,35 +183,35 @@ void LinkButton::_notification(int p_what) { case DRAW_HOVER_PRESSED: case DRAW_PRESSED: { if (has_theme_color(SNAME("font_pressed_color"))) { - color = get_theme_color(SNAME("font_pressed_color")); + color = theme_cache.font_pressed_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } do_underline = underline_mode != UNDERLINE_MODE_NEVER; } break; case DRAW_HOVER: { - color = get_theme_color(SNAME("font_hover_color")); + color = theme_cache.font_hover_color; do_underline = underline_mode != UNDERLINE_MODE_NEVER; } break; case DRAW_DISABLED: { - color = get_theme_color(SNAME("font_disabled_color")); + color = theme_cache.font_disabled_color; do_underline = underline_mode == UNDERLINE_MODE_ALWAYS; } break; } if (has_focus()) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("focus")); + Ref<StyleBox> style = theme_cache.focus; style->draw(ci, Rect2(Point2(), size)); } int width = text_buf->get_line_width(); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; if (is_layout_rtl()) { if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(get_canvas_item(), Vector2(size.width - width, 0), outline_size, font_outline_color); @@ -205,7 +225,7 @@ void LinkButton::_notification(int p_what) { } if (do_underline) { - int underline_spacing = get_theme_constant(SNAME("underline_spacing")) + text_buf->get_line_underline_position(); + int underline_spacing = theme_cache.underline_spacing + text_buf->get_line_underline_position(); int y = text_buf->get_line_ascent() + underline_spacing; if (is_layout_rtl()) { diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 12a6a7618f..accd848163 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -55,10 +55,29 @@ private: TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; + struct ThemeCache { + Ref<StyleBox> focus; + + Color font_color; + Color font_focus_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_disabled_color; + + Ref<Font> font; + int font_size = 0; + int outline_size = 0; + Color font_outline_color; + + int underline_spacing = 0; + } theme_cache; + void _shape(); protected: virtual Size2 get_minimum_size() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index fac37a8634..60fe681824 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -30,12 +30,16 @@ #include "margin_container.h" -Size2 MarginContainer::get_minimum_size() const { - int margin_left = get_theme_constant(SNAME("margin_left")); - int margin_top = get_theme_constant(SNAME("margin_top")); - int margin_right = get_theme_constant(SNAME("margin_right")); - int margin_bottom = get_theme_constant(SNAME("margin_bottom")); +void MarginContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.margin_left = get_theme_constant(SNAME("margin_left")); + theme_cache.margin_top = get_theme_constant(SNAME("margin_top")); + theme_cache.margin_right = get_theme_constant(SNAME("margin_right")); + theme_cache.margin_bottom = get_theme_constant(SNAME("margin_bottom")); +} +Size2 MarginContainer::get_minimum_size() const { Size2 max; for (int i = 0; i < get_child_count(); i++) { @@ -59,8 +63,8 @@ Size2 MarginContainer::get_minimum_size() const { } } - max.width += (margin_left + margin_right); - max.height += (margin_top + margin_bottom); + max.width += (theme_cache.margin_left + theme_cache.margin_right); + max.height += (theme_cache.margin_top + theme_cache.margin_bottom); return max; } @@ -86,11 +90,6 @@ Vector<int> MarginContainer::get_allowed_size_flags_vertical() const { void MarginContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { - int margin_left = get_theme_constant(SNAME("margin_left")); - int margin_top = get_theme_constant(SNAME("margin_top")); - int margin_right = get_theme_constant(SNAME("margin_right")); - int margin_bottom = get_theme_constant(SNAME("margin_bottom")); - Size2 s = get_size(); for (int i = 0; i < get_child_count(); i++) { @@ -102,9 +101,9 @@ void MarginContainer::_notification(int p_what) { continue; } - int w = s.width - margin_left - margin_right; - int h = s.height - margin_top - margin_bottom; - fit_child_in_rect(c, Rect2(margin_left, margin_top, w, h)); + int w = s.width - theme_cache.margin_left - theme_cache.margin_right; + int h = s.height - theme_cache.margin_top - theme_cache.margin_bottom; + fit_child_in_rect(c, Rect2(theme_cache.margin_left, theme_cache.margin_top, w, h)); } } break; diff --git a/scene/gui/margin_container.h b/scene/gui/margin_container.h index f8a3c5bb11..5c33785170 100644 --- a/scene/gui/margin_container.h +++ b/scene/gui/margin_container.h @@ -36,7 +36,16 @@ class MarginContainer : public Container { GDCLASS(MarginContainer, Container); + struct ThemeCache { + int margin_left = 0; + int margin_top = 0; + int margin_right = 0; + int margin_bottom = 0; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); public: diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 788b320b46..0613652bdf 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -336,6 +336,35 @@ void MenuBar::_update_menu() { queue_redraw(); } +void MenuBar::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + theme_cache.normal_mirrored = get_theme_stylebox(SNAME("normal_mirrored")); + theme_cache.disabled = get_theme_stylebox(SNAME("disabled")); + theme_cache.disabled_mirrored = get_theme_stylebox(SNAME("disabled_mirrored")); + theme_cache.pressed = get_theme_stylebox(SNAME("pressed")); + theme_cache.pressed_mirrored = get_theme_stylebox(SNAME("pressed_mirrored")); + theme_cache.hover = get_theme_stylebox(SNAME("hover")); + theme_cache.hover_mirrored = get_theme_stylebox(SNAME("hover_mirrored")); + theme_cache.hover_pressed = get_theme_stylebox(SNAME("hover_pressed")); + theme_cache.hover_pressed_mirrored = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); +} + void MenuBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -397,8 +426,7 @@ void MenuBar::_notification(int p_what) { } int MenuBar::_get_index_at_point(const Point2 &p_point) const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - int hsep = get_theme_constant(SNAME("h_separation")); + Ref<StyleBox> style = theme_cache.normal; int offset = 0; for (int i = 0; i < menu_cache.size(); i++) { if (menu_cache[i].hidden) { @@ -410,7 +438,7 @@ int MenuBar::_get_index_at_point(const Point2 &p_point) const { return i; } } - offset += size.x + hsep; + offset += size.x + theme_cache.h_separation; } return -1; } @@ -418,8 +446,7 @@ int MenuBar::_get_index_at_point(const Point2 &p_point) const { Rect2 MenuBar::_get_menu_item_rect(int p_index) const { ERR_FAIL_INDEX_V(p_index, menu_cache.size(), Rect2()); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - int hsep = get_theme_constant(SNAME("h_separation")); + Ref<StyleBox> style = theme_cache.normal; int offset = 0; for (int i = 0; i < p_index; i++) { @@ -427,7 +454,7 @@ Rect2 MenuBar::_get_menu_item_rect(int p_index) const { continue; } Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size(); - offset += size.x + hsep; + offset += size.x + theme_cache.h_separation; } return Rect2(Point2(offset, 0), menu_cache[p_index].text_buf->get_size() + style->get_minimum_size()); @@ -446,76 +473,76 @@ void MenuBar::_draw_menu_item(int p_index) { } Color color; - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; Rect2 item_rect = _get_menu_item_rect(p_index); if (menu_cache[p_index].disabled) { if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) { - style = get_theme_stylebox(SNAME("disabled_mirrored")); + style = theme_cache.disabled_mirrored; } else { - style = get_theme_stylebox(SNAME("disabled")); + style = theme_cache.disabled; } if (!flat) { style->draw(ci, item_rect); } - color = get_theme_color(SNAME("font_disabled_color")); + color = theme_cache.font_disabled_color; } else if (hovered && pressed && has_theme_stylebox("hover_pressed")) { if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) { - style = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + style = theme_cache.hover_pressed_mirrored; } else { - style = get_theme_stylebox(SNAME("hover_pressed")); + style = theme_cache.hover_pressed; } if (!flat) { style->draw(ci, item_rect); } if (has_theme_color(SNAME("font_hover_pressed_color"))) { - color = get_theme_color(SNAME("font_hover_pressed_color")); + color = theme_cache.font_hover_pressed_color; } } else if (pressed) { if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) { - style = get_theme_stylebox(SNAME("pressed_mirrored")); + style = theme_cache.pressed_mirrored; } else { - style = get_theme_stylebox(SNAME("pressed")); + style = theme_cache.pressed; } if (!flat) { style->draw(ci, item_rect); } if (has_theme_color(SNAME("font_pressed_color"))) { - color = get_theme_color(SNAME("font_pressed_color")); + color = theme_cache.font_pressed_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } } else if (hovered) { if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) { - style = get_theme_stylebox(SNAME("hover_mirrored")); + style = theme_cache.hover_mirrored; } else { - style = get_theme_stylebox(SNAME("hover")); + style = theme_cache.hover; } if (!flat) { style->draw(ci, item_rect); } - color = get_theme_color(SNAME("font_hover_color")); + color = theme_cache.font_hover_color; } else { if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) { - style = get_theme_stylebox(SNAME("normal_mirrored")); + style = theme_cache.normal_mirrored; } else { - style = get_theme_stylebox(SNAME("normal")); + style = theme_cache.normal; } if (!flat) { style->draw(ci, item_rect); } // Focus colors only take precedence over normal state. if (has_focus()) { - color = get_theme_color(SNAME("font_focus_color")); + color = theme_cache.font_focus_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } } Point2 text_ofs = item_rect.position + Point2(style->get_margin(SIDE_LEFT), style->get_margin(SIDE_TOP)); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; if (outline_size > 0 && font_outline_color.a > 0) { menu_cache[p_index].text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); } @@ -523,16 +550,13 @@ void MenuBar::_draw_menu_item(int p_index) { } void MenuBar::shape(Menu &p_menu) { - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - p_menu.text_buf->clear(); if (text_direction == Control::TEXT_DIRECTION_INHERITED) { p_menu.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { p_menu.text_buf->set_direction((TextServer::Direction)text_direction); } - p_menu.text_buf->add_string(p_menu.name, font, font_size, language); + p_menu.text_buf->add_string(p_menu.name, theme_cache.font, theme_cache.font_size, language); } void MenuBar::_refresh_menu_names() { @@ -762,7 +786,7 @@ Size2 MenuBar::get_minimum_size() const { return Size2(); } - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; Vector2 size; for (int i = 0; i < menu_cache.size(); i++) { @@ -774,7 +798,7 @@ Size2 MenuBar::get_minimum_size() const { size.x += sz.x; } if (menu_cache.size() > 1) { - size.x += get_theme_constant(SNAME("h_separation")) * (menu_cache.size() - 1); + size.x += theme_cache.h_separation * (menu_cache.size() - 1); } return size; } diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index 5aa8ac7324..f7ef19e98b 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -76,6 +76,33 @@ class MenuBar : public Control { Vector2i old_mouse_pos; ObjectID shortcut_context; + struct ThemeCache { + Ref<StyleBox> normal; + Ref<StyleBox> normal_mirrored; + Ref<StyleBox> disabled; + Ref<StyleBox> disabled_mirrored; + Ref<StyleBox> pressed; + Ref<StyleBox> pressed_mirrored; + Ref<StyleBox> hover; + Ref<StyleBox> hover_mirrored; + Ref<StyleBox> hover_pressed; + Ref<StyleBox> hover_pressed_mirrored; + + Ref<Font> font; + int font_size = 0; + int outline_size = 0; + Color font_outline_color; + + Color font_color; + Color font_disabled_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_focus_color; + + int h_separation = 0; + } theme_cache; + int _get_index_at_point(const Point2 &p_point) const; Rect2 _get_menu_item_rect(int p_index) const; void _draw_menu_item(int p_index); @@ -96,6 +123,7 @@ class MenuBar : public Control { protected: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); virtual void add_child_notify(Node *p_child) override; virtual void move_child_notify(Node *p_child) override; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index a87c46e8ac..0dd9666858 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -43,11 +43,11 @@ Size2 OptionButton::get_minimum_size() const { } if (has_theme_icon(SNAME("arrow"))) { - const Size2 padding = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); - const Size2 arrow_size = Control::get_theme_icon(SNAME("arrow"))->get_size(); + const Size2 padding = theme_cache.normal->get_minimum_size(); + const Size2 arrow_size = theme_cache.arrow_icon->get_size(); Size2 content_size = minsize - padding; - content_size.width += arrow_size.width + MAX(0, get_theme_constant(SNAME("h_separation"))); + content_size.width += arrow_size.width + MAX(0, theme_cache.h_separation); content_size.height = MAX(content_size.height, arrow_size.height); minsize = content_size + padding; @@ -56,35 +56,63 @@ Size2 OptionButton::get_minimum_size() const { return minsize; } +void OptionButton::_update_theme_item_cache() { + Button::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + + theme_cache.arrow_icon = get_theme_icon(SNAME("arrow")); + theme_cache.arrow_margin = get_theme_constant(SNAME("arrow_margin")); + theme_cache.modulate_arrow = get_theme_constant(SNAME("modulate_arrow")); +} + void OptionButton::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + if (has_theme_icon(SNAME("arrow"))) { + if (is_layout_rtl()) { + _set_internal_margin(SIDE_LEFT, theme_cache.arrow_icon->get_width()); + } else { + _set_internal_margin(SIDE_RIGHT, theme_cache.arrow_icon->get_width()); + } + } + } break; + case NOTIFICATION_DRAW: { if (!has_theme_icon(SNAME("arrow"))) { return; } RID ci = get_canvas_item(); - Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow")); Color clr = Color(1, 1, 1); - if (get_theme_constant(SNAME("modulate_arrow"))) { + if (theme_cache.modulate_arrow) { switch (get_draw_mode()) { case DRAW_PRESSED: - clr = get_theme_color(SNAME("font_pressed_color")); + clr = theme_cache.font_pressed_color; break; case DRAW_HOVER: - clr = get_theme_color(SNAME("font_hover_color")); + clr = theme_cache.font_hover_color; break; case DRAW_HOVER_PRESSED: - clr = get_theme_color(SNAME("font_hover_pressed_color")); + clr = theme_cache.font_hover_pressed_color; break; case DRAW_DISABLED: - clr = get_theme_color(SNAME("font_disabled_color")); + clr = theme_cache.font_disabled_color; break; default: if (has_focus()) { - clr = get_theme_color(SNAME("font_focus_color")); + clr = theme_cache.font_focus_color; } else { - clr = get_theme_color(SNAME("font_color")); + clr = theme_cache.font_color; } } } @@ -93,11 +121,11 @@ void OptionButton::_notification(int p_what) { Point2 ofs; if (is_layout_rtl()) { - ofs = Point2(get_theme_constant(SNAME("arrow_margin")), int(Math::abs((size.height - arrow->get_height()) / 2))); + ofs = Point2(theme_cache.arrow_margin, int(Math::abs((size.height - theme_cache.arrow_icon->get_height()) / 2))); } else { - ofs = Point2(size.width - arrow->get_width() - get_theme_constant(SNAME("arrow_margin")), int(Math::abs((size.height - arrow->get_height()) / 2))); + ofs = Point2(size.width - theme_cache.arrow_icon->get_width() - theme_cache.arrow_margin, int(Math::abs((size.height - theme_cache.arrow_icon->get_height()) / 2))); } - arrow->draw(ci, ofs, clr); + theme_cache.arrow_icon->draw(ci, ofs, clr); } break; case NOTIFICATION_TRANSLATION_CHANGED: @@ -108,11 +136,11 @@ void OptionButton::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { if (has_theme_icon(SNAME("arrow"))) { if (is_layout_rtl()) { - _set_internal_margin(SIDE_LEFT, Control::get_theme_icon(SNAME("arrow"))->get_width()); + _set_internal_margin(SIDE_LEFT, theme_cache.arrow_icon->get_width()); _set_internal_margin(SIDE_RIGHT, 0.f); } else { _set_internal_margin(SIDE_LEFT, 0.f); - _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon(SNAME("arrow"))->get_width()); + _set_internal_margin(SIDE_RIGHT, theme_cache.arrow_icon->get_width()); } } _refresh_size_cache(); @@ -540,15 +568,6 @@ OptionButton::OptionButton(const String &p_text) : Button(p_text) { set_toggle_mode(true); set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - if (is_layout_rtl()) { - if (has_theme_icon(SNAME("arrow"))) { - _set_internal_margin(SIDE_LEFT, Control::get_theme_icon(SNAME("arrow"))->get_width()); - } - } else { - if (has_theme_icon(SNAME("arrow"))) { - _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon(SNAME("arrow"))->get_width()); - } - } set_action_mode(ACTION_MODE_BUTTON_PRESS); popup = memnew(PopupMenu); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index cd709b8f5f..2c7e0510f5 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -43,6 +43,23 @@ class OptionButton : public Button { Vector2 _cached_size; bool cache_refresh_pending = false; + struct ThemeCache { + Ref<StyleBox> normal; + + Color font_color; + Color font_focus_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_disabled_color; + + int h_separation = 0; + + Ref<Texture2D> arrow_icon; + int arrow_margin = 0; + int modulate_arrow = 0; + } theme_cache; + void _focused(int p_which); void _selected(int p_which); void _select(int p_which, bool p_emit = false); @@ -54,6 +71,7 @@ class OptionButton : public Button { protected: Size2 get_minimum_size() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; diff --git a/scene/gui/panel.cpp b/scene/gui/panel.cpp index 1ac6cf57ab..09bc295513 100644 --- a/scene/gui/panel.cpp +++ b/scene/gui/panel.cpp @@ -30,12 +30,17 @@ #include "panel.h" +void Panel::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void Panel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); - style->draw(ci, Rect2(Point2(), get_size())); + theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size())); } break; } } diff --git a/scene/gui/panel.h b/scene/gui/panel.h index 5d2e912680..f9bd721681 100644 --- a/scene/gui/panel.h +++ b/scene/gui/panel.h @@ -36,7 +36,13 @@ class Panel : public Control { GDCLASS(Panel, Control); + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); public: diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index fe01712a89..eeaded1788 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -31,14 +31,6 @@ #include "panel_container.h" Size2 PanelContainer::get_minimum_size() const { - Ref<StyleBox> style; - - if (has_theme_stylebox(SNAME("panel"))) { - style = get_theme_stylebox(SNAME("panel")); - } else { - style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); - } - Size2 ms; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -54,8 +46,8 @@ Size2 PanelContainer::get_minimum_size() const { ms.height = MAX(ms.height, minsize.height); } - if (style.is_valid()) { - ms += style->get_minimum_size(); + if (theme_cache.panel_style.is_valid()) { + ms += theme_cache.panel_style->get_minimum_size(); } return ms; } @@ -78,35 +70,25 @@ Vector<int> PanelContainer::get_allowed_size_flags_vertical() const { return flags; } +void PanelContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void PanelContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); - Ref<StyleBox> style; - - if (has_theme_stylebox(SNAME("panel"))) { - style = get_theme_stylebox(SNAME("panel")); - } else { - style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); - } - - style->draw(ci, Rect2(Point2(), get_size())); + theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size())); } break; case NOTIFICATION_SORT_CHILDREN: { - Ref<StyleBox> style; - - if (has_theme_stylebox(SNAME("panel"))) { - style = get_theme_stylebox(SNAME("panel")); - } else { - style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); - } - Size2 size = get_size(); Point2 ofs; - if (style.is_valid()) { - size -= style->get_minimum_size(); - ofs += style->get_offset(); + if (theme_cache.panel_style.is_valid()) { + size -= theme_cache.panel_style->get_minimum_size(); + ofs += theme_cache.panel_style->get_offset(); } for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/panel_container.h b/scene/gui/panel_container.h index 8f07ce38eb..97d0cdc872 100644 --- a/scene/gui/panel_container.h +++ b/scene/gui/panel_container.h @@ -36,7 +36,12 @@ class PanelContainer : public Container { GDCLASS(PanelContainer, Container); + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); public: diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index c4396f636a..ceae3791f3 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -68,6 +68,12 @@ void Popup::_deinitialize_visible_parents() { } } +void Popup::_update_theme_item_cache() { + Window::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void Popup::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { @@ -186,8 +192,6 @@ Popup::~Popup() { } Size2 PopupPanel::_get_contents_minimum_size() const { - Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); - Size2 ms; for (int i = 0; i < get_child_count(); i++) { @@ -205,14 +209,12 @@ Size2 PopupPanel::_get_contents_minimum_size() const { ms.y = MAX(cms.y, ms.y); } - return ms + p->get_minimum_size(); + return ms + theme_cache.panel_style->get_minimum_size(); } void PopupPanel::_update_child_rects() { - Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); - - Vector2 cpos(p->get_offset()); - Vector2 csize(get_size() - p->get_minimum_size()); + Vector2 cpos(theme_cache.panel_style->get_offset()); + Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size()); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -234,15 +236,17 @@ void PopupPanel::_update_child_rects() { } } +void PopupPanel::_update_theme_item_cache() { + Popup::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void PopupPanel::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: case NOTIFICATION_THEME_CHANGED: { - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); - } break; - - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_READY: { - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); + panel->add_theme_style_override("panel", theme_cache.panel_style); _update_child_rects(); } break; diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 70eb8722d0..0d6ca25c18 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -43,6 +43,10 @@ class Popup : public Window { LocalVector<Window *> visible_parents; bool popped_up = false; + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + void _input_from_window(const Ref<InputEvent> &p_event); void _initialize_visible_parents(); @@ -52,6 +56,7 @@ protected: void _close_pressed(); virtual Rect2i _popup_adjust_rect() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); @@ -69,8 +74,14 @@ class PopupPanel : public Popup { Panel *panel = nullptr; + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + protected: void _update_child_rects(); + + virtual void _update_theme_item_cache() override; void _notification(int p_what); virtual Size2 _get_contents_minimum_size() const override; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index c3060bf242..bcc84fc8dc 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -48,15 +48,12 @@ String PopupMenu::_get_accel_text(const Item &p_item) const { } Size2 PopupMenu::_get_contents_minimum_size() const { - int vseparation = get_theme_constant(SNAME("v_separation")); - int hseparation = get_theme_constant(SNAME("h_separation")); - - Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container + Size2 minsize = theme_cache.panel_style->get_minimum_size(); // Accounts for margin in the margin container minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content float max_w = 0.0; float icon_w = 0.0; - int check_w = MAX(get_theme_icon(SNAME("checked"))->get_width(), get_theme_icon(SNAME("radio_checked"))->get_width()) + hseparation; + int check_w = MAX(theme_cache.checked->get_width(), theme_cache.radio_checked->get_width()) + theme_cache.h_separation; int accel_max_w = 0; bool has_check = false; @@ -67,23 +64,23 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.height = _get_item_height(i); icon_w = MAX(icon_size.width, icon_w); - size.width += items[i].indent * get_theme_constant(SNAME("indent")); + size.width += items[i].indent * theme_cache.indent; if (items[i].checkable_type && !items[i].separator) { has_check = true; } size.width += items[i].text_buf->get_size().x; - size.height += vseparation; + size.height += theme_cache.v_separation; if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { - int accel_w = hseparation * 2; + int accel_w = theme_cache.h_separation * 2; accel_w += items[i].accel_text_buf->get_size().x; accel_max_w = MAX(accel_w, accel_max_w); } if (!items[i].submenu.is_empty()) { - size.width += get_theme_icon(SNAME("submenu"))->get_width(); + size.width += theme_cache.submenu->get_width(); } max_w = MAX(max_w, size.width); @@ -91,7 +88,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { minsize.height += size.height; } - int item_side_padding = get_theme_constant(SNAME("item_start_padding")) + get_theme_constant(SNAME("item_end_padding")); + int item_side_padding = theme_cache.item_start_padding + theme_cache.item_end_padding; minsize.width += max_w + icon_w + accel_max_w + item_side_padding; if (has_check) { @@ -113,33 +110,31 @@ int PopupMenu::_get_item_height(int p_item) const { int icon_height = items[p_item].get_icon_size().height; if (items[p_item].checkable_type && !items[p_item].separator) { - icon_height = MAX(icon_height, MAX(get_theme_icon(SNAME("checked"))->get_height(), get_theme_icon(SNAME("radio_checked"))->get_height())); + icon_height = MAX(icon_height, MAX(theme_cache.checked->get_height(), theme_cache.radio_checked->get_height())); } int text_height = items[p_item].text_buf->get_size().height; if (text_height == 0 && !items[p_item].separator) { - text_height = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size"))); + text_height = theme_cache.font->get_height(theme_cache.font_size); } int separator_height = 0; if (items[p_item].separator) { - separator_height = MAX(get_theme_stylebox(SNAME("separator"))->get_minimum_size().height, MAX(get_theme_stylebox(SNAME("labeled_separator_left"))->get_minimum_size().height, get_theme_stylebox(SNAME("labeled_separator_right"))->get_minimum_size().height)); + separator_height = MAX(theme_cache.separator_style->get_minimum_size().height, MAX(theme_cache.labeled_separator_left->get_minimum_size().height, theme_cache.labeled_separator_right->get_minimum_size().height)); } return MAX(separator_height, MAX(text_height, icon_height)); } int PopupMenu::_get_items_total_height() const { - int vsep = get_theme_constant(SNAME("v_separation")); - // Get total height of all items by taking max of icon height and font height int items_total_height = 0; for (int i = 0; i < items.size(); i++) { - items_total_height += _get_item_height(i) + vsep; + items_total_height += _get_item_height(i) + theme_cache.v_separation; } // Subtract a separator which is not needed for the last item. - return items_total_height - vsep; + return items_total_height - theme_cache.v_separation; } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { @@ -147,18 +142,15 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { return -1; } - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container - - int vseparation = get_theme_constant(SNAME("v_separation")); - - Point2 ofs = style->get_offset() + Point2(0, vseparation / 2); + // Accounts for margin in the margin container + Point2 ofs = theme_cache.panel_style->get_offset() + Point2(0, theme_cache.v_separation / 2); if (ofs.y > p_over.y) { return -1; } for (int i = 0; i < items.size(); i++) { - ofs.y += i > 0 ? vseparation : (float)vseparation / 2; + ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2; ofs.y += _get_item_height(i); @@ -179,9 +171,6 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { return; // Already visible. } - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); - int vsep = get_theme_constant(SNAME("v_separation")); - Point2 this_pos = get_position(); Rect2 this_rect(this_pos, get_size()); @@ -231,7 +220,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. Rect2 safe_area = this_rect; - safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2; + safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; safe_area.size.y = items[p_over]._height_cache; DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area); @@ -240,11 +229,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Autohide area above the submenu item. submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2)); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); // If there is an area below the submenu item, add an autohide area there. if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height; + int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -528,34 +517,17 @@ void PopupMenu::_draw_items() { margin_size.height = margin_container->get_theme_constant(SNAME("margin_top")) + margin_container->get_theme_constant(SNAME("margin_bottom")); // Space between the item content and the sides of popup menu. - int item_start_padding = get_theme_constant(SNAME("item_start_padding")); - int item_end_padding = get_theme_constant(SNAME("item_end_padding")); - bool rtl = control->is_layout_rtl(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); - Ref<StyleBox> hover = get_theme_stylebox(SNAME("hover")); // In Item::checkable_type enum order (less the non-checkable member), with disabled repeated at the end. - Ref<Texture2D> check[] = { get_theme_icon(SNAME("checked")), get_theme_icon(SNAME("radio_checked")), get_theme_icon(SNAME("checked_disabled")), get_theme_icon(SNAME("radio_checked_disabled")) }; - Ref<Texture2D> uncheck[] = { get_theme_icon(SNAME("unchecked")), get_theme_icon(SNAME("radio_unchecked")), get_theme_icon(SNAME("unchecked_disabled")), get_theme_icon(SNAME("radio_unchecked_disabled")) }; + Ref<Texture2D> check[] = { theme_cache.checked, theme_cache.radio_checked, theme_cache.checked_disabled, theme_cache.radio_checked_disabled }; + Ref<Texture2D> uncheck[] = { theme_cache.unchecked, theme_cache.radio_unchecked, theme_cache.unchecked_disabled, theme_cache.radio_unchecked_disabled }; Ref<Texture2D> submenu; if (rtl) { - submenu = get_theme_icon(SNAME("submenu_mirrored")); + submenu = theme_cache.submenu_mirrored; } else { - submenu = get_theme_icon(SNAME("submenu")); + submenu = theme_cache.submenu; } - Ref<StyleBox> separator = get_theme_stylebox(SNAME("separator")); - Ref<StyleBox> labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left")); - Ref<StyleBox> labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right")); - - int vseparation = get_theme_constant(SNAME("v_separation")); - int hseparation = get_theme_constant(SNAME("h_separation")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_disabled_color = get_theme_color(SNAME("font_disabled_color")); - Color font_accelerator_color = get_theme_color(SNAME("font_accelerator_color")); - Color font_hover_color = get_theme_color(SNAME("font_hover_color")); - Color font_separator_color = get_theme_color(SNAME("font_separator_color")); - float scroll_width = scroll_container->get_v_scroll_bar()->is_visible_in_tree() ? scroll_container->get_v_scroll_bar()->get_size().width : 0; float display_width = control->get_size().width - scroll_width; @@ -574,7 +546,7 @@ void PopupMenu::_draw_items() { } } if (icon_ofs > 0.0) { - icon_ofs += hseparation; + icon_ofs += theme_cache.h_separation; } float check_ofs = 0.0; @@ -583,7 +555,7 @@ void PopupMenu::_draw_items() { check_ofs = MAX(check_ofs, check[i]->get_width()); check_ofs = MAX(check_ofs, uncheck[i]->get_width()); } - check_ofs += hseparation; + check_ofs += theme_cache.h_separation; } Point2 ofs = Point2(); @@ -591,7 +563,7 @@ void PopupMenu::_draw_items() { // Loop through all items and draw each. for (int i = 0; i < items.size(); i++) { // For the first item only add half a separation. For all other items, add a whole separation to the offset. - ofs.y += i > 0 ? vseparation : (float)vseparation / 2; + ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2; _shape_item(i); @@ -601,47 +573,47 @@ void PopupMenu::_draw_items() { if (i == mouse_over) { if (rtl) { - hover->draw(ci, Rect2(item_ofs + Point2(scroll_width, -vseparation / 2), Size2(display_width, h + vseparation))); + theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(scroll_width, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation))); } else { - hover->draw(ci, Rect2(item_ofs + Point2(0, -vseparation / 2), Size2(display_width, h + vseparation))); + theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(0, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation))); } } String text = items[i].xl_text; // Separator - item_ofs.x += items[i].indent * get_theme_constant(SNAME("indent")); + item_ofs.x += items[i].indent * theme_cache.indent; if (items[i].separator) { if (!text.is_empty() || !items[i].icon.is_null()) { - int content_size = items[i].text_buf->get_size().width + hseparation * 2; + int content_size = items[i].text_buf->get_size().width + theme_cache.h_separation * 2; if (!items[i].icon.is_null()) { - content_size += icon_size.width + hseparation; + content_size += icon_size.width + theme_cache.h_separation; } int content_center = display_width / 2; int content_left = content_center - content_size / 2; int content_right = content_center + content_size / 2; if (content_left > item_ofs.x) { - int sep_h = labeled_separator_left->get_center_size().height + labeled_separator_left->get_minimum_size().height; + int sep_h = theme_cache.labeled_separator_left->get_center_size().height + theme_cache.labeled_separator_left->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h))); + theme_cache.labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h))); } if (content_right < display_width) { - int sep_h = labeled_separator_right->get_center_size().height + labeled_separator_right->get_minimum_size().height; + int sep_h = theme_cache.labeled_separator_right->get_center_size().height + theme_cache.labeled_separator_right->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h))); + theme_cache.labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h))); } } else { - int sep_h = separator->get_center_size().height + separator->get_minimum_size().height; + int sep_h = theme_cache.separator_style->get_center_size().height + theme_cache.separator_style->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h))); + theme_cache.separator_style->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h))); } } Color icon_color(1, 1, 1, items[i].disabled && !items[i].separator ? 0.5 : 1); // For non-separator items, add some padding for the content. - item_ofs.x += item_start_padding; + item_ofs.x += theme_cache.item_start_padding; // Checkboxes if (items[i].checkable_type && !items[i].separator) { @@ -659,7 +631,7 @@ void PopupMenu::_draw_items() { // Icon if (!items[i].icon.is_null()) { if (items[i].separator) { - separator_ofs -= (icon_size.width + hseparation) / 2; + separator_ofs -= (icon_size.width + theme_cache.h_separation) / 2; if (rtl) { items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); @@ -678,61 +650,55 @@ void PopupMenu::_draw_items() { // Submenu arrow on right hand side. if (!items[i].submenu.is_empty()) { if (rtl) { - submenu->draw(ci, Point2(scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); + submenu->draw(ci, Point2(scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); } else { - submenu->draw(ci, Point2(display_width - style->get_margin(SIDE_RIGHT) - submenu->get_width() - item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); + submenu->draw(ci, Point2(display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - submenu->get_width() - theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); } } - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - // Text if (items[i].separator) { - Color font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color")); - int separator_outline_size = get_theme_constant(SNAME("separator_outline_size")); - if (!text.is_empty()) { Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); if (!rtl && !items[i].icon.is_null()) { - text_pos.x += icon_size.width + hseparation; + text_pos.x += icon_size.width + theme_cache.h_separation; } - if (separator_outline_size > 0 && font_separator_outline_color.a > 0) { - items[i].text_buf->draw_outline(ci, text_pos, separator_outline_size, font_separator_outline_color); + if (theme_cache.font_separator_outline_size > 0 && theme_cache.font_separator_outline_color.a > 0) { + items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_separator_outline_size, theme_cache.font_separator_outline_color); } - items[i].text_buf->draw(ci, text_pos, font_separator_color); + items[i].text_buf->draw(ci, text_pos, theme_cache.font_separator_color); } } else { item_ofs.x += icon_ofs + check_ofs; if (rtl) { Vector2 text_pos = Size2(control->get_size().width - items[i].text_buf->get_size().width - item_ofs.x, item_ofs.y) + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color)); + items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color)); } else { Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color)); + items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color)); } } // Accelerator / Shortcut if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { if (rtl) { - item_ofs.x = scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding; + item_ofs.x = scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding; } else { - item_ofs.x = display_width - style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - item_end_padding; + item_ofs.x = display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - theme_cache.item_end_padding; } Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].accel_text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].accel_text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? font_hover_color : font_accelerator_color); + items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_accelerator_color); } // Cache the item vertical offset from the first item and the height. @@ -744,9 +710,8 @@ void PopupMenu::_draw_items() { } void PopupMenu::_draw_background() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); RID ci2 = margin_container->get_canvas_item(); - style->draw(ci2, Rect2(Point2(), margin_container->get_size())); + theme_cache.panel_style->draw(ci2, Rect2(Point2(), margin_container->get_size())); } void PopupMenu::_minimum_lifetime_timeout() { @@ -778,8 +743,8 @@ void PopupMenu::_shape_item(int p_item) { if (items.write[p_item].dirty) { items.write[p_item].text_buf->clear(); - Ref<Font> font = get_theme_font(items[p_item].separator ? SNAME("font_separator") : SNAME("font")); - int font_size = get_theme_font_size(items[p_item].separator ? SNAME("font_separator_size") : SNAME("font_size")); + Ref<Font> font = items[p_item].separator ? theme_cache.font_separator : theme_cache.font; + int font_size = items[p_item].separator ? theme_cache.font_separator_size : theme_cache.font_size; if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) { items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); @@ -821,6 +786,51 @@ void PopupMenu::remove_child_notify(Node *p_child) { _menu_changed(); } +void PopupMenu::_update_theme_item_cache() { + Popup::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.hover_style = get_theme_stylebox(SNAME("hover")); + + theme_cache.separator_style = get_theme_stylebox(SNAME("separator")); + theme_cache.labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left")); + theme_cache.labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right")); + + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.indent = get_theme_constant(SNAME("indent")); + theme_cache.item_start_padding = get_theme_constant(SNAME("item_start_padding")); + theme_cache.item_end_padding = get_theme_constant(SNAME("item_end_padding")); + + theme_cache.checked = get_theme_icon(SNAME("checked")); + theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled")); + theme_cache.unchecked = get_theme_icon(SNAME("unchecked")); + theme_cache.unchecked_disabled = get_theme_icon(SNAME("unchecked_disabled")); + theme_cache.radio_checked = get_theme_icon(SNAME("radio_checked")); + theme_cache.radio_checked_disabled = get_theme_icon(SNAME("radio_checked_disabled")); + theme_cache.radio_unchecked = get_theme_icon(SNAME("radio_unchecked")); + theme_cache.radio_unchecked_disabled = get_theme_icon(SNAME("radio_unchecked_disabled")); + + theme_cache.submenu = get_theme_icon(SNAME("submenu")); + theme_cache.submenu_mirrored = get_theme_icon(SNAME("submenu_mirrored")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_separator = get_theme_font(SNAME("font_separator")); + theme_cache.font_separator_size = get_theme_font_size(SNAME("font_separator_size")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_accelerator_color = get_theme_color(SNAME("font_accelerator_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.font_separator_color = get_theme_color(SNAME("font_separator_color")); + theme_cache.font_separator_outline_size = get_theme_constant(SNAME("separator_outline_size")); + theme_cache.font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color")); +} + void PopupMenu::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -909,11 +919,10 @@ void PopupMenu::_notification(int p_what) { } // Set margin on the margin container - Ref<StyleBox> panel_style = get_theme_stylebox(SNAME("panel")); - margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Side::SIDE_LEFT)); - margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP)); - margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Side::SIDE_RIGHT)); - margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM)); + margin_container->add_theme_constant_override("margin_left", theme_cache.panel_style->get_margin(Side::SIDE_LEFT)); + margin_container->add_theme_constant_override("margin_top", theme_cache.panel_style->get_margin(Side::SIDE_TOP)); + margin_container->add_theme_constant_override("margin_right", theme_cache.panel_style->get_margin(Side::SIDE_RIGHT)); + margin_container->add_theme_constant_override("margin_bottom", theme_cache.panel_style->get_margin(Side::SIDE_BOTTOM)); } } break; } diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index d3ad0762e4..c8c598bd50 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -129,6 +129,49 @@ class PopupMenu : public Popup { ScrollContainer *scroll_container = nullptr; Control *control = nullptr; + struct ThemeCache { + Ref<StyleBox> panel_style; + Ref<StyleBox> hover_style; + + Ref<StyleBox> separator_style; + Ref<StyleBox> labeled_separator_left; + Ref<StyleBox> labeled_separator_right; + + int v_separation = 0; + int h_separation = 0; + int indent = 0; + int item_start_padding = 0; + int item_end_padding = 0; + + Ref<Texture2D> checked; + Ref<Texture2D> checked_disabled; + Ref<Texture2D> unchecked; + Ref<Texture2D> unchecked_disabled; + Ref<Texture2D> radio_checked; + Ref<Texture2D> radio_checked_disabled; + Ref<Texture2D> radio_unchecked; + Ref<Texture2D> radio_unchecked_disabled; + + Ref<Texture2D> submenu; + Ref<Texture2D> submenu_mirrored; + + Ref<Font> font; + int font_size = 0; + Ref<Font> font_separator; + int font_separator_size = 0; + + Color font_color; + Color font_hover_color; + Color font_disabled_color; + Color font_accelerator_color; + int font_outline_size = 0; + Color font_outline_color; + + Color font_separator_color; + int font_separator_outline_size = 0; + Color font_separator_outline_color; + } theme_cache; + void _draw_items(); void _draw_background(); @@ -137,6 +180,8 @@ class PopupMenu : public Popup { void _menu_changed(); protected: + virtual void _update_theme_item_cache() override; + virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; void _notification(int p_what); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 63a2db4569..fe609dd834 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -33,18 +33,13 @@ #include "scene/resources/text_line.h" Size2 ProgressBar::get_minimum_size() const { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg")); - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - - Size2 minimum_size = bg->get_minimum_size(); - minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height); - minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width); + Size2 minimum_size = theme_cache.bg_style->get_minimum_size(); + minimum_size.height = MAX(minimum_size.height, theme_cache.fg_style->get_minimum_size().height); + minimum_size.width = MAX(minimum_size.width, theme_cache.fg_style->get_minimum_size().width); if (percent_visible) { String txt = "100%"; - TextLine tl = TextLine(txt, font, font_size); - minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + tl.get_size().y); + TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size); + minimum_size.height = MAX(minimum_size.height, theme_cache.bg_style->get_minimum_size().height + tl.get_size().y); } else { // this is needed, else the progressbar will collapse minimum_size.width = MAX(minimum_size.width, 1); minimum_size.height = MAX(minimum_size.height, 1); @@ -52,23 +47,30 @@ Size2 ProgressBar::get_minimum_size() const { return minimum_size; } +void ProgressBar::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.bg_style = get_theme_stylebox(SNAME("bg")); + theme_cache.fg_style = get_theme_stylebox(SNAME("fg")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); +} + void ProgressBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg")); - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - Color font_color = get_theme_color(SNAME("font_color")); - - draw_style_box(bg, Rect2(Point2(), get_size())); + draw_style_box(theme_cache.bg_style, Rect2(Point2(), get_size())); float r = get_as_ratio(); switch (mode) { case FILL_BEGIN_TO_END: case FILL_END_TO_BEGIN: { - int mp = fg->get_minimum_size().width; + int mp = theme_cache.fg_style->get_minimum_size().width; int p = round(r * (get_size().width - mp)); // We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL, // and left to right otherwise. And likewise for FILL_END_TO_BEGIN. @@ -76,23 +78,23 @@ void ProgressBar::_notification(int p_what) { if (p > 0) { if (right_to_left) { int p_remaining = round((1.0 - r) * (get_size().width - mp)); - draw_style_box(fg, Rect2(Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + draw_style_box(theme_cache.fg_style, Rect2(Point2(p_remaining, 0), Size2(p + theme_cache.fg_style->get_minimum_size().width, get_size().height))); } else { - draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + draw_style_box(theme_cache.fg_style, Rect2(Point2(0, 0), Size2(p + theme_cache.fg_style->get_minimum_size().width, get_size().height))); } } } break; case FILL_TOP_TO_BOTTOM: case FILL_BOTTOM_TO_TOP: { - int mp = fg->get_minimum_size().height; + int mp = theme_cache.fg_style->get_minimum_size().height; int p = round(r * (get_size().height - mp)); if (p > 0) { if (mode == FILL_TOP_TO_BOTTOM) { - draw_style_box(fg, Rect2(Point2(0, 0), Size2(get_size().width, p + fg->get_minimum_size().height))); + draw_style_box(theme_cache.fg_style, Rect2(Point2(0, 0), Size2(get_size().width, p + theme_cache.fg_style->get_minimum_size().height))); } else { int p_remaining = round((1.0 - r) * (get_size().height - mp)); - draw_style_box(fg, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + fg->get_minimum_size().height))); + draw_style_box(theme_cache.fg_style, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + theme_cache.fg_style->get_minimum_size().height))); } } } break; @@ -102,14 +104,14 @@ void ProgressBar::_notification(int p_what) { if (percent_visible) { String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign(); - TextLine tl = TextLine(txt, font, font_size); + TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size); Vector2 text_pos = (Point2(get_size().width - tl.get_size().x, get_size().height - tl.get_size().y) / 2).round(); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - if (outline_size > 0 && font_outline_color.a > 0) { - tl.draw_outline(get_canvas_item(), text_pos, outline_size, font_outline_color); + + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + tl.draw_outline(get_canvas_item(), text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - tl.draw(get_canvas_item(), text_pos, font_color); + + tl.draw(get_canvas_item(), text_pos, theme_cache.font_color); } } break; } diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h index 5ba21ad7d5..c79b901928 100644 --- a/scene/gui/progress_bar.h +++ b/scene/gui/progress_bar.h @@ -38,7 +38,20 @@ class ProgressBar : public Range { bool percent_visible = true; + struct ThemeCache { + Ref<StyleBox> bg_style; + Ref<StyleBox> fg_style; + + Ref<Font> font; + int font_size = 0; + Color font_color; + int font_outline_size = 0; + Color font_outline_color; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 2555318f39..374ce6feea 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -70,8 +70,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (b->is_pressed()) { double ofs = orientation == VERTICAL ? b->get_position().y : b->get_position().x; - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = theme_cache.decrement_icon; + Ref<Texture2D> incr = theme_cache.increment_icon; double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width(); @@ -146,7 +146,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (drag.active) { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); + Ref<Texture2D> decr = theme_cache.decrement_icon; double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); ofs -= decr_size; @@ -156,8 +156,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { set_as_ratio(drag.value_at_click + diff); } else { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = theme_cache.decrement_icon; + Ref<Texture2D> incr = theme_cache.increment_icon; double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width(); @@ -217,6 +217,24 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } } +void ScrollBar::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.scroll_style = get_theme_stylebox(SNAME("scroll")); + theme_cache.scroll_focus_style = get_theme_stylebox(SNAME("scroll_focus")); + theme_cache.scroll_offset_style = get_theme_stylebox(SNAME("hscroll")); + theme_cache.grabber_style = get_theme_stylebox(SNAME("grabber")); + theme_cache.grabber_hl_style = get_theme_stylebox(SNAME("grabber_highlight")); + theme_cache.grabber_pressed_style = get_theme_stylebox(SNAME("grabber_pressed")); + + theme_cache.increment_icon = get_theme_icon(SNAME("increment")); + theme_cache.increment_hl_icon = get_theme_icon(SNAME("increment_highlight")); + theme_cache.increment_pressed_icon = get_theme_icon(SNAME("increment_pressed")); + theme_cache.decrement_icon = get_theme_icon(SNAME("decrement")); + theme_cache.decrement_hl_icon = get_theme_icon(SNAME("decrement_highlight")); + theme_cache.decrement_pressed_icon = get_theme_icon(SNAME("decrement_pressed")); +} + void ScrollBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { @@ -225,30 +243,30 @@ void ScrollBar::_notification(int p_what) { Ref<Texture2D> decr, incr; if (decr_active) { - decr = get_theme_icon(SNAME("decrement_pressed")); + decr = theme_cache.decrement_pressed_icon; } else if (highlight == HIGHLIGHT_DECR) { - decr = get_theme_icon(SNAME("decrement_highlight")); + decr = theme_cache.decrement_hl_icon; } else { - decr = get_theme_icon(SNAME("decrement")); + decr = theme_cache.decrement_icon; } if (incr_active) { - incr = get_theme_icon(SNAME("increment_pressed")); + incr = theme_cache.increment_pressed_icon; } else if (highlight == HIGHLIGHT_INCR) { - incr = get_theme_icon(SNAME("increment_highlight")); + incr = theme_cache.increment_hl_icon; } else { - incr = get_theme_icon(SNAME("increment")); + incr = theme_cache.increment_icon; } - Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); + Ref<StyleBox> bg = has_focus() ? theme_cache.scroll_focus_style : theme_cache.scroll_style; Ref<StyleBox> grabber; if (drag.active) { - grabber = get_theme_stylebox(SNAME("grabber_pressed")); + grabber = theme_cache.grabber_pressed_style; } else if (highlight == HIGHLIGHT_RANGE) { - grabber = get_theme_stylebox(SNAME("grabber_highlight")); + grabber = theme_cache.grabber_hl_style; } else { - grabber = get_theme_stylebox(SNAME("grabber")); + grabber = theme_cache.grabber_style; } Point2 ofs; @@ -414,7 +432,7 @@ void ScrollBar::_notification(int p_what) { } double ScrollBar::get_grabber_min_size() const { - Ref<StyleBox> grabber = get_theme_stylebox(SNAME("grabber")); + Ref<StyleBox> grabber = theme_cache.grabber_style; Size2 gminsize = grabber->get_minimum_size() + grabber->get_center_size(); return (orientation == VERTICAL) ? gminsize.height : gminsize.width; } @@ -435,17 +453,17 @@ double ScrollBar::get_area_size() const { switch (orientation) { case VERTICAL: { double area = get_size().height; - area -= get_theme_stylebox(SNAME("scroll"))->get_minimum_size().height; - area -= get_theme_icon(SNAME("increment"))->get_height(); - area -= get_theme_icon(SNAME("decrement"))->get_height(); + area -= theme_cache.scroll_style->get_minimum_size().height; + area -= theme_cache.increment_icon->get_height(); + area -= theme_cache.decrement_icon->get_height(); area -= get_grabber_min_size(); return area; } break; case HORIZONTAL: { double area = get_size().width; - area -= get_theme_stylebox(SNAME("scroll"))->get_minimum_size().width; - area -= get_theme_icon(SNAME("increment"))->get_width(); - area -= get_theme_icon(SNAME("decrement"))->get_width(); + area -= theme_cache.scroll_style->get_minimum_size().width; + area -= theme_cache.increment_icon->get_width(); + area -= theme_cache.decrement_icon->get_width(); area -= get_grabber_min_size(); return area; } break; @@ -459,13 +477,13 @@ double ScrollBar::get_area_offset() const { double ofs = 0.0; if (orientation == VERTICAL) { - ofs += get_theme_stylebox(SNAME("hscroll"))->get_margin(SIDE_TOP); - ofs += get_theme_icon(SNAME("decrement"))->get_height(); + ofs += theme_cache.scroll_offset_style->get_margin(SIDE_TOP); + ofs += theme_cache.decrement_icon->get_height(); } if (orientation == HORIZONTAL) { - ofs += get_theme_stylebox(SNAME("hscroll"))->get_margin(SIDE_LEFT); - ofs += get_theme_icon(SNAME("decrement"))->get_width(); + ofs += theme_cache.scroll_offset_style->get_margin(SIDE_LEFT); + ofs += theme_cache.decrement_icon->get_width(); } return ofs; @@ -476,9 +494,9 @@ double ScrollBar::get_grabber_offset() const { } Size2 ScrollBar::get_minimum_size() const { - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - Ref<StyleBox> bg = get_theme_stylebox(SNAME("scroll")); + Ref<Texture2D> incr = theme_cache.increment_icon; + Ref<Texture2D> decr = theme_cache.decrement_icon; + Ref<StyleBox> bg = theme_cache.scroll_style; Size2 minsize; if (orientation == VERTICAL) { diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 1823f86a67..13ca62d7ff 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -86,14 +86,31 @@ class ScrollBar : public Range { double target_scroll = 0.0; bool smooth_scroll_enabled = false; + struct ThemeCache { + Ref<StyleBox> scroll_style; + Ref<StyleBox> scroll_focus_style; + Ref<StyleBox> scroll_offset_style; + Ref<StyleBox> grabber_style; + Ref<StyleBox> grabber_hl_style; + Ref<StyleBox> grabber_pressed_style; + + Ref<Texture2D> increment_icon; + Ref<Texture2D> increment_hl_icon; + Ref<Texture2D> increment_pressed_icon; + Ref<Texture2D> decrement_icon; + Ref<Texture2D> decrement_hl_icon; + Ref<Texture2D> decrement_pressed_icon; + } theme_cache; + void _drag_node_exit(); void _drag_node_input(const Ref<InputEvent> &p_input); virtual void gui_input(const Ref<InputEvent> &p_event) override; protected: - void _notification(int p_what); + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); public: diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index cd595446bb..f68cebd657 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -35,7 +35,6 @@ #include "scene/main/window.h" Size2 ScrollContainer::get_minimum_size() const { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); Size2 min_size; // Calculated in this function, as it needs to traverse all child controls once to calculate; @@ -77,10 +76,16 @@ Size2 ScrollContainer::get_minimum_size() const { min_size.x += v_scroll->get_minimum_size().x; } - min_size += sb->get_minimum_size(); + min_size += theme_cache.bg_style->get_minimum_size(); return min_size; } +void ScrollContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.bg_style = get_theme_stylebox(SNAME("bg")); +} + void ScrollContainer::_cancel_drag() { set_physics_process_internal(false); drag_touching_deaccel = false; @@ -271,9 +276,8 @@ void ScrollContainer::_reposition_children() { Size2 size = get_size(); Point2 ofs; - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); - size -= sb->get_minimum_size(); - ofs += sb->get_offset(); + size -= theme_cache.bg_style->get_minimum_size(); + ofs += theme_cache.bg_style->get_offset(); bool rtl = is_layout_rtl(); if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) { //scrolls may have been moved out for reasons @@ -337,8 +341,7 @@ void ScrollContainer::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); - draw_style_box(sb, Rect2(Vector2(), get_size())); + draw_style_box(theme_cache.bg_style, Rect2(Vector2(), get_size())); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -413,8 +416,7 @@ void ScrollContainer::_notification(int p_what) { void ScrollContainer::update_scrollbars() { Size2 size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); - size -= sb->get_minimum_size(); + size -= theme_cache.bg_style->get_minimum_size(); Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index bfa74cfd0f..fa1f09ab3f 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -69,9 +69,14 @@ private: int deadzone = 0; bool follow_focus = false; + struct ThemeCache { + Ref<StyleBox> bg_style; + } theme_cache; + void _cancel_drag(); protected: + virtual void _update_theme_item_cache() override; Size2 get_minimum_size() const override; void _gui_focus_changed(Control *p_control); diff --git a/scene/gui/separator.cpp b/scene/gui/separator.cpp index e3400d9c8f..8177c1e469 100644 --- a/scene/gui/separator.cpp +++ b/scene/gui/separator.cpp @@ -33,24 +33,30 @@ Size2 Separator::get_minimum_size() const { Size2 ms(3, 3); if (orientation == VERTICAL) { - ms.x = get_theme_constant(SNAME("separation")); + ms.x = theme_cache.separation; } else { // HORIZONTAL - ms.y = get_theme_constant(SNAME("separation")); + ms.y = theme_cache.separation; } return ms; } +void Separator::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.separation = get_theme_constant(SNAME("separation")); + theme_cache.separator_style = get_theme_stylebox(SNAME("separator")); +} + void Separator::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { Size2i size = get_size(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("separator")); - Size2i ssize = style->get_minimum_size() + style->get_center_size(); + Size2i ssize = theme_cache.separator_style->get_minimum_size() + theme_cache.separator_style->get_center_size(); if (orientation == VERTICAL) { - style->draw(get_canvas_item(), Rect2((size.x - ssize.x) / 2, 0, ssize.x, size.y)); + theme_cache.separator_style->draw(get_canvas_item(), Rect2((size.x - ssize.x) / 2, 0, ssize.x, size.y)); } else { - style->draw(get_canvas_item(), Rect2(0, (size.y - ssize.y) / 2, size.x, ssize.y)); + theme_cache.separator_style->draw(get_canvas_item(), Rect2(0, (size.y - ssize.y) / 2, size.x, ssize.y)); } } break; } diff --git a/scene/gui/separator.h b/scene/gui/separator.h index e6578a4d04..44e18a3f00 100644 --- a/scene/gui/separator.h +++ b/scene/gui/separator.h @@ -35,8 +35,16 @@ class Separator : public Control { GDCLASS(Separator, Control); + struct ThemeCache { + int separation = 0; + Ref<StyleBox> separator_style; + } theme_cache; + protected: Orientation orientation = Orientation::HORIZONTAL; + + virtual void _update_theme_item_cache() override; + void _notification(int p_what); public: diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 7bf61e3541..ff3adfb9ac 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -33,11 +33,8 @@ #include "core/os/keyboard.h" Size2 Slider::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("slider")); - Size2i ss = style->get_minimum_size() + style->get_center_size(); - - Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); - Size2i rs = grabber->get_size(); + Size2i ss = theme_cache.slider_style->get_minimum_size() + theme_cache.slider_style->get_center_size(); + Size2i rs = theme_cache.grabber_icon->get_size(); if (orientation == HORIZONTAL) { return Size2i(ss.width, MAX(ss.height, rs.height)); @@ -58,7 +55,13 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - Ref<Texture2D> grabber = get_theme_icon(mouse_inside || has_focus() ? "grabber_highlight" : "grabber"); + Ref<Texture2D> grabber; + if (mouse_inside || has_focus()) { + grabber = theme_cache.grabber_hl_icon; + } else { + grabber = theme_cache.grabber_icon; + } + grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x; double grab_width = (double)grabber->get_size().width; @@ -95,7 +98,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { if (mm.is_valid()) { if (grab.active) { Size2i size = get_size(); - Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); + Ref<Texture2D> grabber = theme_cache.grabber_icon; double motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos; if (orientation == VERTICAL) { motion = -motion; @@ -145,6 +148,19 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { } } +void Slider::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.slider_style = get_theme_stylebox(SNAME("slider")); + theme_cache.grabber_area_style = get_theme_stylebox(SNAME("grabber_area")); + theme_cache.grabber_area_hl_style = get_theme_stylebox(SNAME("grabber_area_highlight")); + + theme_cache.grabber_icon = get_theme_icon(SNAME("grabber")); + theme_cache.grabber_hl_icon = get_theme_icon(SNAME("grabber_highlight")); + theme_cache.grabber_disabled_icon = get_theme_icon(SNAME("grabber_disabled")); + theme_cache.tick_icon = get_theme_icon(SNAME("tick")); +} + void Slider::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { @@ -171,13 +187,30 @@ void Slider::_notification(int p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2i size = get_size(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("slider")); - bool highlighted = mouse_inside || has_focus(); - Ref<StyleBox> grabber_area = get_theme_stylebox(highlighted ? "grabber_area_highlight" : "grabber_area"); - Ref<Texture2D> grabber = get_theme_icon(editable ? (highlighted ? "grabber_highlight" : "grabber") : "grabber_disabled"); - Ref<Texture2D> tick = get_theme_icon(SNAME("tick")); double ratio = Math::is_nan(get_as_ratio()) ? 0 : get_as_ratio(); + Ref<StyleBox> style = theme_cache.slider_style; + Ref<Texture2D> tick = theme_cache.tick_icon; + + bool highlighted = mouse_inside || has_focus(); + Ref<Texture2D> grabber; + if (editable) { + if (highlighted) { + grabber = theme_cache.grabber_hl_icon; + } else { + grabber = theme_cache.grabber_icon; + } + } else { + grabber = theme_cache.grabber_disabled_icon; + } + + Ref<StyleBox> grabber_area; + if (highlighted) { + grabber_area = theme_cache.grabber_area_hl_style; + } else { + grabber_area = theme_cache.grabber_area_style; + } + if (orientation == VERTICAL) { int widget_width = style->get_minimum_size().width + style->get_center_size().width; double areasize = size.height - grabber->get_size().height; diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 5abaee27aa..51adb354fb 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -49,11 +49,24 @@ class Slider : public Range { bool editable = true; bool scrollable = true; + struct ThemeCache { + Ref<StyleBox> slider_style; + Ref<StyleBox> grabber_area_style; + Ref<StyleBox> grabber_area_hl_style; + + Ref<Texture2D> grabber_icon; + Ref<Texture2D> grabber_hl_icon; + Ref<Texture2D> grabber_disabled_icon; + Ref<Texture2D> tick_icon; + } theme_cache; + protected: + bool ticks_on_borders = false; + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); - bool ticks_on_borders = false; public: virtual Size2 get_minimum_size() const override; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 900249ddd9..fe14049d93 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -210,25 +210,29 @@ inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) { } } +void SpinBox::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.updown_icon = get_theme_icon(SNAME("updown")); +} + void SpinBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - Ref<Texture2D> updown = get_theme_icon(SNAME("updown")); - - _adjust_width_for_icon(updown); + _adjust_width_for_icon(theme_cache.updown_icon); RID ci = get_canvas_item(); Size2i size = get_size(); if (is_layout_rtl()) { - updown->draw(ci, Point2i(0, (size.height - updown->get_height()) / 2)); + theme_cache.updown_icon->draw(ci, Point2i(0, (size.height - theme_cache.updown_icon->get_height()) / 2)); } else { - updown->draw(ci, Point2i(size.width - updown->get_width(), (size.height - updown->get_height()) / 2)); + theme_cache.updown_icon->draw(ci, Point2i(size.width - theme_cache.updown_icon->get_width(), (size.height - theme_cache.updown_icon->get_height()) / 2)); } } break; case NOTIFICATION_ENTER_TREE: { - _adjust_width_for_icon(get_theme_icon(SNAME("updown"))); + _adjust_width_for_icon(theme_cache.updown_icon); _value_changed(0); } break; diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 3fcb85ac99..c2f2ac3f5a 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -69,9 +69,14 @@ class SpinBox : public Range { inline void _adjust_width_for_icon(const Ref<Texture2D> &icon); + struct ThemeCache { + Ref<Texture2D> updown_icon; + } theme_cache; + protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index b7e1f2a914..e7e955a17f 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -76,9 +76,7 @@ void SplitContainer::_resort() { bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; // Determine the separation between items - Ref<Texture2D> g = get_theme_icon(SNAME("grabber")); - int sep = get_theme_constant(SNAME("separation")); - sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0; + int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? theme_cache.grabber_icon->get_height() : theme_cache.grabber_icon->get_width()) : 0; // Compute the minimum size Size2 ms_first = first->get_combined_minimum_size(); @@ -131,9 +129,7 @@ Size2 SplitContainer::get_minimum_size() const { /* Calculate MINIMUM SIZE */ Size2i minimum; - Ref<Texture2D> g = get_theme_icon(SNAME("grabber")); - int sep = get_theme_constant(SNAME("separation")); - sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0; + int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? theme_cache.grabber_icon->get_height() : theme_cache.grabber_icon->get_width()) : 0; for (int i = 0; i < 2; i++) { if (!_getch(i)) { @@ -162,6 +158,14 @@ Size2 SplitContainer::get_minimum_size() const { return minimum; } +void SplitContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.separation = get_theme_constant(SNAME("separation")); + theme_cache.autohide = get_theme_constant(SNAME("autohide")); + theme_cache.grabber_icon = get_theme_icon(SNAME("grabber")); +} + void SplitContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: @@ -175,7 +179,7 @@ void SplitContainer::_notification(int p_what) { case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; - if (get_theme_constant(SNAME("autohide"))) { + if (theme_cache.autohide) { queue_redraw(); } } break; @@ -185,7 +189,7 @@ void SplitContainer::_notification(int p_what) { return; } - if (collapsed || (!dragging && !mouse_inside && get_theme_constant(SNAME("autohide")))) { + if (collapsed || (!dragging && !mouse_inside && theme_cache.autohide)) { return; } @@ -193,8 +197,8 @@ void SplitContainer::_notification(int p_what) { return; } - int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? get_theme_constant(SNAME("separation")) : 0; - Ref<Texture2D> tex = get_theme_icon(SNAME("grabber")); + int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? theme_cache.separation : 0; + Ref<Texture2D> tex = theme_cache.grabber_icon; Size2 size = get_size(); if (vertical) { @@ -222,16 +226,14 @@ void SplitContainer::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - int sep = get_theme_constant(SNAME("separation")); - if (vertical) { - if (mb->get_position().y > middle_sep && mb->get_position().y < middle_sep + sep) { + if (mb->get_position().y > middle_sep && mb->get_position().y < middle_sep + theme_cache.separation) { dragging = true; drag_from = mb->get_position().y; drag_ofs = split_offset; } } else { - if (mb->get_position().x > middle_sep && mb->get_position().x < middle_sep + sep) { + if (mb->get_position().x > middle_sep && mb->get_position().x < middle_sep + theme_cache.separation) { dragging = true; drag_from = mb->get_position().x; drag_ofs = split_offset; @@ -248,14 +250,14 @@ void SplitContainer::gui_input(const Ref<InputEvent> &p_event) { if (mm.is_valid()) { bool mouse_inside_state = false; if (vertical) { - mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + get_theme_constant(SNAME("separation")); + mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + theme_cache.separation; } else { - mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + get_theme_constant(SNAME("separation")); + mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + theme_cache.separation; } if (mouse_inside != mouse_inside_state) { mouse_inside = mouse_inside_state; - if (get_theme_constant(SNAME("autohide"))) { + if (theme_cache.autohide) { queue_redraw(); } } @@ -281,14 +283,12 @@ Control::CursorShape SplitContainer::get_cursor_shape(const Point2 &p_pos) const } if (!collapsed && _getch(0) && _getch(1) && dragger_visibility == DRAGGER_VISIBLE) { - int sep = get_theme_constant(SNAME("separation")); - if (vertical) { - if (p_pos.y > middle_sep && p_pos.y < middle_sep + sep) { + if (p_pos.y > middle_sep && p_pos.y < middle_sep + theme_cache.separation) { return CURSOR_VSPLIT; } } else { - if (p_pos.x > middle_sep && p_pos.x < middle_sep + sep) { + if (p_pos.x > middle_sep && p_pos.x < middle_sep + theme_cache.separation) { return CURSOR_HSPLIT; } } diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index a69ffe4de9..18f9573973 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -55,12 +55,20 @@ private: DraggerVisibility dragger_visibility = DRAGGER_VISIBLE; bool mouse_inside = false; + struct ThemeCache { + int separation = 0; + int autohide = 0; + Ref<Texture2D> grabber_icon; + } theme_cache; + Control *_getch(int p_idx) const; void _resort(); protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 61cf8e8a86..4d18af7743 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -44,14 +44,7 @@ Size2 TabBar::get_minimum_size() const { return ms; } - Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); - Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); - Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); - Ref<StyleBox> button_highlight = get_theme_stylebox(SNAME("button_highlight")); - Ref<Texture2D> close = get_theme_icon(SNAME("close")); - int hseparation = get_theme_constant(SNAME("h_separation")); - - int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); + int y_margin = MAX(MAX(theme_cache.tab_unselected_style->get_minimum_size().height, theme_cache.tab_selected_style->get_minimum_size().height), theme_cache.tab_disabled_style->get_minimum_size().height); for (int i = 0; i < tabs.size(); i++) { if (tabs[i].hidden) { @@ -62,22 +55,22 @@ Size2 TabBar::get_minimum_size() const { Ref<StyleBox> style; if (tabs[i].disabled) { - style = tab_disabled; + style = theme_cache.tab_disabled_style; } else if (current == i) { - style = tab_selected; + style = theme_cache.tab_selected_style; } else { - style = tab_unselected; + style = theme_cache.tab_unselected_style; } ms.width += style->get_minimum_size().width; Ref<Texture2D> tex = tabs[i].icon; if (tex.is_valid()) { ms.height = MAX(ms.height, tex->get_size().height + y_margin); - ms.width += tex->get_size().width + hseparation; + ms.width += tex->get_size().width + theme_cache.h_separation; } if (!tabs[i].text.is_empty()) { - ms.width += tabs[i].size_text + hseparation; + ms.width += tabs[i].size_text + theme_cache.h_separation; } ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); @@ -87,22 +80,22 @@ Size2 TabBar::get_minimum_size() const { Ref<Texture2D> rb = tabs[i].right_button; if (close_visible) { - ms.width += button_highlight->get_minimum_size().width + rb->get_width(); + ms.width += theme_cache.button_hl_style->get_minimum_size().width + rb->get_width(); } else { - ms.width += button_highlight->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + ms.width += theme_cache.button_hl_style->get_margin(SIDE_LEFT) + rb->get_width() + theme_cache.h_separation; } ms.height = MAX(ms.height, rb->get_height() + y_margin); } if (close_visible) { - ms.width += button_highlight->get_margin(SIDE_LEFT) + close->get_width() + hseparation; + ms.width += theme_cache.button_hl_style->get_margin(SIDE_LEFT) + theme_cache.close_icon->get_width() + theme_cache.h_separation; - ms.height = MAX(ms.height, close->get_height() + y_margin); + ms.height = MAX(ms.height, theme_cache.close_icon->get_height() + y_margin); } if (ms.width - ofs > style->get_minimum_size().width) { - ms.width -= hseparation; + ms.width -= theme_cache.h_separation; } } @@ -122,16 +115,13 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { Point2 pos = mm->get_position(); if (buttons_visible) { - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - if (is_layout_rtl()) { - if (pos.x < decr->get_width()) { + if (pos.x < theme_cache.decrement_icon->get_width()) { if (highlight_arrow != 1) { highlight_arrow = 1; queue_redraw(); } - } else if (pos.x < incr->get_width() + decr->get_width()) { + } else if (pos.x < theme_cache.increment_icon->get_width() + theme_cache.decrement_icon->get_width()) { if (highlight_arrow != 0) { highlight_arrow = 0; queue_redraw(); @@ -141,8 +131,8 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { queue_redraw(); } } else { - int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); - if (pos.x > limit_minus_buttons + decr->get_width()) { + int limit_minus_buttons = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); + if (pos.x > limit_minus_buttons + theme_cache.decrement_icon->get_width()) { if (highlight_arrow != 1) { highlight_arrow = 1; queue_redraw(); @@ -214,18 +204,15 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { Point2 pos = mb->get_position(); if (buttons_visible) { - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - if (is_layout_rtl()) { - if (pos.x < decr->get_width()) { + if (pos.x < theme_cache.decrement_icon->get_width()) { if (missing_right) { offset++; _update_cache(); queue_redraw(); } return; - } else if (pos.x < incr->get_width() + decr->get_width()) { + } else if (pos.x < theme_cache.increment_icon->get_width() + theme_cache.decrement_icon->get_width()) { if (offset > 0) { offset--; _update_cache(); @@ -234,8 +221,8 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { return; } } else { - int limit = get_size().width - incr->get_width() - decr->get_width(); - if (pos.x > limit + decr->get_width()) { + int limit = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); + if (pos.x > limit + theme_cache.decrement_icon->get_width()) { if (missing_right) { offset++; _update_cache(); @@ -299,9 +286,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } void TabBar::_shape(int p_tab) { - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - tabs.write[p_tab].xl_text = atr(tabs[p_tab].text); tabs.write[p_tab].text_buf->clear(); tabs.write[p_tab].text_buf->set_width(-1); @@ -311,7 +295,37 @@ void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction); } - tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].language); + tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, theme_cache.font, theme_cache.font_size, tabs[p_tab].language); +} + +void TabBar::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + + theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected")); + theme_cache.tab_selected_style = get_theme_stylebox(SNAME("tab_selected")); + theme_cache.tab_disabled_style = get_theme_stylebox(SNAME("tab_disabled")); + + theme_cache.increment_icon = get_theme_icon(SNAME("increment")); + theme_cache.increment_hl_icon = get_theme_icon(SNAME("increment_highlight")); + theme_cache.decrement_icon = get_theme_icon(SNAME("decrement")); + theme_cache.decrement_hl_icon = get_theme_icon(SNAME("decrement_highlight")); + theme_cache.drop_mark_icon = get_theme_icon(SNAME("drop_mark")); + theme_cache.drop_mark_color = get_theme_color(SNAME("drop_mark_color")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_unselected_color = get_theme_color(SNAME("font_unselected_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.close_icon = get_theme_icon(SNAME("close")); + theme_cache.button_pressed_style = get_theme_stylebox(SNAME("button_pressed")); + theme_cache.button_hl_style = get_theme_stylebox(SNAME("button_highlight")); } void TabBar::_notification(int p_what) { @@ -352,18 +366,9 @@ void TabBar::_notification(int p_what) { return; } - Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); - Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); - Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); - Color font_selected_color = get_theme_color(SNAME("font_selected_color")); - Color font_unselected_color = get_theme_color(SNAME("font_unselected_color")); - Color font_disabled_color = get_theme_color(SNAME("font_disabled_color")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - bool rtl = is_layout_rtl(); Vector2 size = get_size(); - int limit_minus_buttons = size.width - incr->get_width() - decr->get_width(); + int limit_minus_buttons = size.width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int ofs = tabs[offset].ofs_cache; @@ -378,14 +383,14 @@ void TabBar::_notification(int p_what) { Color col; if (tabs[i].disabled) { - sb = tab_disabled; - col = font_disabled_color; + sb = theme_cache.tab_disabled_style; + col = theme_cache.font_disabled_color; } else if (i == current) { - sb = tab_selected; - col = font_selected_color; + sb = theme_cache.tab_selected_style; + col = theme_cache.font_selected_color; } else { - sb = tab_unselected; - col = font_unselected_color; + sb = theme_cache.tab_unselected_style; + col = theme_cache.font_unselected_color; } _draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs); @@ -396,41 +401,38 @@ void TabBar::_notification(int p_what) { // Draw selected tab in the front, but only if it's visible. if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) { - Ref<StyleBox> sb = tabs[current].disabled ? tab_disabled : tab_selected; + Ref<StyleBox> sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style; float x = rtl ? size.width - tabs[current].ofs_cache - tabs[current].size_cache : tabs[current].ofs_cache; - _draw_tab(sb, font_selected_color, current, x); + _draw_tab(sb, theme_cache.font_selected_color, current, x); } if (buttons_visible) { - Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight")); - Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight")); - - int vofs = (size.height - incr->get_size().height) / 2; + int vofs = (size.height - theme_cache.increment_icon->get_size().height) / 2; if (rtl) { if (missing_right) { - draw_texture(highlight_arrow == 1 ? decr_hl : decr, Point2(0, vofs)); + draw_texture(highlight_arrow == 1 ? theme_cache.decrement_hl_icon : theme_cache.decrement_icon, Point2(0, vofs)); } else { - draw_texture(decr, Point2(0, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.decrement_icon, Point2(0, vofs), Color(1, 1, 1, 0.5)); } if (offset > 0) { - draw_texture(highlight_arrow == 0 ? incr_hl : incr, Point2(incr->get_size().width, vofs)); + draw_texture(highlight_arrow == 0 ? theme_cache.increment_hl_icon : theme_cache.increment_icon, Point2(theme_cache.increment_icon->get_size().width, vofs)); } else { - draw_texture(incr, Point2(incr->get_size().width, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.increment_icon, Point2(theme_cache.increment_icon->get_size().width, vofs), Color(1, 1, 1, 0.5)); } } else { if (offset > 0) { - draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit_minus_buttons, vofs)); + draw_texture(highlight_arrow == 0 ? theme_cache.decrement_hl_icon : theme_cache.decrement_icon, Point2(limit_minus_buttons, vofs)); } else { - draw_texture(decr, Point2(limit_minus_buttons, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.decrement_icon, Point2(limit_minus_buttons, vofs), Color(1, 1, 1, 0.5)); } if (missing_right) { - draw_texture(highlight_arrow == 1 ? incr_hl : incr, Point2(limit_minus_buttons + decr->get_size().width, vofs)); + draw_texture(highlight_arrow == 1 ? theme_cache.increment_hl_icon : theme_cache.increment_icon, Point2(limit_minus_buttons + theme_cache.decrement_icon->get_size().width, vofs)); } else { - draw_texture(incr, Point2(limit_minus_buttons + decr->get_size().width, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.increment_icon, Point2(limit_minus_buttons + theme_cache.decrement_icon->get_size().width, vofs), Color(1, 1, 1, 0.5)); } } } @@ -462,10 +464,7 @@ void TabBar::_notification(int p_what) { } } - Ref<Texture2D> drop_mark = get_theme_icon(SNAME("drop_mark")); - Color drop_mark_color = get_theme_color(SNAME("drop_mark_color")); - - drop_mark->draw(get_canvas_item(), Point2(x - drop_mark->get_width() / 2, (size.height - drop_mark->get_height()) / 2), drop_mark_color); + theme_cache.drop_mark_icon->draw(get_canvas_item(), Point2(x - theme_cache.drop_mark_icon->get_width() / 2, (size.height - theme_cache.drop_mark_icon->get_height()) / 2), theme_cache.drop_mark_color); } } break; } @@ -475,10 +474,6 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in RID ci = get_canvas_item(); bool rtl = is_layout_rtl(); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - int hseparation = get_theme_constant(SNAME("h_separation")); - Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height); p_tab_style->draw(ci, sb_rect); @@ -491,7 +486,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in if (icon.is_valid()) { icon->draw(ci, Point2i(rtl ? p_x - icon->get_width() : p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); - p_x = rtl ? p_x - icon->get_width() - hseparation : p_x + icon->get_width() + hseparation; + p_x = rtl ? p_x - icon->get_width() - theme_cache.h_separation : p_x + icon->get_width() + theme_cache.h_separation; } // Draw the text. @@ -499,17 +494,17 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in Point2i text_pos = Point2i(rtl ? p_x - tabs[p_index].size_text : p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[p_index].text_buf->get_size().y) / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - tabs[p_index].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.outline_size > 0 && theme_cache.font_outline_color.a > 0) { + tabs[p_index].text_buf->draw_outline(ci, text_pos, theme_cache.outline_size, theme_cache.font_outline_color); } tabs[p_index].text_buf->draw(ci, text_pos, p_font_color); - p_x = rtl ? p_x - tabs[p_index].size_text - hseparation : p_x + tabs[p_index].size_text + hseparation; + p_x = rtl ? p_x - tabs[p_index].size_text - theme_cache.h_separation : p_x + tabs[p_index].size_text + theme_cache.h_separation; } // Draw and calculate rect of the right button. if (tabs[p_index].right_button.is_valid()) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); + Ref<StyleBox> style = theme_cache.button_hl_style; Ref<Texture2D> rb = tabs[p_index].right_button; Rect2 rb_rect; @@ -521,7 +516,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in if (rb_hover == p_index) { if (rb_pressing) { - get_theme_stylebox(SNAME("button_pressed"))->draw(ci, rb_rect); + theme_cache.button_pressed_style->draw(ci, rb_rect); } else { style->draw(ci, rb_rect); } @@ -534,8 +529,8 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in // Draw and calculate rect of the close button. if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_index == current)) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); + Ref<StyleBox> style = theme_cache.button_hl_style; + Ref<Texture2D> cb = theme_cache.close_icon; Rect2 cb_rect; cb_rect.size = style->get_minimum_size() + cb->get_size(); @@ -546,7 +541,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in if (!tabs[p_index].disabled && cb_hover == p_index) { if (cb_pressing) { - get_theme_stylebox(SNAME("button_pressed"))->draw(ci, cb_rect); + theme_cache.button_pressed_style->draw(ci, cb_rect); } else { style->draw(ci, cb_rect); } @@ -849,14 +844,8 @@ void TabBar::_update_cache() { return; } - Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); - Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); - Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - int limit = get_size().width; - int limit_minus_buttons = limit - incr->get_width() - decr->get_width(); + int limit_minus_buttons = limit - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int w = 0; @@ -1258,52 +1247,47 @@ void TabBar::move_tab(int p_from, int p_to) { int TabBar::get_tab_width(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, tabs.size(), 0); - Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); - Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); - Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); - int hseparation = get_theme_constant(SNAME("h_separation")); - Ref<StyleBox> style; if (tabs[p_idx].disabled) { - style = tab_disabled; + style = theme_cache.tab_disabled_style; } else if (current == p_idx) { - style = tab_selected; + style = theme_cache.tab_selected_style; } else { - style = tab_unselected; + style = theme_cache.tab_unselected_style; } int x = style->get_minimum_size().width; Ref<Texture2D> tex = tabs[p_idx].icon; if (tex.is_valid()) { - x += tex->get_width() + hseparation; + x += tex->get_width() + theme_cache.h_separation; } if (!tabs[p_idx].text.is_empty()) { - x += tabs[p_idx].size_text + hseparation; + x += tabs[p_idx].size_text + theme_cache.h_separation; } bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current); if (tabs[p_idx].right_button.is_valid()) { - Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); + Ref<StyleBox> btn_style = theme_cache.button_hl_style; Ref<Texture2D> rb = tabs[p_idx].right_button; if (close_visible) { x += btn_style->get_minimum_size().width + rb->get_width(); } else { - x += btn_style->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + x += btn_style->get_margin(SIDE_LEFT) + rb->get_width() + theme_cache.h_separation; } } if (close_visible) { - Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + hseparation; + Ref<StyleBox> btn_style = theme_cache.button_hl_style; + Ref<Texture2D> cb = theme_cache.close_icon; + x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + theme_cache.h_separation; } if (x > style->get_minimum_size().width) { - x -= hseparation; + x -= theme_cache.h_separation; } return x; @@ -1314,9 +1298,7 @@ void TabBar::_ensure_no_over_offset() { return; } - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); + int limit_minus_buttons = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int prev_offset = offset; @@ -1359,9 +1341,7 @@ void TabBar::ensure_tab_visible(int p_idx) { return; } - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); + int limit_minus_buttons = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int total_w = tabs[max_drawn_tab].ofs_cache - tabs[offset].ofs_cache; for (int i = max_drawn_tab; i <= p_idx; i++) { diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index d123385e47..ac4a6a195e 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -104,6 +104,34 @@ private: bool scroll_to_selected = true; int tabs_rearrange_group = -1; + struct ThemeCache { + int h_separation = 0; + + Ref<StyleBox> tab_unselected_style; + Ref<StyleBox> tab_selected_style; + Ref<StyleBox> tab_disabled_style; + + Ref<Texture2D> increment_icon; + Ref<Texture2D> increment_hl_icon; + Ref<Texture2D> decrement_icon; + Ref<Texture2D> decrement_hl_icon; + Ref<Texture2D> drop_mark_icon; + Color drop_mark_color; + + Ref<Font> font; + int font_size; + int outline_size = 0; + + Color font_selected_color; + Color font_unselected_color; + Color font_disabled_color; + Color font_outline_color; + + Ref<Texture2D> close_icon; + Ref<StyleBox> button_pressed_style; + Ref<StyleBox> button_hl_style; + } theme_cache; + int get_tab_width(int p_idx) const; void _ensure_no_over_offset(); @@ -117,6 +145,7 @@ private: protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 3e04ebee6a..f45d132a66 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -60,26 +60,24 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { } // Handle menu button. - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); - if (is_layout_rtl()) { - if (popup && pos.x < menu->get_width()) { + if (popup && pos.x < theme_cache.menu_icon->get_width()) { emit_signal(SNAME("pre_popup_pressed")); Vector2 popup_pos = get_screen_position(); - popup_pos.y += menu->get_height(); + popup_pos.y += theme_cache.menu_icon->get_height(); popup->set_position(popup_pos); popup->popup(); return; } } else { - if (popup && pos.x > size.width - menu->get_width()) { + if (popup && pos.x > size.width - theme_cache.menu_icon->get_width()) { emit_signal(SNAME("pre_popup_pressed")); Vector2 popup_pos = get_screen_position(); popup_pos.x += size.width - popup->get_size().width; - popup_pos.y += menu->get_height(); + popup_pos.y += theme_cache.menu_icon->get_height(); popup->set_position(popup_pos); popup->popup(); @@ -103,10 +101,9 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { return; } - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); if (popup) { if (is_layout_rtl()) { - if (pos.x <= menu->get_width()) { + if (pos.x <= theme_cache.menu_icon->get_width()) { if (!menu_hovered) { menu_hovered = true; queue_redraw(); @@ -117,7 +114,7 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { queue_redraw(); } } else { - if (pos.x >= size.width - menu->get_width()) { + if (pos.x >= size.width - theme_cache.menu_icon->get_width()) { if (!menu_hovered) { menu_hovered = true; queue_redraw(); @@ -136,6 +133,41 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { } } +void TabContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.side_margin = get_theme_constant(SNAME("side_margin")); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.tabbar_style = get_theme_stylebox(SNAME("tabbar_background")); + + theme_cache.menu_icon = get_theme_icon(SNAME("menu")); + theme_cache.menu_hl_icon = get_theme_icon(SNAME("menu_highlight")); + + // TabBar overrides. + theme_cache.icon_separation = get_theme_constant(SNAME("icon_separation")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected")); + theme_cache.tab_selected_style = get_theme_stylebox(SNAME("tab_selected")); + theme_cache.tab_disabled_style = get_theme_stylebox(SNAME("tab_disabled")); + + theme_cache.increment_icon = get_theme_icon(SNAME("increment")); + theme_cache.increment_hl_icon = get_theme_icon(SNAME("increment_highlight")); + theme_cache.decrement_icon = get_theme_icon(SNAME("decrement")); + theme_cache.decrement_hl_icon = get_theme_icon(SNAME("decrement_highlight")); + theme_cache.drop_mark_icon = get_theme_icon(SNAME("drop_mark")); + theme_cache.drop_mark_color = get_theme_color(SNAME("drop_mark_color")); + + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_unselected_color = get_theme_color(SNAME("font_unselected_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.tab_font = get_theme_font(SNAME("font")); + theme_cache.tab_font_size = get_theme_font_size(SNAME("font_size")); +} + void TabContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -155,31 +187,26 @@ void TabContainer::_notification(int p_what) { Size2 size = get_size(); // Draw only the tab area if the header is hidden. - Ref<StyleBox> panel = get_theme_stylebox(SNAME("panel")); if (!tabs_visible) { - panel->draw(canvas, Rect2(0, 0, size.width, size.height)); + theme_cache.panel_style->draw(canvas, Rect2(0, 0, size.width, size.height)); return; } int header_height = _get_top_margin(); // Draw background for the tabbar. - Ref<StyleBox> tabbar_background = get_theme_stylebox(SNAME("tabbar_background")); - tabbar_background->draw(canvas, Rect2(0, 0, size.width, header_height)); + theme_cache.tabbar_style->draw(canvas, Rect2(0, 0, size.width, header_height)); // Draw the background for the tab's content. - panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); + theme_cache.panel_style->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); // Draw the popup menu. if (get_popup()) { - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); - Ref<Texture2D> menu_hl = get_theme_icon(SNAME("menu_highlight")); - - int x = is_layout_rtl() ? 0 : get_size().width - menu->get_width(); + int x = is_layout_rtl() ? 0 : get_size().width - theme_cache.menu_icon->get_width(); if (menu_hovered) { - menu_hl->draw(get_canvas_item(), Point2(x, (header_height - menu_hl->get_height()) / 2)); + theme_cache.menu_hl_icon->draw(get_canvas_item(), Point2(x, (header_height - theme_cache.menu_hl_icon->get_height()) / 2)); } else { - menu->draw(get_canvas_item(), Point2(x, (header_height - menu->get_height()) / 2)); + theme_cache.menu_icon->draw(get_canvas_item(), Point2(x, (header_height - theme_cache.menu_icon->get_height()) / 2)); } } } break; @@ -198,23 +225,27 @@ void TabContainer::_on_theme_changed() { return; } - tab_bar->add_theme_style_override(SNAME("tab_unselected"), get_theme_stylebox(SNAME("tab_unselected"))); - tab_bar->add_theme_style_override(SNAME("tab_selected"), get_theme_stylebox(SNAME("tab_selected"))); - tab_bar->add_theme_style_override(SNAME("tab_disabled"), get_theme_stylebox(SNAME("tab_disabled"))); - tab_bar->add_theme_icon_override(SNAME("increment"), get_theme_icon(SNAME("increment"))); - tab_bar->add_theme_icon_override(SNAME("increment_highlight"), get_theme_icon(SNAME("increment_highlight"))); - tab_bar->add_theme_icon_override(SNAME("decrement"), get_theme_icon(SNAME("decrement"))); - tab_bar->add_theme_icon_override(SNAME("decrement_highlight"), get_theme_icon(SNAME("decrement_highlight"))); - tab_bar->add_theme_icon_override(SNAME("drop_mark"), get_theme_icon(SNAME("drop_mark"))); - tab_bar->add_theme_color_override(SNAME("drop_mark_color"), get_theme_color(SNAME("drop_mark_color"))); - tab_bar->add_theme_color_override(SNAME("font_selected_color"), get_theme_color(SNAME("font_selected_color"))); - tab_bar->add_theme_color_override(SNAME("font_unselected_color"), get_theme_color(SNAME("font_unselected_color"))); - tab_bar->add_theme_color_override(SNAME("font_disabled_color"), get_theme_color(SNAME("font_disabled_color"))); - tab_bar->add_theme_color_override(SNAME("font_outline_color"), get_theme_color(SNAME("font_outline_color"))); - tab_bar->add_theme_font_override(SNAME("font"), get_theme_font(SNAME("font"))); - tab_bar->add_theme_font_size_override(SNAME("font_size"), get_theme_font_size(SNAME("font_size"))); - tab_bar->add_theme_constant_override(SNAME("h_separation"), get_theme_constant(SNAME("icon_separation"))); - tab_bar->add_theme_constant_override(SNAME("outline_size"), get_theme_constant(SNAME("outline_size"))); + tab_bar->add_theme_style_override(SNAME("tab_unselected"), theme_cache.tab_unselected_style); + tab_bar->add_theme_style_override(SNAME("tab_selected"), theme_cache.tab_selected_style); + tab_bar->add_theme_style_override(SNAME("tab_disabled"), theme_cache.tab_disabled_style); + + tab_bar->add_theme_icon_override(SNAME("increment"), theme_cache.increment_icon); + tab_bar->add_theme_icon_override(SNAME("increment_highlight"), theme_cache.increment_hl_icon); + tab_bar->add_theme_icon_override(SNAME("decrement"), theme_cache.decrement_icon); + tab_bar->add_theme_icon_override(SNAME("decrement_highlight"), theme_cache.decrement_hl_icon); + tab_bar->add_theme_icon_override(SNAME("drop_mark"), theme_cache.drop_mark_icon); + tab_bar->add_theme_color_override(SNAME("drop_mark_color"), theme_cache.drop_mark_color); + + tab_bar->add_theme_color_override(SNAME("font_selected_color"), theme_cache.font_selected_color); + tab_bar->add_theme_color_override(SNAME("font_unselected_color"), theme_cache.font_unselected_color); + tab_bar->add_theme_color_override(SNAME("font_disabled_color"), theme_cache.font_disabled_color); + tab_bar->add_theme_color_override(SNAME("font_outline_color"), theme_cache.font_outline_color); + + tab_bar->add_theme_font_override(SNAME("font"), theme_cache.tab_font); + tab_bar->add_theme_font_size_override(SNAME("font_size"), theme_cache.tab_font_size); + + tab_bar->add_theme_constant_override(SNAME("h_separation"), theme_cache.icon_separation); + tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size); _update_margins(); if (get_tab_count() > 0) { @@ -228,7 +259,6 @@ void TabContainer::_on_theme_changed() { } void TabContainer::_repaint() { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); Vector<Control *> controls = _get_tab_controls(); int current = get_current_tab(); @@ -243,10 +273,10 @@ void TabContainer::_repaint() { c->set_offset(SIDE_TOP, _get_top_margin()); } - c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + sb->get_margin(SIDE_TOP)); - c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + sb->get_margin(SIDE_LEFT)); - c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - sb->get_margin(SIDE_RIGHT)); - c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - sb->get_margin(SIDE_BOTTOM)); + c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_TOP)); + c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_LEFT)); + c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - theme_cache.panel_style->get_margin(SIDE_RIGHT)); + c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - theme_cache.panel_style->get_margin(SIDE_BOTTOM)); } else { c->hide(); } @@ -256,8 +286,7 @@ void TabContainer::_repaint() { } void TabContainer::_update_margins() { - int menu_width = get_theme_icon(SNAME("menu"))->get_width(); - int side_margin = get_theme_constant(SNAME("side_margin")); + int menu_width = theme_cache.menu_icon->get_width(); // Directly check for validity, to avoid errors when quitting. bool has_popup = popup_obj_id.is_valid(); @@ -271,7 +300,7 @@ void TabContainer::_update_margins() { switch (get_tab_alignment()) { case TabBar::ALIGNMENT_LEFT: { - tab_bar->set_offset(SIDE_LEFT, side_margin); + tab_bar->set_offset(SIDE_LEFT, theme_cache.side_margin); tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); } break; @@ -293,10 +322,10 @@ void TabContainer::_update_margins() { int total_tabs_width = last_tab_rect.position.x - first_tab_pos + last_tab_rect.size.width; // Calculate if all the tabs would still fit if the margin was present. - if (get_clip_tabs() && (tab_bar->get_offset_buttons_visible() || (get_tab_count() > 1 && (total_tabs_width + side_margin) > get_size().width))) { + if (get_clip_tabs() && (tab_bar->get_offset_buttons_visible() || (get_tab_count() > 1 && (total_tabs_width + theme_cache.side_margin) > get_size().width))) { tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); } else { - tab_bar->set_offset(SIDE_RIGHT, -side_margin); + tab_bar->set_offset(SIDE_RIGHT, -theme_cache.side_margin); } } break; @@ -798,13 +827,12 @@ Size2 TabContainer::get_minimum_size() const { if (!get_clip_tabs()) { if (get_popup()) { - ms.x += get_theme_icon(SNAME("menu"))->get_width(); + ms.x += theme_cache.menu_icon->get_width(); } - int side_margin = get_theme_constant(SNAME("side_margin")); - if (side_margin > 0 && get_tab_alignment() != TabBar::ALIGNMENT_CENTER && + if (theme_cache.side_margin > 0 && get_tab_alignment() != TabBar::ALIGNMENT_CENTER && (get_tab_alignment() != TabBar::ALIGNMENT_RIGHT || !get_popup())) { - ms.x += side_margin; + ms.x += theme_cache.side_margin; } } } @@ -824,7 +852,7 @@ Size2 TabContainer::get_minimum_size() const { } ms.y += max_control_height; - Size2 panel_ms = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); + Size2 panel_ms = theme_cache.panel_style->get_minimum_size(); ms.x = MAX(ms.x, panel_ms.x); ms.y += panel_ms.y; diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 60c8130939..b552aa459b 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -48,6 +48,39 @@ class TabContainer : public Container { bool theme_changing = false; Node *child_removing = nullptr; + struct ThemeCache { + int side_margin = 0; + + Ref<StyleBox> panel_style; + Ref<StyleBox> tabbar_style; + + Ref<Texture2D> menu_icon; + Ref<Texture2D> menu_hl_icon; + + // TabBar overrides. + int icon_separation = 0; + int outline_size = 0; + + Ref<StyleBox> tab_unselected_style; + Ref<StyleBox> tab_selected_style; + Ref<StyleBox> tab_disabled_style; + + Ref<Texture2D> increment_icon; + Ref<Texture2D> increment_hl_icon; + Ref<Texture2D> decrement_icon; + Ref<Texture2D> decrement_hl_icon; + Ref<Texture2D> drop_mark_icon; + Color drop_mark_color; + + Color font_selected_color; + Color font_unselected_color; + Color font_disabled_color; + Color font_outline_color; + + Ref<Font> tab_font; + int tab_font_size; + } theme_cache; + int _get_top_margin() const; Vector<Control *> _get_tab_controls() const; void _on_theme_changed(); @@ -65,6 +98,8 @@ class TabContainer : public Container { protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; + void _notification(int p_what); virtual void add_child_notify(Node *p_child) override; virtual void move_child_notify(Node *p_child) override; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index e2fd903e0e..2efb6593d3 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -112,7 +112,7 @@ bool TextureButton::has_point(const Point2 &p_point) const { } Point2i p = point; - return click_mask->get_bit(p); + return click_mask->get_bitv(p); } return Control::has_point(p_point); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 3c6be008f2..e8164b5728 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1286,14 +1286,14 @@ Size2 TreeItem::get_minimum_size(int p_column) { // Icon. if (cell.mode == CELL_MODE_CHECK) { - size.width += tree->cache.checked->get_width() + tree->cache.hseparation; + size.width += tree->theme_cache.checked->get_width() + tree->theme_cache.hseparation; } if (cell.icon.is_valid()) { Size2i icon_size = cell.get_icon_size(); if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { icon_size.width = cell.icon_max_w; } - size.width += icon_size.width + tree->cache.hseparation; + size.width += icon_size.width + tree->theme_cache.hseparation; size.height = MAX(size.height, icon_size.height); } @@ -1301,13 +1301,13 @@ Size2 TreeItem::get_minimum_size(int p_column) { for (int i = 0; i < cell.buttons.size(); i++) { Ref<Texture2D> texture = cell.buttons[i].texture; if (texture.is_valid()) { - Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size(); + Size2 button_size = texture->get_size() + tree->theme_cache.button_pressed->get_minimum_size(); size.width += button_size.width; size.height = MAX(size.height, button_size.height); } } if (cell.buttons.size() >= 2) { - size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; + size.width += (cell.buttons.size() - 1) * tree->theme_cache.button_margin; } cells.write[p_column].cached_minimum_size = size; @@ -1535,68 +1535,66 @@ TreeItem::~TreeItem() { /**********************************************/ /**********************************************/ -void Tree::update_cache() { - cache.font = get_theme_font(SNAME("font")); - cache.font_size = get_theme_font_size(SNAME("font_size")); - cache.tb_font = get_theme_font(SNAME("title_button_font")); - cache.tb_font_size = get_theme_font_size(SNAME("title_button_font_size")); - cache.bg = get_theme_stylebox(SNAME("bg")); - cache.selected = get_theme_stylebox(SNAME("selected")); - cache.selected_focus = get_theme_stylebox(SNAME("selected_focus")); - cache.cursor = get_theme_stylebox(SNAME("cursor")); - cache.cursor_unfocus = get_theme_stylebox(SNAME("cursor_unfocused")); - cache.button_pressed = get_theme_stylebox(SNAME("button_pressed")); - - cache.checked = get_theme_icon(SNAME("checked")); - cache.unchecked = get_theme_icon(SNAME("unchecked")); - cache.indeterminate = get_theme_icon(SNAME("indeterminate")); - if (is_layout_rtl()) { - cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed_mirrored")); - } else { - cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed")); - } - cache.arrow = get_theme_icon(SNAME("arrow")); - cache.select_arrow = get_theme_icon(SNAME("select_arrow")); - cache.updown = get_theme_icon(SNAME("updown")); - - cache.custom_button = get_theme_stylebox(SNAME("custom_button")); - cache.custom_button_hover = get_theme_stylebox(SNAME("custom_button_hover")); - cache.custom_button_pressed = get_theme_stylebox(SNAME("custom_button_pressed")); - cache.custom_button_font_highlight = get_theme_color(SNAME("custom_button_font_highlight")); - - cache.font_color = get_theme_color(SNAME("font_color")); - cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); - cache.drop_position_color = get_theme_color(SNAME("drop_position_color")); - cache.hseparation = get_theme_constant(SNAME("h_separation")); - cache.vseparation = get_theme_constant(SNAME("v_separation")); - cache.item_margin = get_theme_constant(SNAME("item_margin")); - cache.button_margin = get_theme_constant(SNAME("button_margin")); - - cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); - cache.font_outline_size = get_theme_constant(SNAME("outline_size")); - - cache.draw_guides = get_theme_constant(SNAME("draw_guides")); - cache.guide_color = get_theme_color(SNAME("guide_color")); - cache.draw_relationship_lines = get_theme_constant(SNAME("draw_relationship_lines")); - cache.relationship_line_width = get_theme_constant(SNAME("relationship_line_width")); - cache.parent_hl_line_width = get_theme_constant(SNAME("parent_hl_line_width")); - cache.children_hl_line_width = get_theme_constant(SNAME("children_hl_line_width")); - cache.parent_hl_line_margin = get_theme_constant(SNAME("parent_hl_line_margin")); - cache.relationship_line_color = get_theme_color(SNAME("relationship_line_color")); - cache.parent_hl_line_color = get_theme_color(SNAME("parent_hl_line_color")); - cache.children_hl_line_color = get_theme_color(SNAME("children_hl_line_color")); - - cache.scroll_border = get_theme_constant(SNAME("scroll_border")); - cache.scroll_speed = get_theme_constant(SNAME("scroll_speed")); - - cache.title_button = get_theme_stylebox(SNAME("title_button_normal")); - cache.title_button_pressed = get_theme_stylebox(SNAME("title_button_pressed")); - cache.title_button_hover = get_theme_stylebox(SNAME("title_button_hover")); - cache.title_button_color = get_theme_color(SNAME("title_button_color")); - - cache.base_scale = get_theme_default_base_scale(); - - v_scroll->set_custom_step(cache.font->get_height(cache.font_size)); +void Tree::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.tb_font = get_theme_font(SNAME("title_button_font")); + theme_cache.tb_font_size = get_theme_font_size(SNAME("title_button_font_size")); + theme_cache.bg = get_theme_stylebox(SNAME("bg")); + theme_cache.bg_focus = get_theme_stylebox(SNAME("bg_focus")); + theme_cache.selected = get_theme_stylebox(SNAME("selected")); + theme_cache.selected_focus = get_theme_stylebox(SNAME("selected_focus")); + theme_cache.cursor = get_theme_stylebox(SNAME("cursor")); + theme_cache.cursor_unfocus = get_theme_stylebox(SNAME("cursor_unfocused")); + theme_cache.button_pressed = get_theme_stylebox(SNAME("button_pressed")); + + theme_cache.checked = get_theme_icon(SNAME("checked")); + theme_cache.unchecked = get_theme_icon(SNAME("unchecked")); + theme_cache.indeterminate = get_theme_icon(SNAME("indeterminate")); + theme_cache.arrow = get_theme_icon(SNAME("arrow")); + theme_cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed")); + theme_cache.arrow_collapsed_mirrored = get_theme_icon(SNAME("arrow_collapsed_mirrored")); + theme_cache.select_arrow = get_theme_icon(SNAME("select_arrow")); + theme_cache.updown = get_theme_icon(SNAME("updown")); + + theme_cache.custom_button = get_theme_stylebox(SNAME("custom_button")); + theme_cache.custom_button_hover = get_theme_stylebox(SNAME("custom_button_hover")); + theme_cache.custom_button_pressed = get_theme_stylebox(SNAME("custom_button_pressed")); + theme_cache.custom_button_font_highlight = get_theme_color(SNAME("custom_button_font_highlight")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.drop_position_color = get_theme_color(SNAME("drop_position_color")); + theme_cache.hseparation = get_theme_constant(SNAME("h_separation")); + theme_cache.vseparation = get_theme_constant(SNAME("v_separation")); + theme_cache.item_margin = get_theme_constant(SNAME("item_margin")); + theme_cache.button_margin = get_theme_constant(SNAME("button_margin")); + + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.draw_guides = get_theme_constant(SNAME("draw_guides")); + theme_cache.guide_color = get_theme_color(SNAME("guide_color")); + theme_cache.draw_relationship_lines = get_theme_constant(SNAME("draw_relationship_lines")); + theme_cache.relationship_line_width = get_theme_constant(SNAME("relationship_line_width")); + theme_cache.parent_hl_line_width = get_theme_constant(SNAME("parent_hl_line_width")); + theme_cache.children_hl_line_width = get_theme_constant(SNAME("children_hl_line_width")); + theme_cache.parent_hl_line_margin = get_theme_constant(SNAME("parent_hl_line_margin")); + theme_cache.relationship_line_color = get_theme_color(SNAME("relationship_line_color")); + theme_cache.parent_hl_line_color = get_theme_color(SNAME("parent_hl_line_color")); + theme_cache.children_hl_line_color = get_theme_color(SNAME("children_hl_line_color")); + + theme_cache.scroll_border = get_theme_constant(SNAME("scroll_border")); + theme_cache.scroll_speed = get_theme_constant(SNAME("scroll_speed")); + + theme_cache.title_button = get_theme_stylebox(SNAME("title_button_normal")); + theme_cache.title_button_pressed = get_theme_stylebox(SNAME("title_button_pressed")); + theme_cache.title_button_hover = get_theme_stylebox(SNAME("title_button_hover")); + theme_cache.title_button_color = get_theme_color(SNAME("title_button_color")); + + theme_cache.base_scale = get_theme_default_base_scale(); } int Tree::compute_item_height(TreeItem *p_item) const { @@ -1604,7 +1602,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { return 0; } - ERR_FAIL_COND_V(cache.font.is_null(), 0); + ERR_FAIL_COND_V(theme_cache.font.is_null(), 0); int height = 0; for (int i = 0; i < columns.size(); i++) { @@ -1622,7 +1620,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { switch (p_item->cells[i].mode) { case TreeItem::CELL_MODE_CHECK: { - int check_icon_h = cache.checked->get_height(); + int check_icon_h = theme_cache.checked->get_height(); if (height < check_icon_h) { height = check_icon_h; } @@ -1642,7 +1640,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { } } if (p_item->cells[i].mode == TreeItem::CELL_MODE_CUSTOM && p_item->cells[i].custom_button) { - height += cache.custom_button->get_minimum_size().height; + height += theme_cache.custom_button->get_minimum_size().height; } } break; @@ -1655,7 +1653,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { height = item_min_height; } - height += cache.vseparation; + height += theme_cache.vseparation; return height; } @@ -1665,7 +1663,7 @@ int Tree::get_item_height(TreeItem *p_item) const { return 0; } int height = compute_item_height(p_item); - height += cache.vseparation; + height += theme_cache.vseparation; if (!p_item->collapsed) { /* if not collapsed, check the children */ @@ -1682,7 +1680,7 @@ int Tree::get_item_height(TreeItem *p_item) const { } void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) { - ERR_FAIL_COND(cache.font.is_null()); + ERR_FAIL_COND(theme_cache.font.is_null()); Rect2i rect = p_rect; Size2 ts = p_cell.text_buf->get_size(); @@ -1694,7 +1692,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) { bmsize.width = p_cell.icon_max_w; } - w += bmsize.width + cache.hseparation; + w += bmsize.width + theme_cache.hseparation; if (rect.size.width > 0 && (w + ts.width) > rect.size.width) { ts.width = rect.size.width - w; } @@ -1728,8 +1726,8 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color); } p_cell.text_buf->draw(ci, draw_pos, p_color); - rect.position.x += ts.width + cache.hseparation; - rect.size.x -= ts.width + cache.hseparation; + rect.position.x += ts.width + theme_cache.hseparation; + rect.size.x -= ts.width + theme_cache.hseparation; } if (!p_cell.icon.is_null()) { @@ -1741,8 +1739,8 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co } p_cell.draw_icon(ci, rect.position + Size2i(0, Math::floor((real_t)(rect.size.y - bmsize.y) / 2)), bmsize, p_icon_color); - rect.position.x += bmsize.x + cache.hseparation; - rect.size.x -= bmsize.x + cache.hseparation; + rect.position.x += bmsize.x + theme_cache.hseparation; + rect.size.x -= bmsize.x + theme_cache.hseparation; } if (!rtl) { @@ -1764,7 +1762,7 @@ void Tree::update_column(int p_col) { columns.write[p_col].text_buf->set_direction((TextServer::Direction)columns[p_col].text_direction); } - columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].language); + columns.write[p_col].text_buf->add_string(columns[p_col].title, theme_cache.font, theme_cache.font_size, columns[p_col].language); } void Tree::update_item_cell(TreeItem *p_item, int p_col) { @@ -1813,14 +1811,14 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { if (p_item->cells[p_col].custom_font.is_valid()) { font = p_item->cells[p_col].custom_font; } else { - font = cache.font; + font = theme_cache.font; } int font_size; if (p_item->cells[p_col].custom_font_size > 0) { font_size = p_item->cells[p_col].custom_font_size; } else { - font_size = cache.font_size; + font_size = theme_cache.font_size; } p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].language); TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext)); @@ -1840,7 +1838,7 @@ void Tree::update_item_cache(TreeItem *p_item) { } int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item) { - if (p_pos.y - cache.offset.y > (p_draw_size.height)) { + if (p_pos.y - theme_cache.offset.y > (p_draw_size.height)) { return -1; //draw no more! } @@ -1856,18 +1854,18 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 bool rtl = cache.rtl; /* Calculate height of the label part */ - label_h += cache.vseparation; + label_h += theme_cache.vseparation; /* Draw label, if height fits */ bool skip = (p_item == root && hide_root); - if (!skip && (p_pos.y + label_h - cache.offset.y) > 0) { + if (!skip && (p_pos.y + label_h - theme_cache.offset.y) > 0) { // Draw separation. - ERR_FAIL_COND_V(cache.font.is_null(), -1); + ERR_FAIL_COND_V(theme_cache.font.is_null(), -1); - int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); + int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.hseparation : theme_cache.item_margin); int skip2 = 0; for (int i = 0; i < columns.size(); i++) { if (skip2) { @@ -1885,8 +1883,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 continue; } } else { - ofs += cache.hseparation; - w -= cache.hseparation; + ofs += theme_cache.hseparation; + w -= theme_cache.hseparation; } if (p_item->cells[i].expand_right) { @@ -1902,10 +1900,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int button_w = 0; for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[i].buttons[j].texture; - button_w += b->get_size().width + cache.button_pressed->get_minimum_size().width + cache.button_margin; + button_w += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin; } - int total_ofs = ofs - cache.offset.x; + int total_ofs = ofs - theme_cache.offset.x; if (total_ofs + w > p_draw_size.width) { w = MAX(button_w, p_draw_size.width - total_ofs); @@ -1915,9 +1913,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int bw = 0; for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[i].buttons[j].texture; - Size2 s = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 s = b->get_size() + theme_cache.button_pressed->get_minimum_size(); - Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs; + Point2i o = Point2i(ofs + w - s.width, p_pos.y) - theme_cache.offset + p_draw_ofs; if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) { // Being pressed. @@ -1925,48 +1923,48 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (rtl) { od.x = get_size().width - od.x - s.x; } - cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h))); + theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h))); } o.y += (label_h - s.height) / 2; - o += cache.button_pressed->get_offset(); + o += theme_cache.button_pressed->get_offset(); if (rtl) { o.x = get_size().width - o.x - b->get_width(); } b->draw(ci, o, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color); - w -= s.width + cache.button_margin; - bw += s.width + cache.button_margin; + w -= s.width + theme_cache.button_margin; + bw += s.width + theme_cache.button_margin; } - Rect2i item_rect = Rect2i(Point2i(ofs, p_pos.y) - cache.offset + p_draw_ofs, Size2i(w, label_h)); + Rect2i item_rect = Rect2i(Point2i(ofs, p_pos.y) - theme_cache.offset + p_draw_ofs, Size2i(w, label_h)); Rect2i cell_rect = item_rect; if (i != 0) { - cell_rect.position.x -= cache.hseparation; - cell_rect.size.x += cache.hseparation; + cell_rect.position.x -= theme_cache.hseparation; + cell_rect.size.x += theme_cache.hseparation; } - if (cache.draw_guides) { + if (theme_cache.draw_guides) { Rect2 r = cell_rect; if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, cache.guide_color, 1); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, theme_cache.guide_color, 1); } if (i == 0) { if (p_item->cells[0].selected && select_mode == SELECT_ROW) { - Rect2i row_rect = Rect2i(Point2i(cache.bg->get_margin(SIDE_LEFT), item_rect.position.y), Size2i(get_size().width - cache.bg->get_minimum_size().width, item_rect.size.y)); + Rect2i row_rect = Rect2i(Point2i(theme_cache.bg->get_margin(SIDE_LEFT), item_rect.position.y), Size2i(get_size().width - theme_cache.bg->get_minimum_size().width, item_rect.size.y)); //Rect2 r = Rect2i(row_rect.pos,row_rect.size); //r.grow(cache.selected->get_margin(SIDE_LEFT)); if (rtl) { row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x; } if (has_focus()) { - cache.selected_focus->draw(ci, row_rect); + theme_cache.selected_focus->draw(ci, row_rect); } else { - cache.selected->draw(ci, row_rect); + theme_cache.selected->draw(ci, row_rect); } } } @@ -1982,9 +1980,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } if (p_item->cells[i].selected) { if (has_focus()) { - cache.selected_focus->draw(ci, r); + theme_cache.selected_focus->draw(ci, r); } else { - cache.selected->draw(ci, r); + theme_cache.selected->draw(ci, r); } } } @@ -1996,8 +1994,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 r.position.x = p_draw_ofs.x; r.size.x = w + ofs; } else { - r.position.x -= cache.hseparation; - r.size.x += cache.hseparation; + r.position.x -= theme_cache.hseparation; + r.size.x += theme_cache.hseparation; } if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; @@ -2020,28 +2018,34 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (drop_mode_over == p_item) { if (drop_mode_section == 0 || drop_mode_section == -1) { // Line above. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), theme_cache.drop_position_color); } if (drop_mode_section == 0) { // Side lines. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), theme_cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), theme_cache.drop_position_color); } if (drop_mode_section == 0 || (drop_mode_section == 1 && (!p_item->get_first_child() || p_item->is_collapsed()))) { // Line below. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), theme_cache.drop_position_color); } } else if (drop_mode_over == p_item->get_parent()) { if (drop_mode_section == 1 && !p_item->get_prev() /* && !drop_mode_over->is_collapsed() */) { // The drop_mode_over shouldn't ever be collapsed in here, otherwise we would be drawing a child of a collapsed item. // Line above. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), theme_cache.drop_position_color); } } } - Color col = p_item->cells[i].custom_color ? p_item->cells[i].color : get_theme_color(p_item->cells[i].selected ? "font_selected_color" : "font_color"); - Color font_outline_color = cache.font_outline_color; - int outline_size = cache.font_outline_size; + Color col; + if (p_item->cells[i].custom_color) { + col = p_item->cells[i].color; + } else { + col = p_item->cells[i].selected ? theme_cache.font_selected_color : theme_cache.font_color; + } + + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.font_outline_size; Color icon_col = p_item->cells[i].icon_color; if (p_item->cells[i].dirty) { @@ -2061,9 +2065,9 @@ 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, col, icon_col, outline_size, font_outline_color); } break; case TreeItem::CELL_MODE_CHECK: { - Ref<Texture2D> checked = cache.checked; - Ref<Texture2D> unchecked = cache.unchecked; - Ref<Texture2D> indeterminate = cache.indeterminate; + 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); @@ -2075,7 +2079,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 unchecked->draw(ci, check_ofs); } - int check_w = checked->get_width() + cache.hseparation; + int check_w = checked->get_width() + theme_cache.hseparation; text_pos.x += check_w; @@ -2091,7 +2095,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 break; } - Ref<Texture2D> downarrow = cache.select_arrow; + Ref<Texture2D> downarrow = theme_cache.select_arrow; int cell_width = item_rect.size.x - downarrow->get_width(); p_item->cells.write[i].text_buf->set_width(cell_width); @@ -2113,7 +2117,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 downarrow->draw(ci, arrow_pos); } else { - Ref<Texture2D> updown = cache.updown; + Ref<Texture2D> updown = theme_cache.updown; int cell_width = item_rect.size.x - updown->get_width(); @@ -2170,7 +2174,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 break; } - Ref<Texture2D> downarrow = cache.select_arrow; + Ref<Texture2D> downarrow = theme_cache.select_arrow; Rect2i ir = item_rect; @@ -2182,16 +2186,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (p_item->cells[i].custom_button) { if (cache.hover_item == p_item && cache.hover_cell == i) { if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - draw_style_box(cache.custom_button_pressed, ir); + draw_style_box(theme_cache.custom_button_pressed, ir); } else { - draw_style_box(cache.custom_button_hover, ir); - col = cache.custom_button_font_highlight; + draw_style_box(theme_cache.custom_button_hover, ir); + col = theme_cache.custom_button_font_highlight; } } else { - draw_style_box(cache.custom_button, ir); + draw_style_box(theme_cache.custom_button, ir); } - ir.size -= cache.custom_button->get_minimum_size(); - ir.position += cache.custom_button->get_offset(); + ir.size -= theme_cache.custom_button->get_minimum_size(); + ir.position += theme_cache.custom_button->get_offset(); } draw_item_rect(p_item->cells.write[i], ir, col, icon_col, outline_size, font_outline_color); @@ -2212,9 +2216,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 cell_rect.position.x = get_size().width - cell_rect.position.x - cell_rect.size.x; } if (has_focus()) { - cache.cursor->draw(ci, cell_rect); + theme_cache.cursor->draw(ci, cell_rect); } else { - cache.cursor_unfocus->draw(ci, cell_rect); + theme_cache.cursor_unfocus->draw(ci, cell_rect); } } } @@ -2224,13 +2228,17 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 Ref<Texture2D> arrow; if (p_item->collapsed) { - arrow = cache.arrow_collapsed; + if (is_layout_rtl()) { + arrow = theme_cache.arrow_collapsed_mirrored; + } else { + arrow = theme_cache.arrow_collapsed; + } } else { - arrow = cache.arrow; + arrow = theme_cache.arrow; } - Point2 apos = p_pos + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset + p_draw_ofs; - apos.x += cache.item_margin - arrow->get_width(); + Point2 apos = p_pos + Point2i(0, (label_h - arrow->get_height()) / 2) - theme_cache.offset + p_draw_ofs; + apos.x += theme_cache.item_margin - arrow->get_width(); if (rtl) { apos.x = get_size().width - apos.x - arrow->get_width(); @@ -2243,7 +2251,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 Point2 children_pos = p_pos; if (!skip) { - children_pos.x += cache.item_margin; + children_pos.x += theme_cache.item_margin; htotal += label_h; children_pos.y += htotal; } @@ -2251,7 +2259,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (!p_item->collapsed) { /* if not collapsed, check the children */ TreeItem *c = p_item->first_child; - int base_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y; + int base_ofs = children_pos.y - theme_cache.offset.y + p_draw_ofs.y; int prev_ofs = base_ofs; int prev_hl_ofs = base_ofs; @@ -2262,20 +2270,20 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } // Draw relationship lines. - if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) { - int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); - int parent_ofs = p_pos.x + cache.item_margin; - Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; + if (theme_cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) { + int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.hseparation : theme_cache.item_margin); + int parent_ofs = p_pos.x + theme_cache.item_margin; + Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - theme_cache.offset + p_draw_ofs; if (c->get_visible_child_count() > 0) { - root_pos -= Point2i(cache.arrow->get_width(), 0); + root_pos -= Point2i(theme_cache.arrow->get_width(), 0); } - float line_width = cache.relationship_line_width * Math::round(cache.base_scale); - float parent_line_width = cache.parent_hl_line_width * Math::round(cache.base_scale); - float children_line_width = cache.children_hl_line_width * Math::round(cache.base_scale); + float line_width = theme_cache.relationship_line_width * Math::round(theme_cache.base_scale); + float parent_line_width = theme_cache.parent_hl_line_width * Math::round(theme_cache.base_scale); + float children_line_width = theme_cache.children_hl_line_width * Math::round(theme_cache.base_scale); - Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs; + Point2i parent_pos = Point2i(parent_ofs - theme_cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + theme_cache.arrow->get_height() / 2) - theme_cache.offset + p_draw_ofs; int more_prev_ofs = 0; @@ -2289,43 +2297,43 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (_is_branch_selected(c)) { // If this item or one of its children is selected, we draw the line using parent highlight style. if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), theme_cache.parent_hl_line_color, parent_line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), theme_cache.parent_hl_line_color, parent_line_width); - more_prev_ofs = cache.parent_hl_line_margin; + more_prev_ofs = theme_cache.parent_hl_line_margin; prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); } else if (p_item->is_selected(0)) { // If parent item is selected (but this item is not), we draw the line using children highlight style. // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. if (_is_sibling_branch_selected(c)) { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), theme_cache.children_hl_line_color, children_line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), theme_cache.parent_hl_line_color, parent_line_width); prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); } else { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), theme_cache.children_hl_line_color, children_line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), theme_cache.children_hl_line_color, children_line_width); } } else { // If nothing of the above is true, we draw the line using normal style. // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. if (_is_sibling_branch_selected(c)) { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + theme_cache.parent_hl_line_margin, root_pos.y), theme_cache.relationship_line_color, line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), theme_cache.parent_hl_line_color, parent_line_width); prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); } else { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), theme_cache.relationship_line_color, line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), theme_cache.relationship_line_color, line_width); } } } @@ -2338,12 +2346,12 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 break; // Last loop done, stop. } - if (cache.draw_relationship_lines == 0) { + if (theme_cache.draw_relationship_lines == 0) { return -1; // No need to draw anymore, full stop. } htotal = -1; - children_pos.y = cache.offset.y + p_draw_size.height; + children_pos.y = theme_cache.offset.y + p_draw_size.height; } else { htotal += child_h; children_pos.y += child_h; @@ -2494,7 +2502,7 @@ Rect2 Tree::search_item_rect(TreeItem *p_from, TreeItem *p_item) { void Tree::_range_click_timeout() { if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - Point2 pos = get_local_mouse_position() - cache.bg->get_offset(); + Point2 pos = get_local_mouse_position() - theme_cache.bg->get_offset(); if (show_column_titles) { pos.y -= _get_title_button_height(); @@ -2512,7 +2520,7 @@ void Tree::_range_click_timeout() { Ref<InputEventMouseButton> mb; mb.instantiate(); - int x_limit = get_size().width - cache.bg->get_minimum_size().width; + int x_limit = get_size().width - theme_cache.bg->get_minimum_size().width; if (h_scroll->is_visible()) { x_limit -= h_scroll->get_minimum_size().width; } @@ -2521,7 +2529,7 @@ void Tree::_range_click_timeout() { propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case) blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MouseButton::LEFT, mb); + propagate_mouse_event(pos + theme_cache.offset, 0, 0, x_limit + theme_cache.offset.width, false, root, MouseButton::LEFT, mb); blocked--; if (range_click_timer->is_one_shot()) { @@ -2550,7 +2558,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int return 0; } - int item_h = compute_item_height(p_item) + cache.vseparation; + int item_h = compute_item_height(p_item) + theme_cache.vseparation; bool skip = (p_item == root && hide_root); @@ -2561,7 +2569,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int return -1; } - if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) { + if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + theme_cache.item_margin))) { p_item->set_collapsed(!p_item->is_collapsed()); return -1; } @@ -2580,7 +2588,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (p_item->cells[i].expand_right) { int plus = 1; while (i + plus < columns.size() && !p_item->cells[i + plus].editable && p_item->cells[i + plus].mode == TreeItem::CELL_MODE_STRING && p_item->cells[i + plus].text.is_empty() && p_item->cells[i + plus].icon.is_null()) { - col_width += cache.hseparation; + col_width += theme_cache.hseparation; col_width += get_column_width(i + plus); plus++; } @@ -2600,16 +2608,16 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (col == -1) { return -1; } else if (col == 0) { - int margin = x_ofs + cache.item_margin; //-cache.hseparation; - //int lm = cache.bg->get_margin(SIDE_LEFT); + int margin = x_ofs + theme_cache.item_margin; //-theme_cache.hseparation; + //int lm = theme_cache.bg->get_margin(SIDE_LEFT); col_width -= margin; limit_w -= margin; col_ofs += margin; x -= margin; } else { - col_width -= cache.hseparation; - limit_w -= cache.hseparation; - x -= cache.hseparation; + col_width -= theme_cache.hseparation; + limit_w -= theme_cache.hseparation; + x -= theme_cache.hseparation; } if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) { @@ -2626,7 +2634,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int int button_w = 0; for (int j = p_item->cells[col].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[col].buttons[j].texture; - button_w += b->get_size().width + cache.button_pressed->get_minimum_size().width + cache.button_margin; + button_w += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin; } col_width = MAX(button_w, MIN(limit_w, col_width)); @@ -2634,7 +2642,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - int w = b->get_size().width + cache.button_pressed->get_minimum_size().width; + int w = b->get_size().width + theme_cache.button_pressed->get_minimum_size().width; if (x > col_width - w) { if (c.buttons[j].disabled) { @@ -2662,7 +2670,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int return -1; } - col_width -= w + cache.button_margin; + col_width -= w + theme_cache.button_margin; } if (p_button == MouseButton::LEFT || (p_button == MouseButton::RIGHT && allow_rmb_select)) { @@ -2742,7 +2750,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int case TreeItem::CELL_MODE_CHECK: { bring_up_editor = false; //checkboxes are not edited with editor if (force_edit_checkbox_only_on_checkbox) { - if (x < cache.checked->get_width()) { + if (x < theme_cache.checked->get_width()) { p_item->set_checked(col, !c.checked); item_edited(col, p_item, p_button); } @@ -2764,7 +2772,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } popup_menu->set_size(Size2(col_width, 0)); - popup_menu->set_position(get_screen_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h) - cache.offset); + popup_menu->set_position(get_screen_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h) - theme_cache.offset); popup_menu->popup(); popup_edited_item = p_item; popup_edited_item_col = col; @@ -2822,9 +2830,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int case TreeItem::CELL_MODE_CUSTOM: { edited_item = p_item; edited_col = col; - bool on_arrow = x > col_width - cache.select_arrow->get_width(); + bool on_arrow = x > col_width - theme_cache.select_arrow->get_width(); - custom_popup_rect = Rect2i(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h - cache.offset.y), Size2(get_column_width(col), item_h)); + custom_popup_rect = Rect2i(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h - theme_cache.offset.y), Size2(get_column_width(col), item_h)); if (on_arrow || !p_item->cells[col].custom_button) { emit_signal(SNAME("custom_popup_edited"), ((bool)(x >= (col_width - item_h / 2)))); @@ -2846,7 +2854,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int popup_pressing_edited_item = p_item; popup_pressing_edited_item_column = col; - pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - cache.offset, Size2(col_width, item_h)); + pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - theme_cache.offset, Size2(col_width, item_h)); pressing_for_editor_text = editor_text; pressing_for_editor = true; @@ -2855,8 +2863,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int Point2i new_pos = p_pos; if (!skip) { - x_ofs += cache.item_margin; - //new_pos.x-=cache.item_margin; + x_ofs += theme_cache.item_margin; + //new_pos.x-=theme_cache.item_margin; y_ofs += item_h; new_pos.y -= item_h; } @@ -3300,18 +3308,14 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff - update_cache(); - } - - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.bg; bool rtl = is_layout_rtl(); Point2 pos = mm->get_position(); if (rtl) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.bg->get_offset(); Cache::ClickType old_hover = cache.hover_type; int old_index = cache.hover_index; @@ -3321,7 +3325,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (show_column_titles) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - pos.x += cache.offset.x; + pos.x += theme_cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { len += get_column_width(i); @@ -3339,7 +3343,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (rtl) { mpos.x = get_size().width - mpos.x; } - mpos -= cache.bg->get_offset(); + mpos -= theme_cache.bg->get_offset(); mpos.y -= _get_title_button_height(); if (mpos.y >= 0) { if (h_scroll->is_visible_in_tree()) { @@ -3430,10 +3434,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff - update_cache(); - } - bool rtl = is_layout_rtl(); if (!mb->is_pressed()) { @@ -3443,12 +3443,12 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (rtl) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.bg->get_offset(); if (show_column_titles) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - pos.x += cache.offset.x; + pos.x += theme_cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { len += get_column_width(i); @@ -3537,7 +3537,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { switch (mb->get_button_index()) { case MouseButton::RIGHT: case MouseButton::LEFT: { - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.bg; Point2 pos = mb->get_position(); if (rtl) { @@ -3549,7 +3549,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - pos.x += cache.offset.x; + pos.x += theme_cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { len += get_column_width(i); @@ -3572,14 +3572,14 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pressing_for_editor = false; propagate_mouse_activated = false; - int x_limit = get_size().width - cache.bg->get_minimum_size().width; + int x_limit = get_size().width - theme_cache.bg->get_minimum_size().width; if (h_scroll->is_visible()) { x_limit -= h_scroll->get_minimum_size().width; } cache.rtl = is_layout_rtl(); blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb); + propagate_mouse_event(pos + theme_cache.offset, 0, 0, x_limit + theme_cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb); blocked--; if (pressing_for_editor) { @@ -3766,7 +3766,7 @@ bool Tree::is_editing() { } Size2 Tree::get_internal_min_size() const { - Size2i size = cache.bg->get_offset(); + Size2i size = theme_cache.bg->get_offset(); if (root) { size.height += get_item_height(root); } @@ -3789,23 +3789,23 @@ void Tree::update_scrollbars() { Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); - v_scroll->set_begin(Point2(size.width - vmin.width, cache.bg->get_margin(SIDE_TOP))); - v_scroll->set_end(Point2(size.width, size.height - cache.bg->get_margin(SIDE_TOP) - cache.bg->get_margin(SIDE_BOTTOM))); + v_scroll->set_begin(Point2(size.width - vmin.width, theme_cache.bg->get_margin(SIDE_TOP))); + v_scroll->set_end(Point2(size.width, size.height - theme_cache.bg->get_margin(SIDE_TOP) - theme_cache.bg->get_margin(SIDE_BOTTOM))); h_scroll->set_begin(Point2(0, size.height - hmin.height)); h_scroll->set_end(Point2(size.width - vmin.width, size.height)); Size2 internal_min_size = get_internal_min_size(); - bool display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) > size.height; - bool display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) > size.width; + bool display_vscroll = internal_min_size.height + theme_cache.bg->get_margin(SIDE_TOP) > size.height; + bool display_hscroll = internal_min_size.width + theme_cache.bg->get_margin(SIDE_LEFT) > size.width; for (int i = 0; i < 2; i++) { // Check twice, as both values are dependent on each other. if (display_hscroll) { - display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) + hmin.height > size.height; + display_vscroll = internal_min_size.height + theme_cache.bg->get_margin(SIDE_TOP) + hmin.height > size.height; } if (display_vscroll) { - display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) + vmin.width > size.width; + display_hscroll = internal_min_size.width + theme_cache.bg->get_margin(SIDE_LEFT) + vmin.width > size.width; } } @@ -3813,29 +3813,29 @@ void Tree::update_scrollbars() { v_scroll->show(); v_scroll->set_max(internal_min_size.height); v_scroll->set_page(size.height - hmin.height - tbh); - cache.offset.y = v_scroll->get_value(); + theme_cache.offset.y = v_scroll->get_value(); } else { v_scroll->hide(); - cache.offset.y = 0; + theme_cache.offset.y = 0; } if (display_hscroll) { h_scroll->show(); h_scroll->set_max(internal_min_size.width); h_scroll->set_page(size.width - vmin.width); - cache.offset.x = h_scroll->get_value(); + theme_cache.offset.x = h_scroll->get_value(); } else { h_scroll->hide(); - cache.offset.x = 0; + theme_cache.offset.x = 0; } } int Tree::_get_title_button_height() const { - ERR_FAIL_COND_V(cache.font.is_null() || cache.title_button.is_null(), 0); + ERR_FAIL_COND_V(theme_cache.font.is_null() || theme_cache.title_button.is_null(), 0); int h = 0; if (show_column_titles) { for (int i = 0; i < columns.size(); i++) { - h = MAX(h, columns[i].text_buf->get_size().y + cache.title_button->get_minimum_size().height); + h = MAX(h, columns[i].text_buf->get_size().y + theme_cache.title_button->get_minimum_size().height); } } return h; @@ -3860,10 +3860,6 @@ void Tree::_notification(int p_what) { drag_touching = false; } break; - case NOTIFICATION_ENTER_TREE: { - update_cache(); - } break; - case NOTIFICATION_DRAG_END: { drop_mode_flags = 0; scrolling = false; @@ -3873,7 +3869,7 @@ void Tree::_notification(int p_what) { case NOTIFICATION_DRAG_BEGIN: { single_select_defer = nullptr; - if (cache.scroll_speed > 0) { + if (theme_cache.scroll_speed > 0) { scrolling = true; set_physics_process_internal(true); } @@ -3917,22 +3913,22 @@ void Tree::_notification(int p_what) { } Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position(); - if (scrolling && get_rect().grow(cache.scroll_border).has_point(mouse_position)) { + if (scrolling && get_rect().grow(theme_cache.scroll_border).has_point(mouse_position)) { Point2 point; - if ((ABS(mouse_position.x) < ABS(mouse_position.x - get_size().width)) && (ABS(mouse_position.x) < cache.scroll_border)) { - point.x = mouse_position.x - cache.scroll_border; - } else if (ABS(mouse_position.x - get_size().width) < cache.scroll_border) { - point.x = mouse_position.x - (get_size().width - cache.scroll_border); + if ((ABS(mouse_position.x) < ABS(mouse_position.x - get_size().width)) && (ABS(mouse_position.x) < theme_cache.scroll_border)) { + point.x = mouse_position.x - theme_cache.scroll_border; + } else if (ABS(mouse_position.x - get_size().width) < theme_cache.scroll_border) { + point.x = mouse_position.x - (get_size().width - theme_cache.scroll_border); } - if ((ABS(mouse_position.y) < ABS(mouse_position.y - get_size().height)) && (ABS(mouse_position.y) < cache.scroll_border)) { - point.y = mouse_position.y - cache.scroll_border; - } else if (ABS(mouse_position.y - get_size().height) < cache.scroll_border) { - point.y = mouse_position.y - (get_size().height - cache.scroll_border); + if ((ABS(mouse_position.y) < ABS(mouse_position.y - get_size().height)) && (ABS(mouse_position.y) < theme_cache.scroll_border)) { + point.y = mouse_position.y - theme_cache.scroll_border; + } else if (ABS(mouse_position.y - get_size().height) < theme_cache.scroll_border) { + point.y = mouse_position.y - (get_size().height - theme_cache.scroll_border); } - point *= cache.scroll_speed * get_physics_process_delta_time(); + point *= theme_cache.scroll_speed * get_physics_process_delta_time(); point += get_scroll(); h_scroll->set_value(point.x); v_scroll->set_value(point.y); @@ -3940,13 +3936,12 @@ void Tree::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - update_cache(); + v_scroll->set_custom_step(theme_cache.font->get_height(theme_cache.font_size)); + update_scrollbars(); RID ci = get_canvas_item(); - Ref<StyleBox> bg = cache.bg; - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Ref<StyleBox> bg = theme_cache.bg; Point2 draw_ofs; draw_ofs += bg->get_offset(); @@ -3970,11 +3965,11 @@ void Tree::_notification(int p_what) { if (show_column_titles) { //title buttons - int ofs2 = cache.bg->get_margin(SIDE_LEFT); + int ofs2 = theme_cache.bg->get_margin(SIDE_LEFT); for (int i = 0; i < columns.size(); i++) { - Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? cache.title_button_hover : cache.title_button); - Ref<Font> f = cache.tb_font; - Rect2 tbrect = Rect2(ofs2 - cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh); + Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? theme_cache.title_button_hover : theme_cache.title_button); + Ref<Font> f = theme_cache.tb_font; + Rect2 tbrect = Rect2(ofs2 - theme_cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh); if (cache.rtl) { tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x; } @@ -3985,10 +3980,10 @@ void Tree::_notification(int p_what) { columns.write[i].text_buf->set_width(clip_w); Vector2 text_pos = tbrect.position + Point2i(sb->get_offset().x + (tbrect.size.width - columns[i].text_buf->get_size().x) / 2, (tbrect.size.height - columns[i].text_buf->get_size().y) / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - columns[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + columns[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - columns[i].text_buf->draw(ci, text_pos, cache.title_button_color); + columns[i].text_buf->draw(ci, text_pos, theme_cache.title_button_color); } } @@ -3996,8 +3991,7 @@ void Tree::_notification(int p_what) { // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling. if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - const Ref<StyleBox> bg_focus = get_theme_stylebox(SNAME("bg_focus")); - bg_focus->draw(ci, Rect2(Point2(), get_size())); + theme_cache.bg_focus->draw(ci, Rect2(Point2(), get_size())); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } } break; @@ -4005,7 +3999,6 @@ void Tree::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { - update_cache(); _update_all(); } break; @@ -4040,7 +4033,7 @@ Size2 Tree::get_minimum_size() const { return Size2(); } else { Vector2 min_size = get_internal_min_size(); - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.bg; if (bg.is_valid()) { min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM); @@ -4336,7 +4329,7 @@ int Tree::get_column_minimum_width(int p_column) const { // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); + min_width = MAX(theme_cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + theme_cache.bg->get_margin(SIDE_LEFT) + theme_cache.bg->get_margin(SIDE_RIGHT), min_width); } if (!columns[p_column].clip_content) { @@ -4360,9 +4353,9 @@ int Tree::get_column_minimum_width(int p_column) const { // Get the item minimum size. Size2 item_size = item->get_minimum_size(p_column); if (p_column == 0) { - item_size.width += cache.item_margin * depth; + item_size.width += theme_cache.item_margin * depth; } else { - item_size.width += cache.hseparation; + item_size.width += theme_cache.hseparation; } // Check if the item is wider. @@ -4381,7 +4374,7 @@ int Tree::get_column_width(int p_column) const { if (columns[p_column].expand) { int expand_area = get_size().width; - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.bg; if (bg.is_valid()) { expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); @@ -4458,7 +4451,7 @@ int Tree::get_item_offset(TreeItem *p_item) const { ofs += compute_item_height(it); if (it != root || !hide_root) { - ofs += cache.vseparation; + ofs += theme_cache.vseparation; } if (it->first_child && !it->collapsed) { @@ -4489,14 +4482,14 @@ void Tree::ensure_cursor_is_visible() { return; // Nothing under cursor. } - const Size2 area_size = get_size() - cache.bg->get_minimum_size(); + const Size2 area_size = get_size() - theme_cache.bg->get_minimum_size(); int y_offset = get_item_offset(selected_item); if (y_offset != -1) { const int tbh = _get_title_button_height(); y_offset -= tbh; - const int cell_h = compute_item_height(selected_item) + cache.vseparation; + const int cell_h = compute_item_height(selected_item) + theme_cache.vseparation; const int screen_h = area_size.height - h_scroll->get_combined_minimum_size().height - tbh; if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. @@ -4563,7 +4556,7 @@ Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column, int p_button) const { Vector2 ofst = Vector2(r.position.x + r.size.x, r.position.y); for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); ofst.x -= size.x; if (j == p_button) { @@ -4596,9 +4589,6 @@ void Tree::set_column_title(int p_column, const String &p_title) { return; } - if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff - update_cache(); - } columns.write[p_column].title = p_title; update_column(p_column); queue_redraw(); @@ -4660,7 +4650,7 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { const real_t tree_height = get_size().y; const Rect2 item_rect = get_item_rect(p_item); const real_t item_y = item_rect.position.y; - const real_t item_height = item_rect.size.y + cache.vseparation; + const real_t item_height = item_rect.size.y + theme_cache.vseparation; if (p_center_on_item) { v_scroll->set_value(item_y - (tree_height - item_height) / 2.0f); @@ -4784,7 +4774,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_ Point2 pos = p_pos; if ((root != p_item || !hide_root) && p_item->is_visible()) { - h = compute_item_height(p_item) + cache.vseparation; + h = compute_item_height(p_item) + theme_cache.vseparation; if (pos.y < h) { if (drop_mode_flags == DROP_MODE_ON_ITEM) { section = 0; @@ -4841,7 +4831,7 @@ int Tree::get_column_at_position(const Point2 &p_pos) const { if (is_layout_rtl()) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.bg->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return -1; @@ -4871,7 +4861,7 @@ int Tree::get_drop_section_at_position(const Point2 &p_pos) const { if (is_layout_rtl()) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.bg->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return -100; @@ -4901,7 +4891,7 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const { if (is_layout_rtl()) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.bg->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return nullptr; @@ -4928,7 +4918,7 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const { int Tree::get_button_id_at_position(const Point2 &p_pos) const { if (root) { Point2 pos = p_pos; - pos -= cache.bg->get_offset(); + pos -= theme_cache.bg->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return -1; @@ -4954,7 +4944,7 @@ int Tree::get_button_id_at_position(const Point2 &p_pos) const { for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); if (pos.x > col_width - size.width) { return c.buttons[j].id; } @@ -4969,7 +4959,7 @@ int Tree::get_button_id_at_position(const Point2 &p_pos) const { String Tree::get_tooltip(const Point2 &p_pos) const { if (root) { Point2 pos = p_pos; - pos -= cache.bg->get_offset(); + pos -= theme_cache.bg->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return Control::get_tooltip(p_pos); @@ -4995,7 +4985,7 @@ String Tree::get_tooltip(const Point2 &p_pos) const { for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); if (pos.x > col_width - size.width) { String tooltip = c.buttons[j].tooltip; if (!tooltip.is_empty()) { @@ -5231,8 +5221,6 @@ Tree::Tree() { set_mouse_filter(MOUSE_FILTER_STOP); set_clip_contents(true); - - update_cache(); } Tree::~Tree() { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 8eabdd60a1..b4ee686bab 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -482,12 +482,13 @@ private: void propagate_set_columns(TreeItem *p_item); - struct Cache { + struct ThemeCache { Ref<Font> font; Ref<Font> tb_font; int font_size = 0; int tb_font_size = 0; Ref<StyleBox> bg; + Ref<StyleBox> bg_focus; Ref<StyleBox> selected; Ref<StyleBox> selected_focus; Ref<StyleBox> cursor; @@ -505,8 +506,9 @@ private: Ref<Texture2D> checked; Ref<Texture2D> unchecked; Ref<Texture2D> indeterminate; - Ref<Texture2D> arrow_collapsed; Ref<Texture2D> arrow; + Ref<Texture2D> arrow_collapsed; + Ref<Texture2D> arrow_collapsed_mirrored; Ref<Texture2D> select_arrow; Ref<Texture2D> updown; @@ -536,7 +538,9 @@ private: int scroll_border = 0; int scroll_speed = 0; int font_outline_size = 0; + } theme_cache; + struct Cache { enum ClickType { CLICK_NONE, CLICK_TITLE, @@ -559,7 +563,6 @@ private: Point2i text_editor_position; bool rtl = false; - } cache; int _get_title_button_height() const; @@ -572,7 +575,6 @@ private: bool v_scroll_enabled = true; Size2 get_internal_min_size() const; - void update_cache(); void update_scrollbars(); Rect2 search_item_rect(TreeItem *p_from, TreeItem *p_item); @@ -620,6 +622,8 @@ private: bool _scroll(bool p_horizontal, float p_pages); protected: + virtual void _update_theme_item_cache() override; + static void _bind_methods(); public: diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 1e52b644a3..79a1c71064 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -799,6 +799,11 @@ Viewport *Window::_get_embedder() const { void Window::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + _invalidate_theme_cache(); + _update_theme_item_cache(); + } break; + case NOTIFICATION_ENTER_TREE: { bool embedded = false; { @@ -858,6 +863,8 @@ void Window::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { emit_signal(SceneStringNames::get_singleton()->theme_changed); + _invalidate_theme_cache(); + _update_theme_item_cache(); } break; case NOTIFICATION_READY: { @@ -867,6 +874,9 @@ void Window::_notification(int p_what) { } break; case NOTIFICATION_TRANSLATION_CHANGED: { + _invalidate_theme_cache(); + _update_theme_item_cache(); + if (embedder) { embedder->_sub_window_update(this); } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { @@ -1342,6 +1352,18 @@ void Window::_theme_changed() { } } +void Window::_invalidate_theme_cache() { + theme_icon_cache.clear(); + theme_style_cache.clear(); + theme_font_cache.clear(); + theme_font_size_cache.clear(); + theme_color_cache.clear(); + theme_constant_cache.clear(); +} + +void Window::_update_theme_item_cache() { +} + void Window::set_theme_type_variation(const StringName &p_theme_type) { theme_type_variation = p_theme_type; if (is_inside_tree()) { @@ -1366,39 +1388,75 @@ void Window::_get_theme_type_dependencies(const StringName &p_theme_type, List<S } Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (theme_icon_cache.has(p_theme_type) && theme_icon_cache[p_theme_type].has(p_name)) { + return theme_icon_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return Control::get_theme_item_in_types<Ref<Texture2D>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); + Ref<Texture2D> icon = Control::get_theme_item_in_types<Ref<Texture2D>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); + theme_icon_cache[p_theme_type][p_name] = icon; + return icon; } Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (theme_style_cache.has(p_theme_type) && theme_style_cache[p_theme_type].has(p_name)) { + return theme_style_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return Control::get_theme_item_in_types<Ref<StyleBox>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + Ref<StyleBox> style = Control::get_theme_item_in_types<Ref<StyleBox>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + theme_style_cache[p_theme_type][p_name] = style; + return style; } Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { + if (theme_font_cache.has(p_theme_type) && theme_font_cache[p_theme_type].has(p_name)) { + return theme_font_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return Control::get_theme_item_in_types<Ref<Font>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); + Ref<Font> font = Control::get_theme_item_in_types<Ref<Font>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); + theme_font_cache[p_theme_type][p_name] = font; + return font; } int Window::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (theme_font_size_cache.has(p_theme_type) && theme_font_size_cache[p_theme_type].has(p_name)) { + return theme_font_size_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); + int font_size = Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); + theme_font_size_cache[p_theme_type][p_name] = font_size; + return font_size; } Color Window::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { + if (theme_color_cache.has(p_theme_type) && theme_color_cache[p_theme_type].has(p_name)) { + return theme_color_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return Control::get_theme_item_in_types<Color>(theme_owner, theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); + Color color = Control::get_theme_item_in_types<Color>(theme_owner, theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); + theme_color_cache[p_theme_type][p_name] = color; + return color; } int Window::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (theme_constant_cache.has(p_theme_type) && theme_constant_cache[p_theme_type].has(p_name)) { + return theme_constant_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); + int constant = Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); + theme_constant_cache[p_theme_type][p_name] = constant; + return constant; } bool Window::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { diff --git a/scene/main/window.h b/scene/main/window.h index 238be484c0..5a42c5bb83 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -32,12 +32,12 @@ #define WINDOW_H #include "scene/main/viewport.h" +#include "scene/resources/theme.h" class Control; class Font; class Shortcut; class StyleBox; -class Theme; class Window : public Viewport { GDCLASS(Window, Viewport) @@ -141,6 +141,18 @@ private: Window *theme_owner_window = nullptr; StringName theme_type_variation; + mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache; + mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache; + mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache; + mutable HashMap<StringName, Theme::ThemeFontSizeMap> theme_font_size_cache; + mutable HashMap<StringName, Theme::ThemeColorMap> theme_color_cache; + mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache; + + _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; + + void _theme_changed(); + void _invalidate_theme_cache(); + Viewport *embedder = nullptr; friend class Viewport; //friend back, can call the methods below @@ -158,6 +170,8 @@ protected: Viewport *_get_embedder() const; virtual Rect2i _popup_adjust_rect() const { return Rect2i(); } + virtual void _update_theme_item_cache(); + virtual void _post_popup() {} virtual Size2 _get_contents_minimum_size() const; static void _bind_methods(); @@ -259,11 +273,9 @@ public: void set_theme(const Ref<Theme> &p_theme); Ref<Theme> get_theme() const; - void _theme_changed(); void set_theme_type_variation(const StringName &p_theme_type); StringName get_theme_type_variation() const; - _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; Size2 get_contents_minimum_size() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index cc40d36fa3..6643fb8418 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -54,6 +54,7 @@ #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/multimesh_instance_2d.h" #include "scene/2d/navigation_agent_2d.h" +#include "scene/2d/navigation_link_2d.h" #include "scene/2d/navigation_obstacle_2d.h" #include "scene/2d/parallax_background.h" #include "scene/2d/parallax_layer.h" @@ -240,6 +241,7 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" #include "scene/3d/navigation_agent_3d.h" +#include "scene/3d/navigation_link_3d.h" #include "scene/3d/navigation_obstacle_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/node_3d.h" @@ -577,6 +579,7 @@ void register_scene_types() { GDREGISTER_CLASS(NavigationRegion3D); GDREGISTER_CLASS(NavigationAgent3D); GDREGISTER_CLASS(NavigationObstacle3D); + GDREGISTER_CLASS(NavigationLink3D); OS::get_singleton()->yield(); // may take time to init #endif // _3D_DISABLED @@ -934,6 +937,7 @@ void register_scene_types() { GDREGISTER_CLASS(NavigationRegion2D); GDREGISTER_CLASS(NavigationAgent2D); GDREGISTER_CLASS(NavigationObstacle2D); + GDREGISTER_CLASS(NavigationLink2D); OS::get_singleton()->yield(); // may take time to init diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index 9b1adde00a..0505f6b559 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -33,13 +33,18 @@ #include "core/io/image_loader.h" #include "core/variant/typed_array.h" -void BitMap::create(const Size2 &p_size) { +void BitMap::create(const Size2i &p_size) { ERR_FAIL_COND(p_size.width < 1); ERR_FAIL_COND(p_size.height < 1); + ERR_FAIL_COND(static_cast<int64_t>(p_size.width) * static_cast<int64_t>(p_size.height) > INT32_MAX); + + Error err = bitmask.resize((((p_size.width * p_size.height) - 1) / 8) + 1); + ERR_FAIL_COND(err != OK); + width = p_size.width; height = p_size.height; - bitmask.resize((((width * height) - 1) / 8) + 1); + memset(bitmask.ptrw(), 0, bitmask.size()); } @@ -49,7 +54,7 @@ void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshol img->convert(Image::FORMAT_LA8); ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8); - create(img->get_size()); + create(Size2i(img->get_width(), img->get_height())); const uint8_t *r = img->get_data().ptr(); uint8_t *w = bitmask.ptrw(); @@ -63,7 +68,7 @@ void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshol } } -void BitMap::set_bit_rect(const Rect2 &p_rect, bool p_value) { +void BitMap::set_bit_rect(const Rect2i &p_rect, bool p_value) { Rect2i current = Rect2i(0, 0, width, height).intersection(p_rect); uint8_t *data = bitmask.ptrw(); @@ -91,7 +96,7 @@ int BitMap::get_true_bit_count() const { const uint8_t *d = bitmask.ptr(); int c = 0; - //fast, almost branchless version + // Fast, almost branchless version. for (int i = 0; i < ds; i++) { c += (d[i] & (1 << 7)) >> 7; @@ -107,14 +112,15 @@ int BitMap::get_true_bit_count() const { return c; } -void BitMap::set_bit(const Point2 &p_pos, bool p_value) { - int x = p_pos.x; - int y = p_pos.y; +void BitMap::set_bitv(const Point2i &p_pos, bool p_value) { + set_bit(p_pos.x, p_pos.y, p_value); +} - ERR_FAIL_INDEX(x, width); - ERR_FAIL_INDEX(y, height); +void BitMap::set_bit(int p_x, int p_y, bool p_value) { + ERR_FAIL_INDEX(p_x, width); + ERR_FAIL_INDEX(p_y, height); - int ofs = width * y + x; + int ofs = width * p_y + p_x; int bbyte = ofs / 8; int bbit = ofs % 8; @@ -129,21 +135,23 @@ void BitMap::set_bit(const Point2 &p_pos, bool p_value) { bitmask.write[bbyte] = b; } -bool BitMap::get_bit(const Point2 &p_pos) const { - int x = Math::fast_ftoi(p_pos.x); - int y = Math::fast_ftoi(p_pos.y); - ERR_FAIL_INDEX_V(x, width, false); - ERR_FAIL_INDEX_V(y, height, false); +bool BitMap::get_bitv(const Point2i &p_pos) const { + return get_bit(p_pos.x, p_pos.y); +} + +bool BitMap::get_bit(int p_x, int p_y) const { + ERR_FAIL_INDEX_V(p_x, width, false); + ERR_FAIL_INDEX_V(p_y, height, false); - int ofs = width * y + x; + int ofs = width * p_y + p_x; int bbyte = ofs / 8; int bbit = ofs % 8; return (bitmask[bbyte] & (1 << bbit)) != 0; } -Size2 BitMap::get_size() const { - return Size2(width, height); +Size2i BitMap::get_size() const { + return Size2i(width, height); } void BitMap::_set_data(const Dictionary &p_d) { @@ -161,13 +169,13 @@ Dictionary BitMap::_get_data() const { return d; } -Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) const { +Vector<Vector2> BitMap::_march_square(const Rect2i &p_rect, const Point2i &p_start) const { int stepx = 0; int stepy = 0; int prevx = 0; int prevy = 0; - int startx = start.x; - int starty = start.y; + int startx = p_start.x; + int starty = p_start.y; int curx = startx; int cury = starty; unsigned int count = 0; @@ -176,7 +184,7 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) Vector<Vector2> _points; do { int sv = 0; - { //square value + { // Square value /* checking the 2x2 pixel grid, assigning these values to each pixel, if not transparent @@ -187,13 +195,13 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) +---+---+ */ Point2i tl = Point2i(curx - 1, cury - 1); - sv += (rect.has_point(tl) && get_bit(tl)) ? 1 : 0; + sv += (p_rect.has_point(tl) && get_bitv(tl)) ? 1 : 0; Point2i tr = Point2i(curx, cury - 1); - sv += (rect.has_point(tr) && get_bit(tr)) ? 2 : 0; + sv += (p_rect.has_point(tr) && get_bitv(tr)) ? 2 : 0; Point2i bl = Point2i(curx - 1, cury); - sv += (rect.has_point(bl) && get_bit(bl)) ? 4 : 0; + sv += (p_rect.has_point(bl) && get_bitv(bl)) ? 4 : 0; Point2i br = Point2i(curx, cury); - sv += (rect.has_point(br) && get_bit(br)) ? 8 : 0; + sv += (p_rect.has_point(br) && get_bitv(br)) ? 8 : 0; ERR_FAIL_COND_V(sv == 0 || sv == 15, Vector<Vector2>()); } @@ -303,16 +311,16 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) default: ERR_PRINT("this shouldn't happen."); } - //little optimization - // if previous direction is same as current direction, - // then we should modify the last vec to current + // Small optimization: + // If the previous direction is same as the current direction, + // then we should modify the last vector to current. curx += stepx; cury += stepy; if (stepx == prevx && stepy == prevy) { - _points.write[_points.size() - 1].x = (float)(curx - rect.position.x); - _points.write[_points.size() - 1].y = (float)(cury + rect.position.y); + _points.write[_points.size() - 1].x = (float)(curx - p_rect.position.x); + _points.write[_points.size() - 1].y = (float)(cury + p_rect.position.y); } else { - _points.push_back(Vector2((float)(curx - rect.position.x), (float)(cury + rect.position.y))); + _points.push_back(Vector2((float)(curx - p_rect.position.x), (float)(cury + p_rect.position.y))); } count++; @@ -348,7 +356,7 @@ static Vector<Vector2> rdp(const Vector<Vector2> &v, float optimization) { int index = -1; float dist = 0.0; - //not looping first and last point + // Not looping first and last point. for (size_t i = 1, size = v.size(); i < size - 1; ++i) { float cdist = perpendicular_distance(v[i], v[0], v[v.size() - 1]); if (cdist > dist) { @@ -385,9 +393,9 @@ static Vector<Vector2> rdp(const Vector<Vector2> &v, float optimization) { static Vector<Vector2> reduce(const Vector<Vector2> &points, const Rect2i &rect, float epsilon) { int size = points.size(); - // if there are less than 3 points, then we have nothing + // If there are less than 3 points, then we have nothing. ERR_FAIL_COND_V(size < 3, Vector<Vector2>()); - // if there are less than 9 points (but more than 3), then we don't need to reduce it + // If there are less than 9 points (but more than 3), then we don't need to reduce it. if (size < 9) { return points; } @@ -412,9 +420,9 @@ struct FillBitsStackEntry { }; static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_pos, const Rect2i &rect) { - // Using a custom stack to work iteratively to avoid stack overflow on big bitmaps + // Using a custom stack to work iteratively to avoid stack overflow on big bitmaps. Vector<FillBitsStackEntry> stack; - // Tracking size since we won't be shrinking the stack vector + // Tracking size since we won't be shrinking the stack vector. int stack_size = 0; Point2i pos = p_pos; @@ -433,10 +441,10 @@ static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_ for (int i = next_i; i <= pos.x + 1; i++) { for (int j = next_j; j <= pos.y + 1; j++) { if (popped) { - // The next loop over j must start normally + // The next loop over j must start normally. next_j = pos.y; popped = false; - // Skip because an iteration was already executed with current counter values + // Skip because an iteration was already executed with current counter values. continue; } @@ -447,11 +455,11 @@ static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_ continue; } - if (p_map->get_bit(Vector2(i, j))) { + if (p_map->get_bit(i, j)) { continue; - } else if (p_src->get_bit(Vector2(i, j))) { - p_map->set_bit(Vector2(i, j), true); + } else if (p_src->get_bit(i, j)) { + p_map->set_bit(i, j, true); FillBitsStackEntry se = { pos, i, j }; stack.resize(MAX(stack_size + 1, stack.size())); @@ -482,7 +490,7 @@ static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_ print_verbose("BitMap: Max stack size: " + itos(stack.size())); } -Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, float p_epsilon) const { +Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2i &p_rect, float p_epsilon) const { Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect); print_verbose("BitMap: Rect: " + r); @@ -494,7 +502,7 @@ Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, flo Vector<Vector<Vector2>> polygons; for (int i = r.position.y; i < r.position.y + r.size.height; i++) { for (int j = r.position.x; j < r.position.x + r.size.width; j++) { - if (!fill->get_bit(Point2(j, i)) && get_bit(Point2(j, i))) { + if (!fill->get_bit(j, i) && get_bit(j, i)) { fill_bits(this, fill, Point2i(j, i), r); Vector<Vector2> polygon = _march_square(r, Point2i(j, i)); @@ -515,7 +523,7 @@ Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, flo return polygons; } -void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { +void BitMap::grow_mask(int p_pixels, const Rect2i &p_rect) { if (p_pixels == 0) { return; } @@ -532,7 +540,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { for (int i = r.position.y; i < r.position.y + r.size.height; i++) { for (int j = r.position.x; j < r.position.x + r.size.width; j++) { - if (bit_value == get_bit(Point2(j, i))) { + if (bit_value == get_bit(j, i)) { continue; } @@ -543,7 +551,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { bool outside = false; if ((x < p_rect.position.x) || (x >= p_rect.position.x + p_rect.size.x) || (y < p_rect.position.y) || (y >= p_rect.position.y + p_rect.size.y)) { - // outside of rectangle counts as bit not set + // Outside of rectangle counts as bit not set. if (!bit_value) { outside = true; } else { @@ -556,7 +564,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { continue; } - if (outside || (bit_value == copy->get_bit(Point2(x, y)))) { + if (outside || (bit_value == copy->get_bit(x, y))) { found = true; break; } @@ -567,20 +575,20 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { } if (found) { - set_bit(Point2(j, i), bit_value); + set_bit(j, i, bit_value); } } } } -void BitMap::shrink_mask(int p_pixels, const Rect2 &p_rect) { +void BitMap::shrink_mask(int p_pixels, const Rect2i &p_rect) { grow_mask(-p_pixels, p_rect); } -TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2 &p_rect, float p_epsilon) const { +TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2i &p_rect, float p_epsilon) const { Vector<Vector<Vector2>> result = clip_opaque_to_polygons(p_rect, p_epsilon); - // Convert result to bindable types + // Convert result to bindable types. TypedArray<PackedVector2Array> result_array; result_array.resize(result.size()); @@ -603,15 +611,25 @@ TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2 &p_r return result_array; } -void BitMap::resize(const Size2 &p_new_size) { +void BitMap::resize(const Size2i &p_new_size) { + ERR_FAIL_COND(p_new_size.width < 0 || p_new_size.height < 0); + if (p_new_size == get_size()) { + return; + } + Ref<BitMap> new_bitmap; new_bitmap.instantiate(); new_bitmap->create(p_new_size); - int lw = MIN(width, p_new_size.width); - int lh = MIN(height, p_new_size.height); + // also allow for upscaling + int lw = (width == 0) ? 0 : p_new_size.width; + int lh = (height == 0) ? 0 : p_new_size.height; + + float scale_x = ((float)width / p_new_size.width); + float scale_y = ((float)height / p_new_size.height); for (int x = 0; x < lw; x++) { for (int y = 0; y < lh; y++) { - new_bitmap->set_bit(Vector2(x, y), get_bit(Vector2(x, y))); + bool new_bit = get_bit(x * scale_x, y * scale_y); + new_bitmap->set_bit(x, y, new_bit); } } @@ -627,14 +645,16 @@ Ref<Image> BitMap::convert_to_image() const { for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { - image->set_pixel(i, j, get_bit(Point2(i, j)) ? Color(1, 1, 1) : Color(0, 0, 0)); + image->set_pixel(i, j, get_bit(i, j) ? Color(1, 1, 1) : Color(0, 0, 0)); } } return image; } -void BitMap::blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap) { +void BitMap::blit(const Vector2i &p_pos, const Ref<BitMap> &p_bitmap) { + ERR_FAIL_COND_MSG(p_bitmap.is_null(), "It's not a reference to a valid BitMap object."); + int x = p_pos.x; int y = p_pos.y; int w = p_bitmap->get_size().width; @@ -650,8 +670,8 @@ void BitMap::blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap) { if (py < 0 || py >= height) { continue; } - if (p_bitmap->get_bit(Vector2(i, j))) { - set_bit(Vector2(x, y), true); + if (p_bitmap->get_bit(i, j)) { + set_bit(px, py, true); } } } @@ -661,8 +681,10 @@ void BitMap::_bind_methods() { ClassDB::bind_method(D_METHOD("create", "size"), &BitMap::create); ClassDB::bind_method(D_METHOD("create_from_image_alpha", "image", "threshold"), &BitMap::create_from_image_alpha, DEFVAL(0.1)); - ClassDB::bind_method(D_METHOD("set_bit", "position", "bit"), &BitMap::set_bit); - ClassDB::bind_method(D_METHOD("get_bit", "position"), &BitMap::get_bit); + ClassDB::bind_method(D_METHOD("set_bitv", "position", "bit"), &BitMap::set_bitv); + ClassDB::bind_method(D_METHOD("set_bit", "x", "y", "bit"), &BitMap::set_bit); + ClassDB::bind_method(D_METHOD("get_bitv", "position"), &BitMap::get_bitv); + ClassDB::bind_method(D_METHOD("get_bit", "x", "y"), &BitMap::get_bit); ClassDB::bind_method(D_METHOD("set_bit_rect", "rect", "bit"), &BitMap::set_bit_rect); ClassDB::bind_method(D_METHOD("get_true_bit_count"), &BitMap::get_true_bit_count); @@ -681,5 +703,3 @@ void BitMap::_bind_methods() { } BitMap::BitMap() {} - -////////////////////////////////////// diff --git a/scene/resources/bit_map.h b/scene/resources/bit_map.h index d8507dfa8b..291ed8c4d0 100644 --- a/scene/resources/bit_map.h +++ b/scene/resources/bit_map.h @@ -46,9 +46,9 @@ class BitMap : public Resource { int width = 0; int height = 0; - Vector<Vector2> _march_square(const Rect2i &rect, const Point2i &start) const; + Vector<Vector2> _march_square(const Rect2i &p_rect, const Point2i &p_start) const; - TypedArray<PackedVector2Array> _opaque_to_polygons_bind(const Rect2 &p_rect, float p_epsilon) const; + TypedArray<PackedVector2Array> _opaque_to_polygons_bind(const Rect2i &p_rect, float p_epsilon) const; protected: void _set_data(const Dictionary &p_d); @@ -57,24 +57,27 @@ protected: static void _bind_methods(); public: - void create(const Size2 &p_size); + void create(const Size2i &p_size); void create_from_image_alpha(const Ref<Image> &p_image, float p_threshold = 0.1); - void set_bit(const Point2 &p_pos, bool p_value); - bool get_bit(const Point2 &p_pos) const; - void set_bit_rect(const Rect2 &p_rect, bool p_value); + void set_bitv(const Point2i &p_pos, bool p_value); + void set_bit(int p_x, int p_y, bool p_value); + void set_bit_rect(const Rect2i &p_rect, bool p_value); + bool get_bitv(const Point2i &p_pos) const; + bool get_bit(int p_x, int p_y) const; + int get_true_bit_count() const; - Size2 get_size() const; - void resize(const Size2 &p_new_size); + Size2i get_size() const; + void resize(const Size2i &p_new_size); - void grow_mask(int p_pixels, const Rect2 &p_rect); - void shrink_mask(int p_pixels, const Rect2 &p_rect); + void grow_mask(int p_pixels, const Rect2i &p_rect); + void shrink_mask(int p_pixels, const Rect2i &p_rect); - void blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap); + void blit(const Vector2i &p_pos, const Ref<BitMap> &p_bitmap); Ref<Image> convert_to_image() const; - Vector<Vector<Vector2>> clip_opaque_to_polygons(const Rect2 &p_rect, float p_epsilon = 2.0) const; + Vector<Vector<Vector2>> clip_opaque_to_polygons(const Rect2i &p_rect, float p_epsilon = 2.0) const; BitMap(); }; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 73ad1ceff7..32c3c9fe9e 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -609,11 +609,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Dialogs - theme->set_constant("margin", "Dialogs", 8 * scale); - theme->set_constant("button_margin", "Dialogs", 32 * scale); - - // AcceptDialog - + // AcceptDialog is currently the base dialog, so this defines styles for all extending nodes. + theme->set_constant("margin", "AcceptDialog", 8 * scale); + theme->set_constant("button_margin", "AcceptDialog", 32 * scale); theme->set_stylebox("panel", "AcceptDialog", make_flat_stylebox(style_popup_color, 0, 0, 0, 0)); // File Dialog diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index f8f6900976..028c131d0c 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -294,7 +294,7 @@ bool ImageTexture::is_pixel_opaque(int p_x, int p_y) const { x = CLAMP(x, 0, aw); y = CLAMP(y, 0, ah); - return alpha_cache->get_bit(Point2(x, y)); + return alpha_cache->get_bit(x, y); } return true; @@ -561,7 +561,7 @@ bool PortableCompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { x = CLAMP(x, 0, aw); y = CLAMP(y, 0, ah); - return alpha_cache->get_bit(Point2(x, y)); + return alpha_cache->get_bit(x, y); } return true; @@ -1017,7 +1017,7 @@ bool CompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { x = CLAMP(x, 0, aw); y = CLAMP(y, 0, ah); - return alpha_cache->get_bit(Point2(x, y)); + return alpha_cache->get_bit(x, y); } return true; diff --git a/scene/resources/world_2d.cpp b/scene/resources/world_2d.cpp index 4dfbe5f079..75deb1e60b 100644 --- a/scene/resources/world_2d.cpp +++ b/scene/resources/world_2d.cpp @@ -85,6 +85,7 @@ World2D::World2D() { NavigationServer2D::get_singleton()->map_set_active(navigation_map, true); NavigationServer2D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/2d/default_cell_size", 1)); NavigationServer2D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/2d/default_edge_connection_margin", 1)); + NavigationServer2D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_DEF("navigation/2d/default_link_connection_radius", 4)); } World2D::~World2D() { diff --git a/scene/resources/world_3d.cpp b/scene/resources/world_3d.cpp index 945b6af614..ae8c9a182f 100644 --- a/scene/resources/world_3d.cpp +++ b/scene/resources/world_3d.cpp @@ -153,6 +153,7 @@ World3D::World3D() { NavigationServer3D::get_singleton()->map_set_active(navigation_map, true); NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/3d/default_cell_size", 0.25)); NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 0.25)); + NavigationServer3D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_DEF("navigation/3d/default_link_connection_radius", 1.0)); } World3D::~World3D() { diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index 0f73df8894..cec8b95225 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -53,6 +53,12 @@ NavigationServer2D *NavigationServer2D::singleton = nullptr; return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ } +#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0) \ + NavigationServer2D::FUNC_NAME(T_0 D_0) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \ + } + #define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ const { \ @@ -190,6 +196,22 @@ Color NavigationServer2D::get_debug_navigation_geometry_face_disabled_color() co return NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_disabled_color(); } +void NavigationServer2D::set_debug_navigation_link_connection_color(const Color &p_color) { + NavigationServer3D::get_singleton_mut()->set_debug_navigation_link_connection_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_link_connection_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_color(); +} + +void NavigationServer2D::set_debug_navigation_link_connection_disabled_color(const Color &p_color) { + NavigationServer3D::get_singleton_mut()->set_debug_navigation_link_connection_disabled_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_link_connection_disabled_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_disabled_color(); +} + void NavigationServer2D::set_debug_navigation_enable_edge_connections(const bool p_value) { NavigationServer3D::get_singleton_mut()->set_debug_navigation_enable_edge_connections(p_value); } @@ -209,10 +231,13 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &NavigationServer2D::map_get_cell_size); ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer2D::map_set_edge_connection_margin); ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &NavigationServer2D::map_get_edge_connection_margin); + ClassDB::bind_method(D_METHOD("map_set_link_connection_radius", "map", "radius"), &NavigationServer2D::map_set_link_connection_radius); + ClassDB::bind_method(D_METHOD("map_get_link_connection_radius", "map"), &NavigationServer2D::map_get_link_connection_radius); ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &NavigationServer2D::map_get_path, DEFVAL(1)); ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &NavigationServer2D::map_get_closest_point); ClassDB::bind_method(D_METHOD("map_get_closest_point_owner", "map", "to_point"), &NavigationServer2D::map_get_closest_point_owner); + ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer2D::map_get_links); ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions); ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer2D::map_get_agents); @@ -234,6 +259,22 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_start); ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end); + ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create); + ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map); + ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer2D::link_get_map); + ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer2D::link_set_bidirectional); + ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer2D::link_is_bidirectional); + ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer2D::link_set_navigation_layers); + ClassDB::bind_method(D_METHOD("link_get_navigation_layers", "link"), &NavigationServer2D::link_get_navigation_layers); + ClassDB::bind_method(D_METHOD("link_set_start_location", "link", "location"), &NavigationServer2D::link_set_start_location); + ClassDB::bind_method(D_METHOD("link_get_start_location", "link"), &NavigationServer2D::link_get_start_location); + ClassDB::bind_method(D_METHOD("link_set_end_location", "link", "location"), &NavigationServer2D::link_set_end_location); + ClassDB::bind_method(D_METHOD("link_get_end_location", "link"), &NavigationServer2D::link_get_end_location); + ClassDB::bind_method(D_METHOD("link_set_enter_cost", "link", "enter_cost"), &NavigationServer2D::link_set_enter_cost); + ClassDB::bind_method(D_METHOD("link_get_enter_cost", "link"), &NavigationServer2D::link_get_enter_cost); + ClassDB::bind_method(D_METHOD("link_set_travel_cost", "link", "travel_cost"), &NavigationServer2D::link_set_travel_cost); + ClassDB::bind_method(D_METHOD("link_get_travel_cost", "link"), &NavigationServer2D::link_get_travel_cost); + ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer2D::agent_create); ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer2D::agent_set_map); ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer2D::agent_get_map); @@ -265,6 +306,8 @@ NavigationServer2D::~NavigationServer2D() { TypedArray<RID> FORWARD_0_C(get_maps); +TypedArray<RID> FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid); + TypedArray<RID> FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid); TypedArray<RID> FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid); @@ -289,6 +332,9 @@ real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid); void FORWARD_2_C(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real); real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid); +void FORWARD_2_C(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid); + Vector<Vector2> FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32); Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); @@ -315,6 +361,23 @@ int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid); Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); +RID FORWARD_0_C(link_create); + +void FORWARD_2_C(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid); +void FORWARD_2_C(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid); +void FORWARD_2_C(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); +uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid); +void FORWARD_2_C(link_set_start_location, RID, p_link, Vector2, p_location, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_location, RID, p_link, rid_to_rid); +void FORWARD_2_C(link_set_end_location, RID, p_link, Vector2, p_location, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_location, RID, p_link, rid_to_rid); +void FORWARD_2_C(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid); +void FORWARD_2_C(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid); + RID NavigationServer2D::agent_create() const { RID agent = NavigationServer3D::get_singleton()->agent_create(); NavigationServer3D::get_singleton()->agent_set_ignore_y(agent, true); diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h index 5e96466d66..b2ea4c28a0 100644 --- a/servers/navigation_server_2d.h +++ b/servers/navigation_server_2d.h @@ -76,12 +76,19 @@ public: /// Returns the edge connection margin of this map. virtual real_t map_get_edge_connection_margin(RID p_map) const; + /// Set the map link connection radius used to attach links to the nav mesh. + virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) const; + + /// Returns the link connection radius of this map. + virtual real_t map_get_link_connection_radius(RID p_map) const; + /// Returns the navigation path to reach the destination from the origin. virtual Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const; virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const; virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const; + virtual TypedArray<RID> map_get_links(RID p_map) const; virtual TypedArray<RID> map_get_regions(RID p_map) const; virtual TypedArray<RID> map_get_agents(RID p_map) const; @@ -119,6 +126,37 @@ public: virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const; virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const; + /// Creates a new link between locations in the nav map. + virtual RID link_create() const; + + /// Set the map of this link. + virtual void link_set_map(RID p_link, RID p_map) const; + virtual RID link_get_map(RID p_link) const; + + /// Set whether this link travels in both directions. + virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) const; + virtual bool link_is_bidirectional(RID p_link) const; + + /// Set the link's layers. + virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) const; + virtual uint32_t link_get_navigation_layers(RID p_link) const; + + /// Set the start location of the link. + virtual void link_set_start_location(RID p_link, Vector2 p_location) const; + virtual Vector2 link_get_start_location(RID p_link) const; + + /// Set the end location of the link. + virtual void link_set_end_location(RID p_link, Vector2 p_location) const; + virtual Vector2 link_get_end_location(RID p_link) const; + + /// Set the enter cost of the link. + virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) const; + virtual real_t link_get_enter_cost(RID p_link) const; + + /// Set the travel cost of the link. + virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) const; + virtual real_t link_get_travel_cost(RID p_link) const; + /// Creates the agent. virtual RID agent_create() const; @@ -198,6 +236,12 @@ public: void set_debug_navigation_geometry_face_disabled_color(const Color &p_color); Color get_debug_navigation_geometry_face_disabled_color() const; + void set_debug_navigation_link_connection_color(const Color &p_color); + Color get_debug_navigation_link_connection_color() const; + + void set_debug_navigation_link_connection_disabled_color(const Color &p_color); + Color get_debug_navigation_link_connection_disabled_color() const; + void set_debug_navigation_enable_edge_connections(const bool p_value); bool get_debug_navigation_enable_edge_connections() const; #endif // DEBUG_ENABLED diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index 466b74bc64..bc0602e1df 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -48,12 +48,15 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &NavigationServer3D::map_get_cell_size); ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer3D::map_set_edge_connection_margin); ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &NavigationServer3D::map_get_edge_connection_margin); + ClassDB::bind_method(D_METHOD("map_set_link_connection_radius", "map", "radius"), &NavigationServer3D::map_set_link_connection_radius); + ClassDB::bind_method(D_METHOD("map_get_link_connection_radius", "map"), &NavigationServer3D::map_get_link_connection_radius); ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &NavigationServer3D::map_get_path, DEFVAL(1)); ClassDB::bind_method(D_METHOD("map_get_closest_point_to_segment", "map", "start", "end", "use_collision"), &NavigationServer3D::map_get_closest_point_to_segment, DEFVAL(false)); ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &NavigationServer3D::map_get_closest_point); ClassDB::bind_method(D_METHOD("map_get_closest_point_normal", "map", "to_point"), &NavigationServer3D::map_get_closest_point_normal); ClassDB::bind_method(D_METHOD("map_get_closest_point_owner", "map", "to_point"), &NavigationServer3D::map_get_closest_point_owner); + ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer3D::map_get_links); ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer3D::map_get_regions); ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer3D::map_get_agents); @@ -76,6 +79,22 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer3D::region_get_connection_pathway_start); ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer3D::region_get_connection_pathway_end); + ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer3D::link_create); + ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer3D::link_set_map); + ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer3D::link_get_map); + ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer3D::link_set_bidirectional); + ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer3D::link_is_bidirectional); + ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer3D::link_set_navigation_layers); + ClassDB::bind_method(D_METHOD("link_get_navigation_layers", "link"), &NavigationServer3D::link_get_navigation_layers); + ClassDB::bind_method(D_METHOD("link_set_start_location", "link", "location"), &NavigationServer3D::link_set_start_location); + ClassDB::bind_method(D_METHOD("link_get_start_location", "link"), &NavigationServer3D::link_get_start_location); + ClassDB::bind_method(D_METHOD("link_set_end_location", "link", "location"), &NavigationServer3D::link_set_end_location); + ClassDB::bind_method(D_METHOD("link_get_end_location", "link"), &NavigationServer3D::link_get_end_location); + ClassDB::bind_method(D_METHOD("link_set_enter_cost", "link", "enter_cost"), &NavigationServer3D::link_set_enter_cost); + ClassDB::bind_method(D_METHOD("link_get_enter_cost", "link"), &NavigationServer3D::link_get_enter_cost); + ClassDB::bind_method(D_METHOD("link_set_travel_cost", "link", "travel_cost"), &NavigationServer3D::link_set_travel_cost); + ClassDB::bind_method(D_METHOD("link_get_travel_cost", "link"), &NavigationServer3D::link_get_travel_cost); + ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer3D::agent_create); ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer3D::agent_set_map); ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer3D::agent_get_map); @@ -118,11 +137,16 @@ NavigationServer3D::NavigationServer3D() { debug_navigation_geometry_face_color = GLOBAL_DEF("debug/shapes/navigation/geometry_face_color", Color(0.5, 1.0, 1.0, 0.4)); debug_navigation_geometry_edge_disabled_color = GLOBAL_DEF("debug/shapes/navigation/geometry_edge_disabled_color", Color(0.5, 0.5, 0.5, 1.0)); debug_navigation_geometry_face_disabled_color = GLOBAL_DEF("debug/shapes/navigation/geometry_face_disabled_color", Color(0.5, 0.5, 0.5, 0.4)); + debug_navigation_link_connection_color = GLOBAL_DEF("debug/shapes/navigation/link_connection_color", Color(1.0, 0.5, 1.0, 1.0)); + debug_navigation_link_connection_disabled_color = GLOBAL_DEF("debug/shapes/navigation/link_connection_disabled_color", Color(0.5, 0.5, 0.5, 1.0)); + debug_navigation_enable_edge_connections = GLOBAL_DEF("debug/shapes/navigation/enable_edge_connections", true); debug_navigation_enable_edge_connections_xray = GLOBAL_DEF("debug/shapes/navigation/enable_edge_connections_xray", true); debug_navigation_enable_edge_lines = GLOBAL_DEF("debug/shapes/navigation/enable_edge_lines", true); debug_navigation_enable_edge_lines_xray = GLOBAL_DEF("debug/shapes/navigation/enable_edge_lines_xray", true); debug_navigation_enable_geometry_face_random_color = GLOBAL_DEF("debug/shapes/navigation/enable_geometry_face_random_color", true); + debug_navigation_enable_link_connections = GLOBAL_DEF("debug/shapes/navigation/enable_link_connections", true); + debug_navigation_enable_link_connections_xray = GLOBAL_DEF("debug/shapes/navigation/enable_link_connections_xray", true); if (Engine::get_singleton()->is_editor_hint()) { // enable NavigationServer3D when in Editor or else navmesh edge connections are invisible @@ -261,6 +285,40 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_edge_connection return debug_navigation_edge_connections_material; } +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_link_connections_material() { + if (debug_navigation_link_connections_material.is_valid()) { + return debug_navigation_link_connections_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_albedo(debug_navigation_link_connection_color); + if (debug_navigation_enable_link_connections_xray) { + material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); + } + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MAX - 2); + + debug_navigation_link_connections_material = material; + return debug_navigation_link_connections_material; +} + +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_link_connections_disabled_material() { + if (debug_navigation_link_connections_disabled_material.is_valid()) { + return debug_navigation_link_connections_disabled_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_albedo(debug_navigation_link_connection_disabled_color); + if (debug_navigation_enable_link_connections_xray) { + material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); + } + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MAX - 2); + + debug_navigation_link_connections_disabled_material = material; + return debug_navigation_link_connections_disabled_material; +} + void NavigationServer3D::set_debug_navigation_edge_connection_color(const Color &p_color) { debug_navigation_edge_connection_color = p_color; if (debug_navigation_edge_connections_material.is_valid()) { @@ -316,6 +374,28 @@ Color NavigationServer3D::get_debug_navigation_geometry_face_disabled_color() co return debug_navigation_geometry_face_disabled_color; } +void NavigationServer3D::set_debug_navigation_link_connection_color(const Color &p_color) { + debug_navigation_link_connection_color = p_color; + if (debug_navigation_link_connections_material.is_valid()) { + debug_navigation_link_connections_material->set_albedo(debug_navigation_link_connection_color); + } +} + +Color NavigationServer3D::get_debug_navigation_link_connection_color() const { + return debug_navigation_link_connection_color; +} + +void NavigationServer3D::set_debug_navigation_link_connection_disabled_color(const Color &p_color) { + debug_navigation_link_connection_disabled_color = p_color; + if (debug_navigation_link_connections_disabled_material.is_valid()) { + debug_navigation_link_connections_disabled_material->set_albedo(debug_navigation_link_connection_disabled_color); + } +} + +Color NavigationServer3D::get_debug_navigation_link_connection_disabled_color() const { + return debug_navigation_link_connection_disabled_color; +} + void NavigationServer3D::set_debug_navigation_enable_edge_connections(const bool p_value) { debug_navigation_enable_edge_connections = p_value; debug_dirty = true; @@ -368,6 +448,27 @@ bool NavigationServer3D::get_debug_navigation_enable_geometry_face_random_color( return debug_navigation_enable_geometry_face_random_color; } +void NavigationServer3D::set_debug_navigation_enable_link_connections(const bool p_value) { + debug_navigation_enable_link_connections = p_value; + debug_dirty = true; + call_deferred("_emit_navigation_debug_changed_signal"); +} + +bool NavigationServer3D::get_debug_navigation_enable_link_connections() const { + return debug_navigation_enable_link_connections; +} + +void NavigationServer3D::set_debug_navigation_enable_link_connections_xray(const bool p_value) { + debug_navigation_enable_link_connections_xray = p_value; + if (debug_navigation_link_connections_material.is_valid()) { + debug_navigation_link_connections_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, debug_navigation_enable_link_connections_xray); + } +} + +bool NavigationServer3D::get_debug_navigation_enable_link_connections_xray() const { + return debug_navigation_enable_link_connections_xray; +} + void NavigationServer3D::set_debug_enabled(bool p_enabled) { if (debug_enabled != p_enabled) { debug_dirty = true; diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index 3213da3d84..02770794c6 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -85,6 +85,12 @@ public: /// Returns the edge connection margin of this map. virtual real_t map_get_edge_connection_margin(RID p_map) const = 0; + /// Set the map link connection radius used to attach links to the nav mesh. + virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) const = 0; + + /// Returns the link connection radius of this map. + virtual real_t map_get_link_connection_radius(RID p_map) const = 0; + /// Returns the navigation path to reach the destination from the origin. virtual Vector<Vector3> map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const = 0; @@ -93,6 +99,7 @@ public: virtual Vector3 map_get_closest_point_normal(RID p_map, const Vector3 &p_point) const = 0; virtual RID map_get_closest_point_owner(RID p_map, const Vector3 &p_point) const = 0; + virtual TypedArray<RID> map_get_links(RID p_map) const = 0; virtual TypedArray<RID> map_get_regions(RID p_map) const = 0; virtual TypedArray<RID> map_get_agents(RID p_map) const = 0; @@ -133,6 +140,37 @@ public: virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const = 0; virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const = 0; + /// Creates a new link between locations in the nav map. + virtual RID link_create() const = 0; + + /// Set the map of this link. + virtual void link_set_map(RID p_link, RID p_map) const = 0; + virtual RID link_get_map(RID p_link) const = 0; + + /// Set whether this link travels in both directions. + virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) const = 0; + virtual bool link_is_bidirectional(RID p_link) const = 0; + + /// Set the link's layers. + virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) const = 0; + virtual uint32_t link_get_navigation_layers(RID p_link) const = 0; + + /// Set the start location of the link. + virtual void link_set_start_location(RID p_link, Vector3 p_location) const = 0; + virtual Vector3 link_get_start_location(RID p_link) const = 0; + + /// Set the end location of the link. + virtual void link_set_end_location(RID p_link, Vector3 p_location) const = 0; + virtual Vector3 link_get_end_location(RID p_link) const = 0; + + /// Set the enter cost of the link. + virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) const = 0; + virtual real_t link_get_enter_cost(RID p_link) const = 0; + + /// Set the travel cost of the link. + virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) const = 0; + virtual real_t link_get_travel_cost(RID p_link) const = 0; + /// Creates the agent. virtual RID agent_create() const = 0; @@ -209,29 +247,38 @@ public: virtual ~NavigationServer3D(); #ifdef DEBUG_ENABLED +private: bool debug_enabled = false; bool debug_dirty = true; void _emit_navigation_debug_changed_signal(); - void set_debug_enabled(bool p_enabled); - bool get_debug_enabled() const; - Color debug_navigation_edge_connection_color = Color(1.0, 0.0, 1.0, 1.0); Color debug_navigation_geometry_edge_color = Color(0.5, 1.0, 1.0, 1.0); Color debug_navigation_geometry_face_color = Color(0.5, 1.0, 1.0, 0.4); Color debug_navigation_geometry_edge_disabled_color = Color(0.5, 0.5, 0.5, 1.0); Color debug_navigation_geometry_face_disabled_color = Color(0.5, 0.5, 0.5, 0.4); + Color debug_navigation_link_connection_color = Color(1.0, 0.5, 1.0, 1.0); + Color debug_navigation_link_connection_disabled_color = Color(0.5, 0.5, 0.5, 1.0); + bool debug_navigation_enable_edge_connections = true; bool debug_navigation_enable_edge_connections_xray = true; bool debug_navigation_enable_edge_lines = true; bool debug_navigation_enable_edge_lines_xray = true; bool debug_navigation_enable_geometry_face_random_color = true; + bool debug_navigation_enable_link_connections = true; + bool debug_navigation_enable_link_connections_xray = true; Ref<StandardMaterial3D> debug_navigation_geometry_edge_material; Ref<StandardMaterial3D> debug_navigation_geometry_face_material; Ref<StandardMaterial3D> debug_navigation_geometry_edge_disabled_material; Ref<StandardMaterial3D> debug_navigation_geometry_face_disabled_material; Ref<StandardMaterial3D> debug_navigation_edge_connections_material; + Ref<StandardMaterial3D> debug_navigation_link_connections_material; + Ref<StandardMaterial3D> debug_navigation_link_connections_disabled_material; + +public: + void set_debug_enabled(bool p_enabled); + bool get_debug_enabled() const; void set_debug_navigation_edge_connection_color(const Color &p_color); Color get_debug_navigation_edge_connection_color() const; @@ -248,6 +295,12 @@ public: void set_debug_navigation_geometry_face_disabled_color(const Color &p_color); Color get_debug_navigation_geometry_face_disabled_color() const; + void set_debug_navigation_link_connection_color(const Color &p_color); + Color get_debug_navigation_link_connection_color() const; + + void set_debug_navigation_link_connection_disabled_color(const Color &p_color); + Color get_debug_navigation_link_connection_disabled_color() const; + void set_debug_navigation_enable_edge_connections(const bool p_value); bool get_debug_navigation_enable_edge_connections() const; @@ -263,11 +316,19 @@ public: void set_debug_navigation_enable_geometry_face_random_color(const bool p_value); bool get_debug_navigation_enable_geometry_face_random_color() const; + void set_debug_navigation_enable_link_connections(const bool p_value); + bool get_debug_navigation_enable_link_connections() const; + + void set_debug_navigation_enable_link_connections_xray(const bool p_value); + bool get_debug_navigation_enable_link_connections_xray() const; + Ref<StandardMaterial3D> get_debug_navigation_geometry_face_material(); Ref<StandardMaterial3D> get_debug_navigation_geometry_edge_material(); Ref<StandardMaterial3D> get_debug_navigation_geometry_face_disabled_material(); Ref<StandardMaterial3D> get_debug_navigation_geometry_edge_disabled_material(); Ref<StandardMaterial3D> get_debug_navigation_edge_connections_material(); + Ref<StandardMaterial3D> get_debug_navigation_link_connections_material(); + Ref<StandardMaterial3D> get_debug_navigation_link_connections_disabled_material(); #endif // DEBUG_ENABLED }; diff --git a/tests/scene/test_bit_map.h b/tests/scene/test_bit_map.h new file mode 100644 index 0000000000..53afdc38f7 --- /dev/null +++ b/tests/scene/test_bit_map.h @@ -0,0 +1,445 @@ +/*************************************************************************/ +/* test_bit_map.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_BIT_MAP_H +#define TEST_BIT_MAP_H + +#include "core/os/memory.h" +#include "scene/resources/bit_map.h" +#include "tests/test_macros.h" + +namespace TestBitmap { + +void reset_bit_map(BitMap &p_bm) { + Size2i size = p_bm.get_size(); + p_bm.set_bit_rect(Rect2i(0, 0, size.width, size.height), false); +} + +TEST_CASE("[BitMap] Create bit map") { + Size2i dim{ 256, 512 }; + BitMap bit_map{}; + bit_map.create(dim); + CHECK(bit_map.get_size() == Size2i(256, 512)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "This will go through the entire bitmask inside of bitmap, thus hopefully checking if the bitmask was correctly set up."); + + dim = Size2i(0, 256); + bit_map.create(dim); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is invalid."); + + dim = Size2i(512, 0); + bit_map.create(dim); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is invalid."); + + dim = Size2i(46341, 46341); + bit_map.create(dim); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is too large (46341*46341=2147488281)."); +} + +TEST_CASE("[BitMap] Create bit map from image alpha") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + bit_map.create(dim); + + const Ref<Image> null_img = nullptr; + bit_map.create_from_image_alpha(null_img); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because bitmap creation from a nullptr should fail."); + + Ref<Image> empty_img; + empty_img.instantiate(); + bit_map.create_from_image_alpha(empty_img); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because bitmap creation from an empty image should fail."); + + Ref<Image> wrong_format_img; + wrong_format_img.instantiate(); + wrong_format_img->create(3, 3, false, Image::Format::FORMAT_DXT1); + bit_map.create_from_image_alpha(wrong_format_img); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because converting from a compressed image should fail."); + + Ref<Image> img; + img.instantiate(); + img->create(3, 3, false, Image::Format::FORMAT_RGBA8); + img->set_pixel(0, 0, Color(0, 0, 0, 0)); + img->set_pixel(0, 1, Color(0, 0, 0, 0.09f)); + img->set_pixel(0, 2, Color(0, 0, 0, 0.25f)); + img->set_pixel(1, 0, Color(0, 0, 0, 0.5f)); + img->set_pixel(1, 1, Color(0, 0, 0, 0.75f)); + img->set_pixel(1, 2, Color(0, 0, 0, 0.99f)); + img->set_pixel(2, 0, Color(0, 0, 0, 1.f)); + + // Check different threshold values. + bit_map.create_from_image_alpha(img); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 5, "There are 5 values in the image that are smaller than the default threshold of 0.1."); + + bit_map.create_from_image_alpha(img, 0.08f); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 6, "There are 6 values in the image that are smaller than the threshold of 0.08."); + + bit_map.create_from_image_alpha(img, 1); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "There are no values in the image that are smaller than the threshold of 1, there is one value equal to 1, but we check for inequality only."); +} + +TEST_CASE("[BitMap] Set bit") { + Size2i dim{ 256, 256 }; + BitMap bit_map{}; + + // Setting a point before a bit map is created should not crash, because there are checks to see if we are out of bounds. + bit_map.set_bitv(Point2i(128, 128), true); + + bit_map.create(dim); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "All values should be initialized to false."); + bit_map.set_bitv(Point2i(128, 128), true); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 1, "One bit should be set to true."); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == true, "The bit at (128,128) should be set to true"); + + bit_map.set_bitv(Point2i(128, 128), false); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "The bit should now be set to false again"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == false, "The bit at (128,128) should now be set to false again"); + + bit_map.create(dim); + bit_map.set_bitv(Point2i(512, 512), true); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Nothing should change as we were trying to edit a bit outside of the correct range."); +} + +TEST_CASE("[BitMap] Get bit") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + + CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == false, "Trying to access a bit outside of the BitMap's range should always return false"); + + bit_map.create(dim); + CHECK(bit_map.get_bitv(Point2i(128, 128)) == false); + + bit_map.set_bit_rect(Rect2i(-1, -1, 257, 257), true); + + // Checking that range is [0, 256). + CHECK(bit_map.get_bitv(Point2i(-1, 0)) == false); + CHECK(bit_map.get_bitv(Point2i(0, 0)) == true); + CHECK(bit_map.get_bitv(Point2i(128, 128)) == true); + CHECK(bit_map.get_bitv(Point2i(255, 255)) == true); + CHECK(bit_map.get_bitv(Point2i(256, 256)) == false); + CHECK(bit_map.get_bitv(Point2i(257, 257)) == false); +} + +TEST_CASE("[BitMap] Set bit rect") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + + // Although we have not setup the BitMap yet, this should not crash because we get an empty intersection inside of the method. + bit_map.set_bit_rect(Rect2i{ 0, 0, 128, 128 }, true); + + bit_map.create(dim); + CHECK(bit_map.get_true_bit_count() == 0); + + bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, true); + CHECK(bit_map.get_true_bit_count() == 65536); + + reset_bit_map(bit_map); + + // Checking out of bounds handling. + bit_map.set_bit_rect(Rect2i{ 128, 128, 256, 256 }, true); + CHECK(bit_map.get_true_bit_count() == 16384); + + reset_bit_map(bit_map); + + bit_map.set_bit_rect(Rect2i{ -128, -128, 256, 256 }, true); + CHECK(bit_map.get_true_bit_count() == 16384); + + reset_bit_map(bit_map); + + bit_map.set_bit_rect(Rect2i{ -128, -128, 512, 512 }, true); + CHECK(bit_map.get_true_bit_count() == 65536); +} + +TEST_CASE("[BitMap] Get true bit count") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + + CHECK(bit_map.get_true_bit_count() == 0); + + bit_map.create(dim); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Unitialized bit map should have no true bits"); + bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, true); + CHECK(bit_map.get_true_bit_count() == 65536); + bit_map.set_bitv(Point2i{ 0, 0 }, false); + CHECK(bit_map.get_true_bit_count() == 65535); + bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, false); + CHECK(bit_map.get_true_bit_count() == 0); +} + +TEST_CASE("[BitMap] Get size") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + + CHECK_MESSAGE(bit_map.get_size() == Size2i(0, 0), "Unitialized bit map should have a size of 0x0"); + + bit_map.create(dim); + CHECK(bit_map.get_size() == Size2i(256, 256)); + + bit_map.create(Size2i(-1, 0)); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Invalid size should not be accepted by create"); + + bit_map.create(Size2i(256, 128)); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 128), "Bitmap should have updated size"); +} + +TEST_CASE("[BitMap] Resize") { + const Size2i dim{ 128, 128 }; + BitMap bit_map{}; + + bit_map.resize(dim); + CHECK(bit_map.get_size() == dim); + + bit_map.create(dim); + bit_map.set_bit_rect(Rect2i(0, 0, 10, 10), true); + bit_map.set_bit_rect(Rect2i(118, 118, 10, 10), true); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 200, "There should be 100 bits in the top left corner, and 100 bits in the bottom right corner"); + bit_map.resize(Size2i(64, 64)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 50, "There should be 25 bits in the top left corner, and 25 bits in the bottom right corner"); + + bit_map.create(dim); + bit_map.resize(Size2i(-1, 128)); + CHECK_MESSAGE(bit_map.get_size() == Size2i(128, 128), "When an invalid size is given the bit map will keep its size"); + + bit_map.create(dim); + bit_map.set_bit_rect(Rect2i(0, 0, 10, 10), true); + bit_map.set_bit_rect(Rect2i(118, 118, 10, 10), true); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 200, "There should be 100 bits in the top left corner, and 100 bits in the bottom right corner"); + bit_map.resize(Size2i(256, 256)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 800, "There should still be 100 bits in the bottom right corner, and all new bits should be initialized to false"); + CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "The bitmap should now be 256x256"); +} + +TEST_CASE("[BitMap] Grow and shrink mask") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + bit_map.grow_mask(100, Rect2i(0, 0, 128, 128)); // Check if method does not crash when working with an uninitialised bit map. + CHECK_MESSAGE(bit_map.get_size() == Size2i(0, 0), "Size should still be equal to 0x0"); + + bit_map.create(dim); + + bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true); + + CHECK_MESSAGE(bit_map.get_true_bit_count() == 4096, "Creating a square of 64x64 should be 4096 bits"); + bit_map.grow_mask(0, Rect2i(0, 0, 256, 256)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 4096, "Growing with size of 0 should not change any bits"); + + reset_bit_map(bit_map); + + bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true); + + CHECK_MESSAGE(bit_map.get_bitv(Point2i(95, 128)) == false, "Bits just outside of the square should not be set"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(160, 128)) == false, "Bits just outside of the square should not be set"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 95)) == false, "Bits just outside of the square should not be set"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 160)) == false, "Bits just outside of the square should not be set"); + bit_map.grow_mask(1, Rect2i(0, 0, 256, 256)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 4352, "We should have 4*64 (perimeter of square) more bits set to true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(95, 128)) == true, "Bits that were just outside of the square should now be set to true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(160, 128)) == true, "Bits that were just outside of the square should now be set to true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 95)) == true, "Bits that were just outside of the square should now be set to true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 160)) == true, "Bits that were just outside of the square should now be set to true"); + + reset_bit_map(bit_map); + + bit_map.set_bit_rect(Rect2i(127, 127, 1, 1), true); + + CHECK(bit_map.get_true_bit_count() == 1); + bit_map.grow_mask(32, Rect2i(0, 0, 256, 256)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 3209, "Creates a circle around the initial bit with a radius of 32 bits. Any bit that has a distance within this radius will be set to true"); + + reset_bit_map(bit_map); + + bit_map.set_bit_rect(Rect2i(127, 127, 1, 1), true); + for (int i = 0; i < 32; i++) { + bit_map.grow_mask(1, Rect2i(0, 0, 256, 256)); + } + CHECK_MESSAGE(bit_map.get_true_bit_count() == 2113, "Creates a diamond around the initial bit with diagonals that are 65 bits long."); + + reset_bit_map(bit_map); + + bit_map.set_bit_rect(Rect2i(123, 123, 10, 10), true); + + CHECK(bit_map.get_true_bit_count() == 100); + bit_map.grow_mask(-11, Rect2i(0, 0, 256, 256)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Shrinking by more than the width of the square should totally remove it."); + + reset_bit_map(bit_map); + bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true); + + CHECK_MESSAGE(bit_map.get_bitv(Point2i(96, 129)) == true, "Bits on the edge of the square should be true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(159, 129)) == true, "Bits on the edge of the square should be true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 96)) == true, "Bits on the edge of the square should be true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 159)) == true, "Bits on the edge of the square should be true"); + bit_map.grow_mask(-1, Rect2i(0, 0, 256, 256)); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 3844, "Shrinking by 1 should set 4*63=252 bits to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(96, 129)) == false, "Bits that were on the edge of the square should now be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(159, 129)) == false, "Bits that were on the edge of the square should now be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 96)) == false, "Bits that were on the edge of the square should now be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 159)) == false, "Bits that were on the edge of the square should now be set to false"); + + reset_bit_map(bit_map); + + bit_map.set_bit_rect(Rect2i(125, 125, 1, 6), true); + bit_map.set_bit_rect(Rect2i(130, 125, 1, 6), true); + bit_map.set_bit_rect(Rect2i(125, 130, 6, 1), true); + + CHECK(bit_map.get_true_bit_count() == 16); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 131)) == false, "Bits that are on the edge of the shape should be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 131)) == false, "Bits that are on the edge of the shape should be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 124)) == false, "Bits that are on the edge of the shape should be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(130, 124)) == false, "Bits that are on the edge of the shape should be set to false"); + bit_map.grow_mask(1, Rect2i(0, 0, 256, 256)); + CHECK(bit_map.get_true_bit_count() == 48); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 131)) == true, "Bits that were on the edge of the shape should now be set to true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 130)) == true, "Bits that were on the edge of the shape should now be set to true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 124)) == true, "Bits that were on the edge of the shape should now be set to true"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(130, 124)) == true, "Bits that were on the edge of the shape should now be set to true"); + + CHECK_MESSAGE(bit_map.get_bitv(Point2i(124, 124)) == false, "Bits that are on the edge of the shape should be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(126, 124)) == false, "Bits that are on the edge of the shape should be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(124, 131)) == false, "Bits that are on the edge of the shape should be set to false"); + CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 131)) == false, "Bits that are on the edge of the shape should be set to false"); +} + +TEST_CASE("[BitMap] Blit") { + Point2i blit_pos{ 128, 128 }; + Point2i bit_map_size{ 256, 256 }; + Point2i blit_size{ 32, 32 }; + + BitMap bit_map{}; + Ref<BitMap> blit_bit_map{}; + + // Testing null reference to blit bit map. + bit_map.blit(blit_pos, blit_bit_map); + + blit_bit_map.instantiate(); + + // Testing if uninitialised blit bit map and uninitialised bit map does not crash + bit_map.blit(blit_pos, blit_bit_map); + + // Testing if uninitialised bit map does not crash + blit_bit_map->create(blit_size); + bit_map.blit(blit_pos, blit_bit_map); + + // Testing if uninitialised bit map does not crash + blit_bit_map.unref(); + blit_bit_map.instantiate(); + CHECK_MESSAGE(blit_bit_map->get_size() == Point2i(0, 0), "Size should be cleared by unref and instance calls."); + bit_map.create(bit_map_size); + bit_map.blit(Point2i(128, 128), blit_bit_map); + + // Testing if both initialised does not crash. + blit_bit_map->create(blit_size); + bit_map.blit(blit_pos, blit_bit_map); + + bit_map.set_bit_rect(Rect2i{ 127, 127, 3, 3 }, true); + CHECK(bit_map.get_true_bit_count() == 9); + bit_map.blit(Point2i(112, 112), blit_bit_map); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 9, "No bits should have been changed, as the blit bit map only contains falses"); + + bit_map.create(bit_map_size); + blit_bit_map->create(blit_size); + blit_bit_map->set_bit_rect(Rect2i(15, 15, 3, 3), true); + CHECK(blit_bit_map->get_true_bit_count() == 9); + + CHECK(bit_map.get_true_bit_count() == 0); + bit_map.blit(Point2i(112, 112), blit_bit_map); + CHECK_MESSAGE(bit_map.get_true_bit_count() == 9, "All true bits should have been moved to the bit map"); + for (int x = 127; x < 129; ++x) { + for (int y = 127; y < 129; ++y) { + CHECK_MESSAGE(bit_map.get_bitv(Point2i(x, y)) == true, "All true bits should have been moved to the bit map"); + } + } +} + +TEST_CASE("[BitMap] Convert to image") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + Ref<Image> img; + + img = bit_map.convert_to_image(); + CHECK_MESSAGE(img.is_valid(), "We should receive a valid Image Object even if BitMap is not created yet"); + CHECK_MESSAGE(img->get_format() == Image::FORMAT_L8, "We should receive a valid Image Object even if BitMap is not created yet"); + CHECK_MESSAGE(img->get_size() == (Size2i(0, 0)), "Image should have no width or height, because BitMap has not yet been created"); + + bit_map.create(dim); + img = bit_map.convert_to_image(); + CHECK_MESSAGE(img->get_size() == dim, "Image should have the same dimensions as the BitMap"); + CHECK_MESSAGE(img->get_pixel(0, 0).is_equal_approx(Color(0, 0, 0)), "BitMap is intialized to all 0's, so Image should be all black"); + + reset_bit_map(bit_map); + bit_map.set_bit_rect(Rect2i(0, 0, 128, 128), true); + img = bit_map.convert_to_image(); + CHECK_MESSAGE(img->get_pixel(0, 0).is_equal_approx(Color(1, 1, 1)), "BitMap's top-left quadrant is all 1's, so Image should be white"); + CHECK_MESSAGE(img->get_pixel(256, 256).is_equal_approx(Color(0, 0, 0)), "All other quadrants were 0's, so these should be black"); +} + +TEST_CASE("[BitMap] Clip to polygon") { + const Size2i dim{ 256, 256 }; + BitMap bit_map{}; + Vector<Vector<Vector2>> polygons; + + polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128)); + CHECK_MESSAGE(polygons.size() == 0, "We should have no polygons, because the BitMap was not initialized"); + + bit_map.create(dim); + polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128)); + CHECK_MESSAGE(polygons.size() == 0, "We should have no polygons, because the BitMap was all 0's"); + + reset_bit_map(bit_map); + bit_map.set_bit_rect(Rect2i(0, 0, 64, 64), true); + polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128)); + CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon"); + CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points"); + + reset_bit_map(bit_map); + bit_map.set_bit_rect(Rect2i(0, 0, 32, 32), true); + bit_map.set_bit_rect(Rect2i(64, 64, 32, 32), true); + polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128)); + CHECK_MESSAGE(polygons.size() == 2, "We should have exactly 2 polygons"); + CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points"); + CHECK_MESSAGE(polygons[1].size() == 4, "The polygon should have exactly 4 points"); + + reset_bit_map(bit_map); + bit_map.set_bit_rect(Rect2i(124, 112, 8, 32), true); + bit_map.set_bit_rect(Rect2i(112, 124, 32, 8), true); + polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 256, 256)); + CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon"); + CHECK_MESSAGE(polygons[0].size() == 12, "The polygon should have exactly 12 points"); + + reset_bit_map(bit_map); + bit_map.set_bit_rect(Rect2i(124, 112, 8, 32), true); + bit_map.set_bit_rect(Rect2i(112, 124, 32, 8), true); + polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128)); + CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon"); + CHECK_MESSAGE(polygons[0].size() == 6, "The polygon should have exactly 6 points"); +} + +} // namespace TestBitmap + +#endif // TEST_BIT_MAP_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index a30d11342a..fecfab7459 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -85,6 +85,7 @@ #include "tests/core/variant/test_variant.h" #include "tests/scene/test_animation.h" #include "tests/scene/test_audio_stream_wav.h" +#include "tests/scene/test_bit_map.h" #include "tests/scene/test_code_edit.h" #include "tests/scene/test_curve.h" #include "tests/scene/test_gradient.h" |