diff options
70 files changed, 821 insertions, 314 deletions
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 829e1d8e5b..f158755a85 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -549,6 +549,15 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String return Ref<Resource>(); } + // Handle icons if any are specified. + if (config->has_section("icons")) { + List<String> keys; + config->get_section_keys("icons", &keys); + for (const String &key : keys) { + lib->class_icon_paths[key] = config->get_value("icons", key); + } + } + return lib; } diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 39523a142f..9d946ca7ba 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -67,6 +67,8 @@ protected: static void _bind_methods(); public: + HashMap<String, String> class_icon_paths; + static String get_extension_list_config_file(); static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr); diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 8701e6d77b..63e809bc7c 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -50,6 +50,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String & extension->initialize_library(GDExtension::InitializationLevel(i)); } } + + for (const KeyValue<String, String> &kv : extension->class_icon_paths) { + gdextension_class_icon_paths[kv.key] = kv.value; + } + gdextension_map[p_path] = extension; return LOAD_STATUS_OK; } @@ -74,6 +79,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String extension->deinitialize_library(GDExtension::InitializationLevel(i)); } } + + for (const KeyValue<String, String> &kv : extension->class_icon_paths) { + gdextension_class_icon_paths.erase(kv.key); + } + gdextension_map.erase(p_path); return LOAD_STATUS_OK; } @@ -95,6 +105,19 @@ Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) { return E->value; } +bool GDExtensionManager::class_has_icon_path(const String &p_class) const { + // TODO: Check that the icon belongs to a registered class somehow. + return gdextension_class_icon_paths.has(p_class); +} + +String GDExtensionManager::class_get_icon_path(const String &p_class) const { + // TODO: Check that the icon belongs to a registered class somehow. + if (gdextension_class_icon_paths.has(p_class)) { + return gdextension_class_icon_paths[p_class]; + } + return ""; +} + void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) { ERR_FAIL_COND(int32_t(p_level) - 1 != level); for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) { diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index 456942af0d..3643f043d8 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -38,6 +38,7 @@ class GDExtensionManager : public Object { int32_t level = -1; HashMap<String, Ref<GDExtension>> gdextension_map; + HashMap<String, String> gdextension_class_icon_paths; static void _bind_methods(); @@ -59,6 +60,9 @@ public: Vector<String> get_loaded_extensions() const; Ref<GDExtension> get_extension(const String &p_path); + bool class_has_icon_path(const String &p_class) const; + String class_get_icon_path(const String &p_class) const; + void initialize_extensions(GDExtension::InitializationLevel p_level); void deinitialize_extensions(GDExtension::InitializationLevel p_level); diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml index 9fce014125..ec2447dbbc 100644 --- a/doc/classes/Button.xml +++ b/doc/classes/Button.xml @@ -48,7 +48,7 @@ When this property is enabled, text that is too large to fit the button is clipped, when disabled the Button will always be wide enough to hold the text. </member> <member name="expand_icon" type="bool" setter="set_expand_icon" getter="is_expand_icon" default="false"> - When enabled, the button's icon will expand/shrink to fit the button's size while keeping its aspect. + When enabled, the button's icon will expand/shrink to fit the button's size while keeping its aspect. See also [theme_item icon_max_width]. </member> <member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false"> Flat buttons don't display decoration. @@ -116,6 +116,9 @@ <theme_item name="h_separation" data_type="constant" type="int" default="2"> The horizontal space between [Button]'s icon and text. Negative values will be treated as [code]0[/code] when used. </theme_item> + <theme_item name="icon_max_width" data_type="constant" type="int" default="0"> + The maximum allowed width of the [Button]'s icon. This limit is applied on top of the default size of the icon, or its expanded size if [member expand_icon] is [code]true[/code]. The height is adjusted according to the icon's ratio. + </theme_item> <theme_item name="outline_size" data_type="constant" type="int" default="0"> The size of the text outline. [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index dcc975f24c..5817c45f41 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -42,6 +42,18 @@ The elements in the array returned from this method are displayed as warnings in the Scene dock if the script that overrides it is a [code]tool[/code] script. Returning an empty array produces no warnings. Call [method update_configuration_warnings] when the warnings need to be updated for this node. + [codeblock] + @export var energy = 0: + set(value): + energy = value + update_configuration_warnings() + + func _get_configuration_warnings(): + if energy < 0: + return ["Energy must be 0 or greater."] + else: + return [] + [/codeblock] </description> </method> <method name="_input" qualifiers="virtual"> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 6787b5d20a..6996061bd8 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -202,6 +202,13 @@ Returns the icon of the item at the given [param index]. </description> </method> + <method name="get_item_icon_max_width" qualifiers="const"> + <return type="int" /> + <param index="0" name="index" type="int" /> + <description> + Returns the maximum allowed width of the icon for the item at the given [param index]. + </description> + </method> <method name="get_item_id" qualifiers="const"> <return type="int" /> <param index="0" name="index" type="int" /> @@ -397,6 +404,14 @@ Replaces the [Texture2D] icon of the item at the given [param index]. </description> </method> + <method name="set_item_icon_max_width"> + <return type="void" /> + <param index="0" name="index" type="int" /> + <param index="1" name="width" type="int" /> + <description> + Sets the maximum allowed width of the icon for the item at the given [param index]. This limit is applied on top of the default size of the icon and on top of [theme_item icon_max_width]. The height is adjusted according to the icon's ratio. + </description> + </method> <method name="set_item_id"> <return type="void" /> <param index="0" name="index" type="int" /> @@ -573,6 +588,9 @@ <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between the item's elements. </theme_item> + <theme_item name="icon_max_width" data_type="constant" type="int" default="0"> + The maximum allowed width of the item's icon. This limit is applied on top of the default size of the icon, but before the value set with [method set_item_icon_max_width]. The height is adjusted according to the icon's ratio. + </theme_item> <theme_item name="indent" data_type="constant" type="int" default="10"> Width of the single indentation level. </theme_item> diff --git a/doc/classes/TabBar.xml b/doc/classes/TabBar.xml index ae6d589339..7211fc2137 100644 --- a/doc/classes/TabBar.xml +++ b/doc/classes/TabBar.xml @@ -46,14 +46,21 @@ <return type="Texture2D" /> <param index="0" name="tab_idx" type="int" /> <description> - Returns the [Texture2D] for the right button of the tab at index [param tab_idx] or [code]null[/code] if the button has no [Texture2D]. + Returns the icon for the right button of the tab at index [param tab_idx] or [code]null[/code] if the right button has no icon. </description> </method> <method name="get_tab_icon" qualifiers="const"> <return type="Texture2D" /> <param index="0" name="tab_idx" type="int" /> <description> - Returns the [Texture2D] for the tab at index [param tab_idx] or [code]null[/code] if the tab has no [Texture2D]. + Returns the icon for the tab at index [param tab_idx] or [code]null[/code] if the tab has no icon. + </description> + </method> + <method name="get_tab_icon_max_width" qualifiers="const"> + <return type="int" /> + <param index="0" name="tab_idx" type="int" /> + <description> + Returns the maximum allowed width of the icon for the tab at index [param tab_idx]. </description> </method> <method name="get_tab_idx_at_point" qualifiers="const"> @@ -158,6 +165,14 @@ Sets an [param icon] for the tab at index [param tab_idx]. </description> </method> + <method name="set_tab_icon_max_width"> + <return type="void" /> + <param index="0" name="tab_idx" type="int" /> + <param index="1" name="width" type="int" /> + <description> + Sets the maximum allowed width of the icon for the tab at index [param tab_idx]. This limit is applied on top of the default size of the icon and on top of [theme_item icon_max_width]. The height is adjusted according to the icon's ratio. + </description> + </method> <method name="set_tab_language"> <return type="void" /> <param index="0" name="tab_idx" type="int" /> @@ -323,6 +338,9 @@ <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal separation between the elements inside tabs. </theme_item> + <theme_item name="icon_max_width" data_type="constant" type="int" default="0"> + The maximum allowed width of the tab's icon. This limit is applied on top of the default size of the icon, but before the value set with [method set_tab_icon_max_width]. The height is adjusted according to the icon's ratio. + </theme_item> <theme_item name="outline_size" data_type="constant" type="int" default="0"> The size of the tab text outline. [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index c34336353f..b0dcb932dc 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -209,6 +209,9 @@ <theme_item name="font_unselected_color" data_type="color" type="Color" default="Color(0.7, 0.7, 0.7, 1)"> Font color of the other, unselected tabs. </theme_item> + <theme_item name="icon_max_width" data_type="constant" type="int" default="0"> + The maximum allowed width of the tab's icon. This limit is applied on top of the default size of the icon, but before the value set with [method TabBar.set_tab_icon_max_width]. The height is adjusted according to the icon's ratio. + </theme_item> <theme_item name="icon_separation" data_type="constant" type="int" default="4"> Space between tab's name and its icon. </theme_item> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 95778f86c4..d09f61b8c8 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -518,6 +518,9 @@ <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between item cells. This is also used as the margin at the start of an item when folding is disabled. </theme_item> + <theme_item name="icon_max_width" data_type="constant" type="int" default="0"> + The maximum allowed width of the icon in item's cells. This limit is applied on top of the default size of the icon, but before the value set with [method TreeItem.set_icon_max_width]. The height is adjusted according to the icon's ratio. + </theme_item> <theme_item name="item_margin" data_type="constant" type="int" default="16"> The horizontal margin at the start of an item. This is used when folding is enabled for the item. </theme_item> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 3ce434e069..49b4622aed 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -183,7 +183,7 @@ <return type="int" /> <param index="0" name="column" type="int" /> <description> - Returns the column's icon's maximum width. + Returns the maximum allowed width of the icon in the given [param column]. </description> </method> <method name="get_icon_modulate" qualifiers="const"> @@ -545,7 +545,7 @@ <param index="0" name="column" type="int" /> <param index="1" name="width" type="int" /> <description> - Sets the given column's icon's maximum width. + Sets the maximum allowed width of the icon in the given [param column]. This limit is applied on top of the default size of the icon and on top of [theme_item Tree.icon_max_width]. The height is adjusted according to the icon's ratio. </description> </method> <method name="set_icon_modulate"> diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 9a8a1097cd..121af0170c 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -42,7 +42,8 @@ void GotoLineDialog::popup_find_line(CodeEdit *p_edit) { text_editor = p_edit; - line->set_text(itos(text_editor->get_caret_line())); + // Add 1 because text_editor->get_caret_line() starts from 0, but the editor user interface starts from 1. + line->set_text(itos(text_editor->get_caret_line() + 1)); line->select_all(); popup_centered(Size2(180, 80) * EDSCALE); line->grab_focus(); @@ -53,12 +54,14 @@ int GotoLineDialog::get_line() const { } void GotoLineDialog::ok_pressed() { - if (get_line() < 1 || get_line() > text_editor->get_line_count()) { + // Subtract 1 because the editor user interface starts from 1, but text_editor->set_caret_line(n) starts from 0. + const int line_number = get_line() - 1; + if (line_number < 0 || line_number >= text_editor->get_line_count()) { return; } text_editor->remove_secondary_carets(); - text_editor->unfold_line(get_line() - 1); - text_editor->set_caret_line(get_line() - 1); + text_editor->unfold_line(line_number); + text_editor->set_caret_line(line_number); hide(); } @@ -1092,13 +1095,13 @@ void CodeTextEditor::remove_find_replace_bar() { } void CodeTextEditor::trim_trailing_whitespace() { - bool trimed_whitespace = false; + bool trimmed_whitespace = false; for (int i = 0; i < text_editor->get_line_count(); i++) { String line = text_editor->get_line(i); if (line.ends_with(" ") || line.ends_with("\t")) { - if (!trimed_whitespace) { + if (!trimmed_whitespace) { text_editor->begin_complex_operation(); - trimed_whitespace = true; + trimmed_whitespace = true; } int end = 0; @@ -1112,7 +1115,7 @@ void CodeTextEditor::trim_trailing_whitespace() { } } - if (trimed_whitespace) { + if (trimmed_whitespace) { text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index ee47ee5729..aaf106a1c7 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -462,6 +462,11 @@ void CreateDialog::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { + const int icon_width = get_theme_constant(SNAME("class_icon_size"), SNAME("Editor")); + search_options->add_theme_constant_override("icon_max_width", icon_width); + favorites->add_theme_constant_override("icon_max_width", icon_width); + recent->set_fixed_icon_size(Size2(icon_width, icon_width)); + _update_theme(); } break; } diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 3059ce445c..c381c8c322 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -31,10 +31,13 @@ #include "editor_data.h" #include "core/config/project_settings.h" +#include "core/extension/gdextension_manager.h" #include "core/io/file_access.h" +#include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/editor_scale.h" #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/script_editor_plugin.h" #include "scene/resources/packed_scene.h" @@ -457,10 +460,10 @@ void EditorData::add_custom_type(const String &p_type, const String &p_inherits, ct.name = p_type; ct.icon = p_icon; ct.script = p_script; + if (!custom_types.has(p_inherits)) { custom_types[p_inherits] = Vector<CustomType>(); } - custom_types[p_inherits].push_back(ct); } @@ -1028,8 +1031,66 @@ void EditorData::script_class_load_icon_paths() { } } +Ref<Texture2D> EditorData::extension_class_get_icon(const String &p_class) const { + if (GDExtensionManager::get_singleton()->class_has_icon_path(p_class)) { + String icon_path = GDExtensionManager::get_singleton()->class_get_icon_path(p_class); + Ref<Texture2D> icon = _load_script_icon(icon_path); + if (icon.is_valid()) { + return icon; + } + } + return nullptr; +} + +Ref<Texture2D> EditorData::_load_script_icon(const String &p_path) const { + if (!p_path.is_empty() && ResourceLoader::exists(p_path)) { + Ref<Texture2D> icon = ResourceLoader::load(p_path); + if (icon.is_valid()) { + return icon; + } + } + return nullptr; +} + +Ref<Texture2D> EditorData::get_script_icon(const Ref<Script> &p_script) { + // Take from the local cache, if available. + if (_script_icon_cache.has(p_script) && _script_icon_cache[p_script].is_valid()) { + return _script_icon_cache[p_script]; + } + + Ref<Script> base_scr = p_script; + while (base_scr.is_valid()) { + // Check for scripted classes. + StringName class_name = script_class_get_name(base_scr->get_path()); + String icon_path = script_class_get_icon_path(class_name); + Ref<Texture2D> icon = _load_script_icon(icon_path); + if (icon.is_valid()) { + _script_icon_cache[p_script] = icon; + return icon; + } + + // Check for legacy custom classes defined by plugins. + // TODO: Should probably be deprecated in 4.x + const EditorData::CustomType *ctype = get_custom_type_by_path(base_scr->get_path()); + if (ctype && ctype->icon.is_valid()) { + _script_icon_cache[p_script] = ctype->icon; + return ctype->icon; + } + + // Move to the base class. + base_scr = base_scr->get_base_script(); + } + + // If no icon found, cache it as null. + _script_icon_cache[p_script] = Ref<Texture>(); + return nullptr; +} + +void EditorData::clear_script_icon_cache() { + _script_icon_cache.clear(); +} + EditorData::EditorData() { - current_edited_scene = -1; undo_redo_manager = memnew(EditorUndoRedoManager); script_class_load_icon_paths(); } diff --git a/editor/editor_data.h b/editor/editor_data.h index d00501280d..370963074c 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -144,6 +144,9 @@ private: HashMap<StringName, String> _script_class_icon_paths; HashMap<String, StringName> _script_class_file_to_path; + HashMap<Ref<Script>, Ref<Texture>> _script_icon_cache; + + Ref<Texture2D> _load_script_icon(const String &p_path) const; public: EditorPlugin *get_editor(Object *p_object); @@ -240,6 +243,11 @@ public: void script_class_save_icon_paths(); void script_class_load_icon_paths(); + Ref<Texture2D> extension_class_get_icon(const String &p_class) const; + + Ref<Texture2D> get_script_icon(const Ref<Script> &p_script); + void clear_script_icon_cache(); + EditorData(); ~EditorData(); }; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index cc569e777b..61dabb9541 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -263,16 +263,8 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) { class_desc->pop(); } -void EditorHelp::_add_type_icon(const String &p_type, int p_size) { - Ref<Texture2D> icon; - if (has_theme_icon(p_type, SNAME("EditorIcons"))) { - icon = get_theme_icon(p_type, SNAME("EditorIcons")); - } else if (ClassDB::class_exists(p_type) && ClassDB::is_parent_class(p_type, "Object")) { - icon = get_theme_icon(SNAME("Object"), SNAME("EditorIcons")); - } else { - icon = get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons")); - } - +void EditorHelp::_add_type_icon(const String &p_type, int p_size, const String &p_fallback) { + Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(p_type, p_fallback); Vector2i size = Vector2i(icon->get_width(), icon->get_height()); if (p_size > 0) { // Ensures icon scales proportionally on both axis, based on icon height. @@ -644,7 +636,7 @@ void EditorHelp::_update_doc() { section_line.push_back(Pair<String, int>(TTR("Top"), 0)); _push_title_font(); class_desc->add_text(TTR("Class:") + " "); - _add_type_icon(edited_class, theme_cache.doc_title_font_size); + _add_type_icon(edited_class, theme_cache.doc_title_font_size, "Object"); class_desc->add_text(" "); class_desc->push_color(theme_cache.headline_color); _add_text(edited_class); @@ -676,7 +668,7 @@ void EditorHelp::_update_doc() { String inherits = cd.inherits; while (!inherits.is_empty()) { - _add_type_icon(inherits); + _add_type_icon(inherits, theme_cache.doc_font_size, "ArrowRight"); class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type(). _add_type(inherits); @@ -709,7 +701,7 @@ void EditorHelp::_update_doc() { if (prev) { class_desc->add_text(" , "); } - _add_type_icon(E.value.name); + _add_type_icon(E.value.name, theme_cache.doc_font_size, "ArrowRight"); class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type(). _add_type(E.value.name); prev = true; diff --git a/editor/editor_help.h b/editor/editor_help.h index b2ffe3bc29..a23dbca213 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -159,7 +159,7 @@ class EditorHelp : public VBoxContainer { //void _button_pressed(int p_idx); void _add_type(const String &p_type, const String &p_enum = String()); - void _add_type_icon(const String &p_type, int p_size = 0); + void _add_type_icon(const String &p_type, int p_size = 0, const String &p_fallback = ""); void _add_method(const DocData::MethodDoc &p_method, bool p_overview = true); void _add_bulletpoint(); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 44426dc143..87c4dabd8d 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -1134,17 +1134,21 @@ void EditorInspectorCategory::_notification(int p_what) { int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); int hs = get_theme_constant(SNAME("h_separation"), SNAME("Tree")); + int icon_size = get_theme_constant(SNAME("class_icon_size"), SNAME("Editor")); int w = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; if (icon.is_valid()) { - w += hs + icon->get_width(); + w += hs + icon_size; } int ofs = (get_size().width - w) / 2; if (icon.is_valid()) { - draw_texture(icon, Point2(ofs, (get_size().height - icon->get_height()) / 2).floor()); - ofs += hs + icon->get_width(); + Size2 rect_size = Size2(icon_size, icon_size); + Point2 rect_pos = Point2(ofs, (get_size().height - icon_size) / 2).floor(); + draw_texture_rect(icon, Rect2(rect_pos, rect_size)); + + ofs += hs + icon_size; } Color color = get_theme_color(SNAME("font_color"), SNAME("Tree")); @@ -2753,46 +2757,28 @@ void EditorInspector::update_tree() { String label = p.name; doc_name = p.name; - // Set the category icon. + // Use category's owner script to update some of its information. if (!EditorNode::get_editor_data().is_type_recognized(type) && p.hint_string.length() && FileAccess::exists(p.hint_string)) { - // If we have a category inside a script, search for the first script with a valid icon. + StringName script_name; + Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script"); - StringName base_type; - StringName name; if (scr.is_valid()) { - base_type = scr->get_instance_base_type(); - name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); + script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); + + // Update the docs reference and the label based on the script. Vector<DocData::ClassDoc> docs = scr->get_documentation(); if (!docs.is_empty()) { doc_name = docs[0].name; } - if (name != StringName() && label != name) { - label = name; + if (script_name != StringName() && label != script_name) { + label = script_name; } } - while (scr.is_valid()) { - name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); - String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name); - if (name != StringName() && !icon_path.is_empty()) { - category->icon = ResourceLoader::load(icon_path, "Texture"); - break; - } - const EditorData::CustomType *ctype = EditorNode::get_editor_data().get_custom_type_by_path(scr->get_path()); - if (ctype) { - category->icon = ctype->icon; - break; - } - scr = scr->get_base_script(); - } - if (category->icon.is_null() && has_theme_icon(base_type, SNAME("EditorIcons"))) { - category->icon = get_theme_icon(base_type, SNAME("EditorIcons")); - } - } - if (category->icon.is_null()) { - if (!type.is_empty()) { // Can happen for built-in scripts. - category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object"); - } + // Find the corresponding icon. + category->icon = EditorNode::get_singleton()->get_class_icon(script_name, "Object"); + } else if (!type.is_empty()) { + category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object"); } // Set the category label. @@ -3133,6 +3119,11 @@ void EditorInspector::update_tree() { StringName propname = property_prefix + p.name; bool found = false; + // Small hack for theme_overrides. They are listed under Control, but come from another class. + if (classname == "Control" && p.name.begins_with("theme_override_")) { + classname = get_edited_object()->get_class(); + } + // Search for the property description in the cache. HashMap<StringName, HashMap<StringName, PropertyDocInfo>>::Iterator E = doc_info_cache.find(classname); if (E) { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 3e6faf99e7..4fe43f3892 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -34,7 +34,6 @@ #include "core/input/input.h" #include "core/io/config_file.h" #include "core/io/file_access.h" -#include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/class_db.h" @@ -682,10 +681,6 @@ void EditorNode::_notification(int p_what) { editor_data.clear_edited_scenes(); } break; - case Control::NOTIFICATION_THEME_CHANGED: { - scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size()); - } break; - case NOTIFICATION_READY: { { _initializing_plugins = true; @@ -773,6 +768,9 @@ void EditorNode::_notification(int p_what) { bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), SNAME("EditorStyles"))); tabbar_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("tabbar_background"), SNAME("TabContainer"))); + scene_tabs->add_theme_constant_override("icon_max_width", gui_base->get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"))); + scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size()); + main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); } @@ -3682,7 +3680,7 @@ void EditorNode::_remove_edited_scene(bool p_change_tab) { void EditorNode::_remove_scene(int index, bool p_change_tab) { // Clear icon cache in case some scripts are no longer needed. - script_icon_cache.clear(); + editor_data.clear_script_icon_cache(); if (editor_data.get_edited_scene() == index) { // Scene to remove is current scene. @@ -4446,18 +4444,6 @@ StringName EditorNode::get_object_custom_type_name(const Object *p_object) const return StringName(); } -Ref<ImageTexture> EditorNode::_load_custom_class_icon(const String &p_path) const { - if (p_path.length()) { - Ref<Image> img = memnew(Image); - Error err = ImageLoader::load_image(p_path, img); - if (err == OK) { - img->resize(16 * EDSCALE, 16 * EDSCALE, Image::INTERPOLATE_LANCZOS); - return ImageTexture::create_from_image(img); - } - } - return nullptr; -} - void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_name) { if (p_custom_action_name == "select_current") { Node *scene = editor_data.get_edited_scene_root(); @@ -4480,106 +4466,86 @@ void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_na } } -Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String &p_fallback) { - ERR_FAIL_COND_V(!p_object || !gui_base, nullptr); +Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback) { + ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty."); + EditorData &ed = EditorNode::get_editor_data(); - Ref<Script> scr = p_object->get_script(); - if (scr.is_null() && p_object->is_class("Script")) { - scr = p_object; - } + // Check for a script icon first. + if (p_script.is_valid()) { + Ref<Texture2D> script_icon = ed.get_script_icon(p_script); + if (script_icon.is_valid()) { + return script_icon; + } - if (scr.is_valid() && !script_icon_cache.has(scr)) { - Ref<Script> base_scr = scr; - while (base_scr.is_valid()) { - StringName name = EditorNode::get_editor_data().script_class_get_name(base_scr->get_path()); - String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name); - Ref<ImageTexture> icon = _load_custom_class_icon(icon_path); - if (icon.is_valid()) { - script_icon_cache[scr] = icon; - return icon; - } + // No custom icon was found in the inheritance chain, so check the base + // class of the script instead. + String base_type; + p_script->get_language()->get_global_class_name(p_script->get_path(), &base_type); - // TODO: should probably be deprecated in 4.x - StringName base = base_scr->get_instance_base_type(); - if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) { - const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base]; - for (int i = 0; i < types.size(); ++i) { - if (types[i].script == base_scr && types[i].icon.is_valid()) { - script_icon_cache[scr] = types[i].icon; - return types[i].icon; - } - } - } - base_scr = base_scr->get_base_script(); + // Check if the base type is an extension-defined type. + Ref<Texture2D> ext_icon = ed.extension_class_get_icon(base_type); + if (ext_icon.is_valid()) { + return ext_icon; } - // If no icon found, cache it as null. - script_icon_cache[scr] = Ref<Texture>(); - } else if (scr.is_valid() && script_icon_cache.has(scr) && script_icon_cache[scr].is_valid()) { - return script_icon_cache[scr]; + // Look for the base type in the editor theme. + // This is only relevant for built-in classes. + if (gui_base && gui_base->has_theme_icon(base_type, "EditorIcons")) { + return gui_base->get_theme_icon(base_type, "EditorIcons"); + } } - // TODO: Should probably be deprecated in 4.x. - if (p_object->has_meta("_editor_icon")) { - return p_object->get_meta("_editor_icon"); + // Script was not valid or didn't yield any useful values, try the class name + // directly. + + // Check if the class name is an extension-defined type. + Ref<Texture2D> ext_icon = ed.extension_class_get_icon(p_class); + if (ext_icon.is_valid()) { + return ext_icon; } - if (gui_base->has_theme_icon(p_object->get_class(), SNAME("EditorIcons"))) { - return gui_base->get_theme_icon(p_object->get_class(), SNAME("EditorIcons")); + // Check if the class name is a custom type. + // TODO: Should probably be deprecated in 4.x + const EditorData::CustomType *ctype = ed.get_custom_type_by_name(p_class); + if (ctype && ctype->icon.is_valid()) { + return ctype->icon; } - if (p_fallback.length()) { - return gui_base->get_theme_icon(p_fallback, SNAME("EditorIcons")); + // Look up the class name or the fallback name in the editor theme. + // This is only relevant for built-in classes. + if (gui_base) { + if (gui_base->has_theme_icon(p_class, SNAME("EditorIcons"))) { + return gui_base->get_theme_icon(p_class, SNAME("EditorIcons")); + } + + if (p_fallback.length() && gui_base->has_theme_icon(p_fallback, SNAME("EditorIcons"))) { + return gui_base->get_theme_icon(p_fallback, SNAME("EditorIcons")); + } } return nullptr; } -Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) const { - ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty."); +Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String &p_fallback) { + ERR_FAIL_NULL_V_MSG(p_object, nullptr, "Object cannot be null."); - if (ScriptServer::is_global_class(p_class)) { - String class_name = p_class; - Ref<Script> scr = EditorNode::get_editor_data().script_class_load_script(class_name); - - while (true) { - String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(class_name); - Ref<Texture> icon = _load_custom_class_icon(icon_path); - if (icon.is_valid()) { - return icon; // Current global class has icon. - } - - // Find next global class along the inheritance chain. - do { - Ref<Script> base_scr = scr->get_base_script(); - if (base_scr.is_null()) { - // We've reached a native class, use its icon. - String base_type; - scr->get_language()->get_global_class_name(scr->get_path(), &base_type); - if (gui_base->has_theme_icon(base_type, "EditorIcons")) { - return gui_base->get_theme_icon(base_type, "EditorIcons"); - } - return gui_base->get_theme_icon(p_fallback, "EditorIcons"); - } - scr = base_scr; - class_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); - } while (class_name.is_empty()); - } + Ref<Script> scr = p_object->get_script(); + if (scr.is_null() && p_object->is_class("Script")) { + scr = p_object; } - if (const EditorData::CustomType *ctype = EditorNode::get_editor_data().get_custom_type_by_name(p_class)) { - return ctype->icon; - } + return _get_class_or_script_icon(p_object->get_class(), scr, p_fallback); +} - if (gui_base->has_theme_icon(p_class, SNAME("EditorIcons"))) { - return gui_base->get_theme_icon(p_class, SNAME("EditorIcons")); - } +Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) { + ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty."); - if (p_fallback.length() && gui_base->has_theme_icon(p_fallback, SNAME("EditorIcons"))) { - return gui_base->get_theme_icon(p_fallback, SNAME("EditorIcons")); + Ref<Script> scr; + if (ScriptServer::is_global_class(p_class)) { + scr = EditorNode::get_editor_data().script_class_load_script(p_class); } - return nullptr; + return _get_class_or_script_icon(p_class, scr, p_fallback); } bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringName &p_class) { @@ -7159,6 +7125,7 @@ EditorNode::EditorNode() { scene_tabs->set_select_with_rmb(true); scene_tabs->add_tab("unsaved"); scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button").operator int()); + scene_tabs->add_theme_constant_override("icon_max_width", gui_base->get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"))); scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE); scene_tabs->set_drag_to_rearrange_enabled(true); scene_tabs->set_auto_translate(false); diff --git a/editor/editor_node.h b/editor/editor_node.h index 66a3bf5be2..6bf2750fd4 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -513,7 +513,6 @@ private: PrintHandlerList print_handler; HashMap<String, Ref<Texture2D>> icon_type_cache; - HashMap<Ref<Script>, Ref<Texture>> script_icon_cache; static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS]; static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS]; @@ -697,7 +696,8 @@ private: void _feature_profile_changed(); bool _is_class_editor_disabled_by_feature_profile(const StringName &p_class); - Ref<ImageTexture> _load_custom_class_icon(const String &p_path) const; + + Ref<Texture2D> _get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback = "Object"); void _pick_main_scene_custom_action(const String &p_custom_action_name); @@ -879,7 +879,7 @@ public: Ref<Script> get_object_custom_type_base(const Object *p_object) const; StringName get_object_custom_type_name(const Object *p_object) const; Ref<Texture2D> get_object_icon(const Object *p_object, const String &p_fallback = "Object"); - Ref<Texture2D> get_class_icon(const String &p_class, const String &p_fallback = "Object") const; + Ref<Texture2D> get_class_icon(const String &p_class, const String &p_fallback = "Object"); bool is_object_of_custom_type(const Object *p_object, const StringName &p_class); diff --git a/editor/editor_path.cpp b/editor/editor_path.cpp index 01e3bff5c2..bb464ee366 100644 --- a/editor/editor_path.cpp +++ b/editor/editor_path.cpp @@ -201,8 +201,12 @@ void EditorPath::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { update_path(); - sub_objects_icon->set_texture(get_theme_icon(SNAME("arrow"), SNAME("OptionButton"))); + int icon_size = get_theme_constant(SNAME("class_icon_size"), SNAME("Editor")); + + current_object_icon->set_custom_minimum_size(Size2(icon_size, icon_size)); current_object_label->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts"))); + sub_objects_icon->set_texture(get_theme_icon(SNAME("arrow"), SNAME("OptionButton"))); + sub_objects_menu->add_theme_constant_override("icon_max_width", icon_size); } break; case NOTIFICATION_READY: { @@ -227,7 +231,8 @@ EditorPath::EditorPath(EditorSelectionHistory *p_history) { main_mc->add_child(main_hb); current_object_icon = memnew(TextureRect); - current_object_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + current_object_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + current_object_icon->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); main_hb->add_child(current_object_icon); current_object_label = memnew(Label); diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp index b90edb8f90..c7c41a6ed0 100644 --- a/editor/editor_quick_open.cpp +++ b/editor/editor_quick_open.cpp @@ -69,10 +69,9 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) { for (int i = 0; i < p_efsd->get_file_count(); i++) { String file = p_efsd->get_file_path(i); String engine_type = p_efsd->get_file_type(i); - String script_type = p_efsd->get_file_resource_script_class(i); - String actual_type = script_type.is_empty() ? engine_type : script_type; + // Iterate all possible base types. for (String &parent_type : base_types) { if (ClassDB::is_parent_class(engine_type, parent_type) || EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type)) { @@ -81,7 +80,7 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) { // Store refs to used icons. String ext = file.get_extension(); if (!icons.has(ext)) { - icons.insert(ext, get_theme_icon((has_theme_icon(actual_type, SNAME("EditorIcons")) ? actual_type : "Object"), SNAME("EditorIcons"))); + icons.insert(ext, EditorNode::get_singleton()->get_class_icon(actual_type, "Object")); } // Stop testing base types as soon as we got a match. diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index 17f005a579..4e7b7a8434 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -784,6 +784,7 @@ void EditorResourcePicker::_notification(int p_what) { [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { + assign_button->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"))); edit_button->set_icon(get_theme_icon(SNAME("select_arrow"), SNAME("Tree"))); } break; @@ -923,6 +924,7 @@ EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) { assign_button = memnew(Button); assign_button->set_flat(true); assign_button->set_h_size_flags(SIZE_EXPAND_FILL); + assign_button->set_expand_icon(true); assign_button->set_clip_text(true); assign_button->set_auto_translate(false); SET_DRAG_FORWARDING_GCD(assign_button, EditorResourcePicker); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 3e6b0ee07f..ea34c07740 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -587,9 +587,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { } else { theme->set_color("highend_color", "Editor", Color(1.0, 0.0, 0.0)); } + const int thumb_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size"); theme->set_constant("scale", "Editor", EDSCALE); theme->set_constant("thumb_size", "Editor", thumb_size); + theme->set_constant("class_icon_size", "Editor", 16 * EDSCALE); theme->set_constant("dark_theme", "Editor", dark_theme); theme->set_constant("color_picker_button_height", "Editor", 28 * EDSCALE); diff --git a/editor/renames_map_3_to_4.cpp b/editor/renames_map_3_to_4.cpp index b35c0987a3..1908e877e7 100644 --- a/editor/renames_map_3_to_4.cpp +++ b/editor/renames_map_3_to_4.cpp @@ -134,6 +134,7 @@ const char *RenamesMap3To4::enum_renames[][2] = { { "PAUSE_MODE_STOP", "PROCESS_MODE_PAUSABLE" }, // Node { "RENDER_DRAW_CALLS_IN_FRAME", "RENDER_TOTAL_DRAW_CALLS_IN_FRAME" }, // Performance { "RENDER_OBJECTS_IN_FRAME", "RENDER_TOTAL_OBJECTS_IN_FRAME" }, // Performance + { "SOURCE_GEOMETRY_NAVMESH_CHILDREN", "SOURCE_GEOMETRY_ROOT_NODE_CHILDREN" }, // NavigationMesh { "TEXTURE_TYPE_2D_ARRAY", "TEXTURE_LAYERED_2D_ARRAY" }, // RenderingServer { "TEXTURE_TYPE_CUBEMAP", "TEXTURE_LAYERED_CUBEMAP_ARRAY" }, // RenderingServer { "TRACKER_LEFT_HAND", "TRACKER_HAND_LEFT" }, // XRPositionalTracker @@ -309,6 +310,7 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "get_endian_swap", "is_big_endian" }, // File { "get_error_string", "get_error_message" }, // JSON { "get_filename", "get_scene_file_path" }, // Node -- WARNING: This may be used in a lot of other places. + { "get_final_location", "get_final_position" }, // NavigationAgent2D, NavigationAgent3D { "get_focus_neighbour", "get_focus_neighbor" }, // Control { "get_follow_smoothing", "get_position_smoothing_speed" }, // Camera2D { "get_font_types", "get_font_type_list" }, // Theme @@ -328,6 +330,8 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "get_importer_name", "_get_importer_name" }, // EditorImportPlugin { "get_interior_ambient", "get_ambient_color" }, // ReflectionProbe { "get_interior_ambient_energy", "get_ambient_color_energy" }, // ReflectionProbe + { "get_item_navmesh", "get_item_navigation_mesh" }, // MeshLibrary + { "get_item_navmesh_transform", "get_item_navigation_mesh_transform" }, // MeshLibrary { "get_iterations_per_second", "get_physics_ticks_per_second" }, // Engine { "get_last_mouse_speed", "get_last_mouse_velocity" }, // Input { "get_layer_mask_bit", "get_layer_mask_value" }, // VisualInstance3D @@ -336,11 +340,14 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "get_metakey", "is_meta_pressed" }, // InputEventWithModifiers { "get_mid_height", "get_height" }, // CapsuleMesh { "get_motion_remainder", "get_remainder" }, // PhysicsTestMotionResult2D + { "get_nav_path", "get_current_navigation_path" }, // NavigationAgent2D, NavigationAgent3D + { "get_nav_path_index", "get_current_navigation_path_index" }, // NavigationAgent2D, NavigationAgent3D { "get_neighbor_dist", "get_neighbor_distance" }, // NavigationAgent2D, NavigationAgent3D { "get_network_connected_peers", "get_peers" }, // Multiplayer API { "get_network_master", "get_multiplayer_authority" }, // Node { "get_network_peer", "get_multiplayer_peer" }, // Multiplayer API { "get_network_unique_id", "get_unique_id" }, // Multiplayer API + { "get_next_location", "get_next_path_position" }, // NavigationAgent2D, NavigationAgent3D { "get_ok", "get_ok_button" }, // AcceptDialog { "get_oneshot", "get_one_shot" }, // AnimatedTexture { "get_option_visibility", "_get_option_visibility" }, // EditorImportPlugin @@ -379,6 +386,7 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "get_surface_material_count", "get_surface_override_material_count" }, // MeshInstance3D { "get_tab_disabled", "is_tab_disabled" }, // Tab { "get_tab_hidden", "is_tab_hidden" }, // Tab + { "get_target_location", "get_target_position" }, // NavigationAgent2D, NavigationAgent3D { "get_text_align", "get_text_alignment" }, // Button { "get_theme_item_types", "get_theme_item_type_list" }, // Theme { "get_timer_process_mode", "get_timer_process_callback" }, // Timer @@ -387,6 +395,7 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "get_unit_db", "get_volume_db" }, // AudioStreamPlayer3D { "get_unit_offset", "get_progress_ratio" }, // PathFollow2D, PathFollow3D { "get_use_in_baked_light", "is_baking_navigation" }, // GridMap + { "get_verts_per_poly", "get_vertices_per_polygon" }, // NavigationMesh { "get_v_scrollbar", "get_v_scroll_bar" }, // ScrollContainer { "get_visible_name", "_get_visible_name" }, // EditorImportPlugin { "get_window_layout", "_get_window_layout" }, // EditorPlugin @@ -467,6 +476,9 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "property_list_changed_notify", "notify_property_list_changed" }, // Object { "recognize", "_recognize" }, // ResourceFormatLoader { "regen_normalmaps", "regen_normal_maps" }, // ArrayMesh + { "region_bake_navmesh", "region_bake_navigation_mesh" }, // Navigation3DServer + { "region_set_navmesh", "region_set_navigation_mesh" }, // Navigation3DServer + { "region_set_navpoly", "region_set_navigation_polygon" }, // Navigation2DServer { "remove_animation", "remove_animation_library" }, // AnimationPlayer { "remove_color_override", "remove_theme_color_override" }, // Control { "remove_constant_override", "remove_theme_constant_override" }, // Control @@ -522,6 +534,8 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "set_invert_faces", "set_flip_faces" }, // CSGPrimitive3D { "set_is_initialized", "_is_initialized" }, // XRInterface { "set_is_primary", "set_primary" }, // XRInterface + { "set_item_navmesh", "set_item_navigation_mesh" }, // MeshLibrary + { "set_item_navmesh_transform", "set_item_navigation_mesh_transform" }, // MeshLibrary { "set_iterations_per_second", "set_physics_ticks_per_second" }, // Engine { "set_layer_mask_bit", "set_layer_mask_value" }, // VisualInstance3D { "set_margins_preset", "set_offsets_preset" }, // Control @@ -554,6 +568,7 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "set_surface_material", "set_surface_override_material" }, // MeshInstance3D -- Breaks ImporterMesh. { "set_tab_align", "set_tab_alignment" }, // TabContainer { "set_tangent", "surface_set_tangent" }, // ImmediateGeometry -- Breaks SurfaceTool. + { "set_target_location", "set_target_position" }, // NavigationAgent2D, NavigationAgent3D { "set_text_align", "set_text_alignment" }, // Button { "set_timer_process_mode", "set_timer_process_callback" }, // Timer { "set_translation", "set_position" }, // Node3D -- This breaks GLTFNode, but it is used rarely. @@ -561,6 +576,7 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = { { "set_unit_db", "set_volume_db" }, // AudioStreamPlayer3D { "set_unit_offset", "set_progress_ratio" }, // PathFollow2D, PathFollow3D { "set_uv2", "surface_set_uv2" }, // ImmediateMesh -- Breaks SurfaceTool. + { "set_verts_per_poly", "set_vertices_per_polygon" }, // NavigationMesh { "set_v_drag_enabled", "set_drag_vertical_enabled" }, // Camera2D { "set_valign", "set_vertical_alignment" }, // Label { "set_window_layout", "_set_window_layout" }, // EditorPlugin @@ -718,6 +734,7 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "GetEnabledFocusMode", "GetFocusMode" }, // BaseButton { "GetEndianSwap", "IsBigEndian" }, // File { "GetErrorString", "GetErrorMessage" }, // JSON + { "GetFinalLocation", "GetFinalPosition" }, // NavigationAgent2D, NavigationAgent3D { "GetFocusNeighbour", "GetFocusNeighbor" }, // Control { "GetFollowSmoothing", "GetPositionSmoothingSpeed" }, // Camera2D { "GetFontTypes", "GetFontTypeList" }, // Theme @@ -737,6 +754,8 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "GetImporterName", "_GetImporterName" }, // EditorImportPlugin { "GetInteriorAmbient", "GetAmbientColor" }, // ReflectionProbe { "GetInteriorAmbientEnergy", "GetAmbientColorEnergy" }, // ReflectionProbe + { "GetItemNavmesh", "GetItemMavigationMesh" }, // MeshLibrary + { "GetItemNavmeshTransform", "GetItemNavigationMeshTransform" }, // MeshLibrary { "GetIterationsPerSecond", "GetPhysicsTicksPerSecond" }, // Engine { "GetLastMouseSpeed", "GetLastMouseVelocity" }, // Input { "GetLayerMaskBit", "GetLayerMaskValue" }, // VisualInstance3D @@ -745,11 +764,14 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "GetMetakey", "IsMetaPressed" }, // InputEventWithModifiers { "GetMidHeight", "GetHeight" }, // CapsuleMesh { "GetMotionRemainder", "GetRemainder" }, // PhysicsTestMotionResult2D + { "GetNavPath", "GetCurrentNavigationPath" }, // NavigationAgent2D, NavigationAgent3D + { "GetNavPathIndex", "GetCurrentNavigationPathIndex" }, // NavigationAgent2D, NavigationAgent3D { "GetNeighborDist", "GetNeighborDistance" }, // NavigationAgent2D, NavigationAgent3D { "GetNetworkConnectedPeers", "GetPeers" }, // Multiplayer API { "GetNetworkMaster", "GetMultiplayerAuthority" }, // Node { "GetNetworkPeer", "GetMultiplayerPeer" }, // Multiplayer API { "GetNetworkUniqueId", "GetUniqueId" }, // Multiplayer API + { "GetNextLocation", "GetNextPathPosition" }, // NavigationAgent2D, NavigationAgent3D { "GetOneshot", "GetOneShot" }, // AnimatedTexture { "GetOk", "GetOkButton" }, // AcceptDialog { "GetOptionVisibility", "_GetOptionVisibility" }, // EditorImportPlugin @@ -785,6 +807,7 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "GetSurfaceMaterialCount", "GetSurfaceOverrideMaterialCount" }, // MeshInstance3D { "GetTabDisabled", "IsTabDisabled" }, // Tab { "GetTabHidden", "IsTabHidden" }, // Tab + { "GetTargetLocation", "GetTargetPosition" }, // NavigationAgent2D, NavigationAgent3D { "GetTextAlign", "GetTextAlignment" }, // Button { "GetThemeItemTypes", "GetThemeItemTypeList" }, // Theme { "GetTimerProcessMode", "GetTimerProcessCallback" }, // Timer @@ -793,6 +816,7 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "GetUnitDb", "GetVolumeDb" }, // AudioStreamPlayer3D { "GetUnitOffset", "GetProgressRatio" }, // PathFollow2D, PathFollow3D { "GetUseInBakedLight", "IsBakingNavigation" }, // GridMap + { "GetVertsPerPoly", "GetVerticesPerPolygon" }, // NavigationMesh { "GetVScrollbar", "GetVScrollBar" }, // ScrollContainer { "GetVisibleName", "_GetVisibleName" }, // EditorImportPlugin { "GetWindowLayout", "_GetWindowLayout" }, // EditorPlugin @@ -869,6 +893,9 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "PropertyListChangedNotify", "NotifyPropertyListChanged" }, // Object { "Recognize", "_Recognize" }, // ResourceFormatLoader { "RegenNormalmaps", "RegenNormalMaps" }, // ArrayMesh + { "RegionBakeNavmesh", "region_bake_navigation_mesh" }, // Navigation3DServer + { "RegionSetNavmesh", "RegionSetNavigationMesh" }, // Navigation3DServer + { "RegionSetNavpoly", "RegionSetNavigationPolygon" }, // Navigation2DServer { "RemoveAnimation", "RemoveAnimationLibrary" }, // AnimationPlayer { "RemoveColorOverride", "RemoveThemeColorOverride" }, // Control { "RemoveConstantOverride", "RemoveThemeConstantOverride" }, // Control @@ -920,6 +947,8 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "SetInteriorAmbientEnergy", "SetAmbientColorEnergy" }, // ReflectionProbe { "SetIsInitialized", "_IsInitialized" }, // XRInterface { "SetIsPrimary", "SetPrimary" }, // XRInterface + { "SetItemNavmesh", "SetItemNavigationMesh" }, // MeshLibrary + { "SetItemNavmeshTransform", "SetItemNavigationMeshTransform" }, // MeshLibrary { "SetIterationsPerSecond", "SetPhysicsTicksPerSecond" }, // Engine { "SetLayerMaskBit", "SetLayerMaskValue" }, // VisualInstance3D { "SetMarginsPreset", "SetOffsetsPreset" }, // Control @@ -951,6 +980,7 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "SetSurfaceMaterial", "SetSurfaceOverrideMaterial" }, // MeshInstance3D -- Breaks ImporterMesh. { "SetTabAlign", "SetTabAlignment" }, // TabContainer { "SetTangent", "SurfaceSetTangent" }, // ImmediateGeometry -- Breaks SurfaceTool. + { "SetTargetLocation", "SetTargetPosition" }, // NavigationAgent2D, NavigationAgent3D { "SetTextAlign", "SetTextAlignment" }, // Button { "SetTimerProcessMode", "SetTimerProcessCallback" }, // Timer { "SetTonemapAutoExposure", "SetTonemapAutoExposureEnabled" }, // Environment @@ -959,6 +989,7 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = { { "SetUnitDb", "SetVolumeDb" }, // AudioStreamPlayer3D { "SetUnitOffset", "SetProgressRatio" }, // PathFollow2D, PathFollow3D { "SetUv2", "SurfaceSetUv2" }, // ImmediateMesh -- Breaks SurfaceTool. + { "SetVertsPerPoly", "SetVerticesPerPolygon" }, // NavigationMesh { "SetVDragEnabled", "SetDragVerticalEnabled" }, // Camera2D { "SetValign", "SetVerticalAlignment" }, // Label { "SetWindowLayout", "_SetWindowLayout" }, // EditorPlugin @@ -1092,6 +1123,8 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = { { "margin_right", "offset_right" }, // Control -- Breaks NinePatchRect, StyleBox. { "margin_top", "offset_top" }, // Control -- Breaks NinePatchRect, StyleBox. { "mid_height", "height" }, // CapsuleMesh + { "navpoly", "navigation_polygon" }, // NavigationRegion2D + { "navmesh", "navigation_mesh" }, // NavigationRegion3D { "neighbor_dist", "neighbor_distance" }, // NavigationAgent2D, NavigationAgent3D { "octaves", "fractal_octaves" }, // OpenSimplexNoise -> FastNoiseLite { "offset_h", "drag_horizontal_offset" }, // Camera2D @@ -1102,6 +1135,7 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = { { "out_of_range_mode", "max_polyphony" }, // AudioStreamPlayer3D { "pause_mode", "process_mode" }, // Node { "physical_scancode", "physical_keycode" }, // InputEventKey + { "polygon_verts_per_poly", "polygon_vertices_per_polyon" }, // NavigationMesh { "popup_exclusive", "exclusive" }, // Window { "proximity_fade_enable", "proximity_fade_enabled" }, // Material { "rect_position", "position" }, // Control @@ -1132,6 +1166,7 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = { { "table_hseparation", "table_h_separation" }, // Theme { "table_vseparation", "table_v_separation" }, // Theme { "tangent", "orthogonal" }, // Vector2 + { "target_location", "target_position" }, // NavigationAgent2D, NavigationAgent3D { "toplevel", "top_level" }, // Node { "translation", "position" }, // Node3D { "unit_db", "volume_db" }, // AudioStreamPlayer3D @@ -1187,6 +1222,8 @@ const char *RenamesMap3To4::csharp_properties_renames[][2] = { { "MarginRight", "OffsetRight" }, // Control -- Breaks NinePatchRect, StyleBox. { "MarginTop", "OffsetTop" }, // Control -- Breaks NinePatchRect, StyleBox. { "MidHeight", "Height" }, // CapsuleMesh + { "Navpoly", "NavigationPolygon" }, // NavigationRegion2D + { "Navmesh", "NavigationMesh" }, // NavigationRegion3D { "NeighborDist", "NeighborDistance" }, // NavigationAgent2D, NavigationAgent3D { "Octaves", "FractalOctaves" }, // OpenSimplexNoise -> FastNoiseLite { "OffsetH", "DragHorizontalOffset" }, // Camera2D @@ -1228,6 +1265,7 @@ const char *RenamesMap3To4::csharp_properties_renames[][2] = { { "TableHseparation", "TableHSeparation" }, // Theme { "TableVseparation", "TableVSeparation" }, // Theme { "Tangent", "Orthogonal" }, // Vector2 + { "TargetLocation", "TargetPosition" }, // NavigationAgent2D, NavigationAgent3D { "Toplevel", "TopLevel" }, // Node { "Translation", "Position" }, // Node3D { "UnitDb", "VolumeDb" }, // AudioStreamPlayer3D diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index b6e5ddafe2..2fd783102c 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1278,13 +1278,6 @@ void SceneTreeDock::_notification(int p_what) { spatial_editor_plugin->get_spatial_editor()->connect("item_lock_status_changed", callable_mp(scene_tree, &SceneTreeEditor::_update_tree).bind(false)); spatial_editor_plugin->get_spatial_editor()->connect("item_group_status_changed", callable_mp(scene_tree, &SceneTreeEditor::_update_tree).bind(false)); - button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - button_instance->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); - button_create_script->set_icon(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons"))); - button_detach_script->set_icon(get_theme_icon(SNAME("ScriptRemove"), SNAME("EditorIcons"))); - button_tree_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - - filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); filter->set_clear_button_enabled(true); // create_root_dialog @@ -1366,19 +1359,35 @@ void SceneTreeDock::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { scene_tree->set_auto_expand_selected(EDITOR_GET("docks/scene_tree/auto_expand_to_selected"), false); + } break; + + case NOTIFICATION_THEME_CHANGED: { button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); button_instance->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); button_create_script->set_icon(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons"))); button_detach_script->set_icon(get_theme_icon(SNAME("ScriptRemove"), SNAME("EditorIcons"))); button_tree_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - button_2d->set_icon(get_theme_icon(SNAME("Node2D"), SNAME("EditorIcons"))); - button_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons"))); - button_ui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons"))); - button_custom->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - button_clipboard->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons"))); filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filter->set_clear_button_enabled(true); + + // These buttons are created on READY, because reasons... + if (button_2d) { + button_2d->set_icon(get_theme_icon(SNAME("Node2D"), SNAME("EditorIcons"))); + } + if (button_3d) { + button_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons"))); + } + if (button_ui) { + button_ui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons"))); + } + if (button_custom) { + button_custom->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + } + if (button_clipboard) { + button_clipboard->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons"))); + } + + menu_subresources->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"))); } break; case NOTIFICATION_PROCESS: { diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 1f02ec4561..8ba6a1e610 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -878,6 +878,8 @@ void SceneTreeEditor::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { + tree->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"))); + _update_tree(); } break; } diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 4dcdbd6446..a7066bc41c 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -593,6 +593,7 @@ void NavMap::set_agent_as_controlled(NavAgent *agent) { if (!exist) { ERR_FAIL_COND(!has_agent(agent)); controlled_agents.push_back(agent); + agents_dirty = true; } } diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index ab5b054bb3..89bb1b5c65 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -2032,8 +2032,8 @@ void CharacterBody3D::_bind_methods() { ADD_GROUP("Moving Platform", "platform_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_on_leave", PROPERTY_HINT_ENUM, "Add Velocity,Add Upward Velocity,Do Nothing", PROPERTY_USAGE_DEFAULT), "set_platform_on_leave", "get_platform_on_leave"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_platform_floor_layers", "get_platform_floor_layers"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_platform_wall_layers", "get_platform_wall_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_floor_layers", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_platform_floor_layers", "get_platform_floor_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "platform_wall_layers", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_platform_wall_layers", "get_platform_wall_layers"); ADD_GROUP("Collision", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001,suffix:m"), "set_safe_margin", "get_safe_margin"); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 2a8b1cd8c4..70d87e221c 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -83,6 +83,7 @@ void Button::_update_theme_item_cache() { theme_cache.icon = get_theme_icon(SNAME("icon")); theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width")); } void Button::_notification(int p_what) { @@ -252,7 +253,6 @@ void Button::_notification(int p_what) { float icon_ofs_region = 0.0; Point2 style_offset; - Size2 icon_size = _icon->get_size(); if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { style_offset.x = style->get_margin(SIDE_LEFT); if (_internal_margin[SIDE_LEFT] > 0) { @@ -268,6 +268,7 @@ void Button::_notification(int p_what) { } style_offset.y = style->get_margin(SIDE_TOP); + Size2 icon_size = _icon->get_size(); if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; int icon_text_separation = text.is_empty() ? 0 : theme_cache.h_separation; @@ -285,6 +286,7 @@ void Button::_notification(int p_what) { icon_size = Size2(icon_width, icon_height); } + icon_size = _fit_icon_size(icon_size); if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { icon_region = Rect2(style_offset + Point2(icon_ofs_region, Math::floor((valign - icon_size.y) * 0.5)), icon_size); @@ -365,6 +367,18 @@ void Button::_notification(int p_what) { } } +Size2 Button::_fit_icon_size(const Size2 &p_size) const { + int max_width = theme_cache.icon_max_width; + Size2 icon_size = p_size; + + if (max_width > 0 && icon_size.width > max_width) { + icon_size.height = icon_size.height * max_width / icon_size.width; + icon_size.width = max_width; + } + + return icon_size; +} + Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Texture2D> p_icon) const { Ref<TextParagraph> paragraph; if (p_text.is_empty()) { @@ -380,15 +394,16 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu } if (!expand_icon && p_icon.is_valid()) { - minsize.height = MAX(minsize.height, p_icon->get_height()); + Size2 icon_size = _fit_icon_size(p_icon->get_size()); + minsize.height = MAX(minsize.height, icon_size.height); if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) { - minsize.width += p_icon->get_width(); + minsize.width += icon_size.width; if (!xl_text.is_empty() || !p_text.is_empty()) { minsize.width += MAX(0, theme_cache.h_separation); } } else { - minsize.width = MAX(minsize.width, p_icon->get_width()); + minsize.width = MAX(minsize.width, icon_size.width); } } diff --git a/scene/gui/button.h b/scene/gui/button.h index af1cde4b1e..3634b5344c 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -89,8 +89,11 @@ private: Ref<Texture2D> icon; int h_separation = 0; + int icon_max_width = 0; } theme_cache; + Size2 _fit_icon_size(const Size2 &p_size) const; + void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = ""); protected: diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 1a6adca121..03860c7449 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -47,6 +47,26 @@ String PopupMenu::_get_accel_text(const Item &p_item) const { return String(); } +Size2 PopupMenu::_get_item_icon_size(int p_item) const { + const PopupMenu::Item &item = items[p_item]; + Size2 icon_size = item.get_icon_size(); + + int max_width = 0; + if (theme_cache.icon_max_width > 0) { + max_width = theme_cache.icon_max_width; + } + if (item.icon_max_width > 0 && (max_width == 0 || item.icon_max_width < max_width)) { + max_width = item.icon_max_width; + } + + if (max_width > 0 && icon_size.width > max_width) { + icon_size.height = icon_size.height * max_width / icon_size.width; + icon_size.width = max_width; + } + + return icon_size; +} + Size2 PopupMenu::_get_contents_minimum_size() const { 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 @@ -61,7 +81,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { Size2 item_size; const_cast<PopupMenu *>(this)->_shape_item(i); - Size2 icon_size = items[i].get_icon_size(); + Size2 icon_size = _get_item_icon_size(i); item_size.height = _get_item_height(i); icon_w = MAX(icon_size.width, icon_w); @@ -109,7 +129,8 @@ Size2 PopupMenu::_get_contents_minimum_size() const { int PopupMenu::_get_item_height(int p_item) const { ERR_FAIL_INDEX_V(p_item, items.size(), 0); - int icon_height = items[p_item].get_icon_size().height; + Size2 icon_size = _get_item_icon_size(p_item); + int icon_height = icon_size.height; if (items[p_item].checkable_type && !items[p_item].separator) { icon_height = MAX(icon_height, MAX(theme_cache.checked->get_height(), theme_cache.radio_checked->get_height())); } @@ -540,7 +561,8 @@ void PopupMenu::_draw_items() { continue; } - icon_ofs = MAX(items[i].get_icon_size().width, icon_ofs); + Size2 icon_size = _get_item_icon_size(i); + icon_ofs = MAX(icon_size.width, icon_ofs); if (items[i].checkable_type) { has_check = true; @@ -569,7 +591,7 @@ void PopupMenu::_draw_items() { _shape_item(i); Point2 item_ofs = ofs; - Size2 icon_size = items[i].get_icon_size(); + Size2 icon_size = _get_item_icon_size(i); float h = _get_item_height(i); if (i == mouse_over) { @@ -631,21 +653,26 @@ void PopupMenu::_draw_items() { // Icon if (!items[i].icon.is_null()) { + const Point2 icon_offset = Point2(0, Math::floor((h - icon_size.height) / 2.0)); + Point2 icon_pos; + if (items[i].separator) { 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); + icon_pos = Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y); } else { - items[i].icon->draw(ci, item_ofs + Size2(separator_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + icon_pos = item_ofs + Size2(separator_ofs, 0); } } else { if (rtl) { - items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + icon_pos = Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y); } else { - items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + icon_pos = item_ofs + Size2(check_ofs, 0); } } + + items[i].icon->draw_rect(ci, Rect2(icon_pos + icon_offset, icon_size), false, icon_color); } // Submenu arrow on right hand side. @@ -802,6 +829,7 @@ void PopupMenu::_update_theme_item_cache() { 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.icon_max_width = get_theme_constant(SNAME("icon_max_width")); theme_cache.checked = get_theme_icon(SNAME("checked")); theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled")); @@ -946,8 +974,10 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); notify_property_list_changed(); _menu_changed(); @@ -958,8 +988,10 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.icon = p_icon; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); notify_property_list_changed(); _menu_changed(); @@ -970,8 +1002,10 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -982,8 +1016,10 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String & item.icon = p_icon; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); } @@ -992,8 +1028,10 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1004,8 +1042,10 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St item.icon = p_icon; item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1016,8 +1056,10 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int item.max_states = p_max_states; item.state = p_default_state; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1035,8 +1077,10 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g Item item; ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1046,8 +1090,10 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); item.icon = p_icon; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1057,8 +1103,10 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1069,8 +1117,10 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref< item.icon = p_icon; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1080,8 +1130,10 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_ ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1092,8 +1144,10 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons item.icon = p_icon; item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1105,8 +1159,10 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, item.id = p_id == -1 ? items.size() : p_id; item.submenu = p_submenu; items.push_back(item); + _shape_item(items.size() - 1); control->queue_redraw(); + child_controls_changed(); _menu_changed(); } @@ -1176,6 +1232,23 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { _menu_changed(); } +void PopupMenu::set_item_icon_max_width(int p_idx, int p_width) { + if (p_idx < 0) { + p_idx += get_item_count(); + } + ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].icon_max_width == p_width) { + return; + } + + items.write[p_idx].icon_max_width = p_width; + + control->queue_redraw(); + child_controls_changed(); + _menu_changed(); +} + void PopupMenu::set_item_checked(int p_idx, bool p_checked) { if (p_idx < 0) { p_idx += get_item_count(); @@ -1314,6 +1387,11 @@ Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const { return items[p_idx].icon; } +int PopupMenu::get_item_icon_max_width(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), 0); + return items[p_idx].icon_max_width; +} + Key PopupMenu::get_item_accelerator(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE); return items[p_idx].accel; @@ -2023,6 +2101,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction); ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language); ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon); + ClassDB::bind_method(D_METHOD("set_item_icon_max_width", "index", "width"), &PopupMenu::set_item_icon_max_width); ClassDB::bind_method(D_METHOD("set_item_checked", "index", "checked"), &PopupMenu::set_item_checked); ClassDB::bind_method(D_METHOD("set_item_id", "index", "id"), &PopupMenu::set_item_id); ClassDB::bind_method(D_METHOD("set_item_accelerator", "index", "accel"), &PopupMenu::set_item_accelerator); @@ -2045,6 +2124,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction); ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language); ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon); + ClassDB::bind_method(D_METHOD("get_item_icon_max_width", "index"), &PopupMenu::get_item_icon_max_width); ClassDB::bind_method(D_METHOD("is_item_checked", "index"), &PopupMenu::is_item_checked); ClassDB::bind_method(D_METHOD("get_item_id", "index"), &PopupMenu::get_item_id); ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index bcc02a890f..74b739ac0f 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -42,6 +42,7 @@ class PopupMenu : public Popup { struct Item { Ref<Texture2D> icon; + int icon_max_width = 0; String text; String xl_text; Ref<TextLine> text_buf; @@ -103,6 +104,7 @@ class PopupMenu : public Popup { int _get_item_height(int p_item) const; int _get_items_total_height() const; + Size2 _get_item_icon_size(int p_item) const; void _shape_item(int p_item); @@ -144,6 +146,7 @@ class PopupMenu : public Popup { int indent = 0; int item_start_padding = 0; int item_end_padding = 0; + int icon_max_width = 0; Ref<Texture2D> checked; Ref<Texture2D> checked_disabled; @@ -222,6 +225,7 @@ public: void set_item_text_direction(int p_idx, Control::TextDirection p_text_direction); void set_item_language(int p_idx, const String &p_language); void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon); + void set_item_icon_max_width(int p_idx, int p_width); void set_item_checked(int p_idx, bool p_checked); void set_item_id(int p_idx, int p_id); void set_item_accelerator(int p_idx, Key p_accel); @@ -245,6 +249,7 @@ public: String get_item_language(int p_idx) const; int get_item_idx_from_text(const String &text) const; Ref<Texture2D> get_item_icon(int p_idx) const; + int get_item_icon_max_width(int p_idx) const; bool is_item_checked(int p_idx) const; int get_item_id(int p_idx) const; int get_item_index(int p_id) const; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 5e378a0321..5eb5a519ff 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -63,10 +63,10 @@ Size2 TabBar::get_minimum_size() const { } 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 + theme_cache.h_separation; + if (tabs[i].icon.is_valid()) { + const Size2 icon_size = _get_tab_icon_size(i); + ms.height = MAX(ms.height, icon_size.height + y_margin); + ms.width += icon_size.width + theme_cache.h_separation; } if (!tabs[i].text.is_empty()) { @@ -304,6 +304,7 @@ void TabBar::_update_theme_item_cache() { Control::_update_theme_item_cache(); theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width")); theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected")); theme_cache.tab_selected_style = get_theme_stylebox(SNAME("tab_selected")); @@ -492,9 +493,11 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in // Draw the icon. Ref<Texture2D> icon = tabs[p_index].icon; 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)); + const Size2 icon_size = _get_tab_icon_size(p_index); + const Point2 icon_pos = Point2i(rtl ? p_x - icon_size.width : p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon_size.height) / 2); + icon->draw_rect(ci, Rect2(icon_pos, icon_size)); - p_x = rtl ? p_x - icon->get_width() - theme_cache.h_separation : p_x + icon->get_width() + theme_cache.h_separation; + p_x = rtl ? p_x - icon_size.width - theme_cache.h_separation : p_x + icon_size.width + theme_cache.h_separation; } // Draw the text. @@ -719,6 +722,29 @@ Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const { return tabs[p_tab].icon; } +void TabBar::set_tab_icon_max_width(int p_tab, int p_width) { + ERR_FAIL_INDEX(p_tab, tabs.size()); + + if (tabs[p_tab].icon_max_width == p_width) { + return; + } + + tabs.write[p_tab].icon_max_width = p_width; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } + queue_redraw(); + update_minimum_size(); +} + +int TabBar::get_tab_icon_max_width(int p_tab) const { + ERR_FAIL_INDEX_V(p_tab, tabs.size(), 0); + return tabs[p_tab].icon_max_width; +} + void TabBar::set_tab_disabled(int p_tab, bool p_disabled) { ERR_FAIL_INDEX(p_tab, tabs.size()); @@ -1023,9 +1049,14 @@ Variant TabBar::get_drag_data(const Point2 &p_point) { HBoxContainer *drag_preview = memnew(HBoxContainer); if (!tabs[tab_over].icon.is_null()) { + const Size2 icon_size = _get_tab_icon_size(tab_over); + TextureRect *tf = memnew(TextureRect); tf->set_texture(tabs[tab_over].icon); - tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + tf->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); + tf->set_custom_minimum_size(icon_size); + drag_preview->add_child(tf); } @@ -1270,9 +1301,9 @@ int TabBar::get_tab_width(int p_idx) const { } int x = style->get_minimum_size().width; - Ref<Texture2D> tex = tabs[p_idx].icon; - if (tex.is_valid()) { - x += tex->get_width() + theme_cache.h_separation; + if (tabs[p_idx].icon.is_valid()) { + const Size2 icon_size = _get_tab_icon_size(p_idx); + x += icon_size.width + theme_cache.h_separation; } if (!tabs[p_idx].text.is_empty()) { @@ -1305,6 +1336,27 @@ int TabBar::get_tab_width(int p_idx) const { return x; } +Size2 TabBar::_get_tab_icon_size(int p_index) const { + ERR_FAIL_INDEX_V(p_index, tabs.size(), Size2()); + const TabBar::Tab &tab = tabs[p_index]; + Size2 icon_size = tab.icon->get_size(); + + int icon_max_width = 0; + if (theme_cache.icon_max_width > 0) { + icon_max_width = theme_cache.icon_max_width; + } + if (tab.icon_max_width > 0 && (icon_max_width == 0 || tab.icon_max_width < icon_max_width)) { + icon_max_width = tab.icon_max_width; + } + + if (icon_max_width > 0 && icon_size.width > icon_max_width) { + icon_size.height = icon_size.height * icon_max_width / icon_size.width; + icon_size.width = icon_max_width; + } + + return icon_size; +} + void TabBar::_ensure_no_over_offset() { if (!is_inside_tree() || !buttons_visible) { return; @@ -1547,6 +1599,8 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon); ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabBar::get_tab_icon); + ClassDB::bind_method(D_METHOD("set_tab_icon_max_width", "tab_idx", "width"), &TabBar::set_tab_icon_max_width); + ClassDB::bind_method(D_METHOD("get_tab_icon_max_width", "tab_idx"), &TabBar::get_tab_icon_max_width); ClassDB::bind_method(D_METHOD("set_tab_button_icon", "tab_idx", "icon"), &TabBar::set_tab_button_icon); ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &TabBar::get_tab_button_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabBar::set_tab_disabled); diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index dc70a84689..a232061b69 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -62,6 +62,8 @@ private: Ref<TextLine> text_buf; Ref<Texture2D> icon; + int icon_max_width = 0; + bool disabled = false; bool hidden = false; int ofs_cache = 0; @@ -106,6 +108,7 @@ private: struct ThemeCache { int h_separation = 0; + int icon_max_width = 0; Ref<StyleBox> tab_unselected_style; Ref<StyleBox> tab_selected_style; @@ -133,6 +136,7 @@ private: } theme_cache; int get_tab_width(int p_idx) const; + Size2 _get_tab_icon_size(int p_idx) const; void _ensure_no_over_offset(); void _update_hover(); @@ -171,6 +175,9 @@ public: void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon); Ref<Texture2D> get_tab_icon(int p_tab) const; + void set_tab_icon_max_width(int p_tab, int p_width); + int get_tab_icon_max_width(int p_tab) const; + void set_tab_disabled(int p_tab, bool p_disabled); bool is_tab_disabled(int p_tab) const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 208cb29772..f4f7183475 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -146,6 +146,7 @@ void TabContainer::_update_theme_item_cache() { // TabBar overrides. theme_cache.icon_separation = get_theme_constant(SNAME("icon_separation")); + theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width")); theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected")); @@ -245,6 +246,7 @@ void TabContainer::_on_theme_changed() { 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("icon_max_width"), theme_cache.icon_max_width); tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size); _update_margins(); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 3020e1fada..f64f1fe2a3 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -59,6 +59,7 @@ class TabContainer : public Container { // TabBar overrides. int icon_separation = 0; + int icon_max_width = 0; int outline_size = 0; Ref<StyleBox> tab_unselected_style; diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 2526ee8215..2464e005ee 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -31,15 +31,10 @@ #include "texture_progress_bar.h" #include "core/config/engine.h" +#include "core/core_string_names.h" void TextureProgressBar::set_under_texture(const Ref<Texture2D> &p_texture) { - if (under == p_texture) { - return; - } - - under = p_texture; - queue_redraw(); - update_minimum_size(); + _set_texture(&under, p_texture); } Ref<Texture2D> TextureProgressBar::get_under_texture() const { @@ -47,15 +42,7 @@ Ref<Texture2D> TextureProgressBar::get_under_texture() const { } void TextureProgressBar::set_over_texture(const Ref<Texture2D> &p_texture) { - if (over == p_texture) { - return; - } - - over = p_texture; - queue_redraw(); - if (under.is_null()) { - update_minimum_size(); - } + _set_texture(&over, p_texture); } Ref<Texture2D> TextureProgressBar::get_over_texture() const { @@ -108,13 +95,7 @@ Size2 TextureProgressBar::get_minimum_size() const { } void TextureProgressBar::set_progress_texture(const Ref<Texture2D> &p_texture) { - if (progress == p_texture) { - return; - } - - progress = p_texture; - queue_redraw(); - update_minimum_size(); + _set_texture(&progress, p_texture); } Ref<Texture2D> TextureProgressBar::get_progress_texture() const { @@ -173,6 +154,28 @@ Color TextureProgressBar::get_tint_over() const { return tint_over; } +void TextureProgressBar::_set_texture(Ref<Texture2D> *p_destination, const Ref<Texture2D> &p_texture) { + DEV_ASSERT(p_destination); + Ref<Texture2D> &destination = *p_destination; + if (destination == p_texture) { + return; + } + if (destination.is_valid()) { + destination->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureProgressBar::_texture_changed)); + } + destination = p_texture; + if (destination.is_valid()) { + // Pass `CONNECT_REFERENCE_COUNTED` to avoid early disconnect in case the same texture is assigned to different "slots". + destination->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureProgressBar::_texture_changed), CONNECT_REFERENCE_COUNTED); + } + _texture_changed(); +} + +void TextureProgressBar::_texture_changed() { + update_minimum_size(); + queue_redraw(); +} + Point2 TextureProgressBar::unit_val_to_uv(float val) { if (progress.is_null()) { return Point2(); diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h index 53ec6fa2ae..5999aa986b 100644 --- a/scene/gui/texture_progress_bar.h +++ b/scene/gui/texture_progress_bar.h @@ -113,6 +113,8 @@ private: Color tint_progress = Color(1, 1, 1); Color tint_over = Color(1, 1, 1); + void _set_texture(Ref<Texture2D> *p_destination, const Ref<Texture2D> &p_texture); + void _texture_changed(); Point2 unit_val_to_uv(float val); Point2 get_relative_center(); void draw_nine_patch_stretched(const Ref<Texture2D> &p_texture, FillMode p_mode, double p_ratio, const Color &p_modulate); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 45a8c48143..4137896b47 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1340,10 +1340,7 @@ Size2 TreeItem::get_minimum_size(int p_column) { size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.h_separation; } 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; - } + Size2i icon_size = parent_tree->_get_cell_icon_size(cell); size.width += icon_size.width + parent_tree->theme_cache.h_separation; size.height = MAX(size.height, icon_size.height); } @@ -1628,6 +1625,7 @@ void Tree::_update_theme_item_cache() { theme_cache.v_separation = 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.icon_max_width = get_theme_constant(SNAME("icon_max_width")); theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); @@ -1654,6 +1652,25 @@ void Tree::_update_theme_item_cache() { theme_cache.base_scale = get_theme_default_base_scale(); } +Size2 Tree::_get_cell_icon_size(const TreeItem::Cell &p_cell) const { + Size2i icon_size = p_cell.get_icon_size(); + + int max_width = 0; + if (theme_cache.icon_max_width > 0) { + max_width = theme_cache.icon_max_width; + } + if (p_cell.icon_max_w > 0 && (max_width == 0 || p_cell.icon_max_w < max_width)) { + max_width = p_cell.icon_max_w; + } + + if (max_width > 0 && icon_size.width > max_width) { + icon_size.height = icon_size.height * max_width / icon_size.width; + icon_size.width = max_width; + } + + return icon_size; +} + int Tree::compute_item_height(TreeItem *p_item) const { if ((p_item == root && hide_root) || !p_item->is_visible()) { return 0; @@ -1688,10 +1705,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { case TreeItem::CELL_MODE_ICON: { Ref<Texture2D> icon = p_item->cells[i].icon; if (!icon.is_null()) { - Size2i s = p_item->cells[i].get_icon_size(); - if (p_item->cells[i].icon_max_w > 0 && s.width > p_item->cells[i].icon_max_w) { - s.height = s.height * p_item->cells[i].icon_max_w / s.width; - } + Size2i s = _get_cell_icon_size(p_item->cells[i]); if (s.height > height) { height = s.height; } @@ -1745,10 +1759,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co int w = 0; if (!p_cell.icon.is_null()) { - Size2i bmsize = p_cell.get_icon_size(); - if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) { - bmsize.width = p_cell.icon_max_w; - } + Size2i bmsize = _get_cell_icon_size(p_cell); w += bmsize.width + theme_cache.h_separation; if (rect.size.width > 0 && (w + ts.width) > rect.size.width) { ts.width = rect.size.width - w; @@ -1788,12 +1799,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co } if (!p_cell.icon.is_null()) { - Size2i bmsize = p_cell.get_icon_size(); - - if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) { - bmsize.height = bmsize.height * p_cell.icon_max_w / bmsize.width; - bmsize.width = p_cell.icon_max_w; - } + Size2i bmsize = _get_cell_icon_size(p_cell); 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 + theme_cache.h_separation; @@ -2206,12 +2212,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (p_item->cells[i].icon.is_null()) { break; } - Size2i icon_size = p_item->cells[i].get_icon_size(); - if (p_item->cells[i].icon_max_w > 0 && icon_size.width > p_item->cells[i].icon_max_w) { - icon_size.height = icon_size.height * p_item->cells[i].icon_max_w / icon_size.width; - icon_size.width = p_item->cells[i].icon_max_w; - } - + Size2i icon_size = _get_cell_icon_size(p_item->cells[i]); Point2i icon_ofs = (item_rect.size - icon_size) / 2; icon_ofs += item_rect.position; @@ -3795,8 +3796,9 @@ bool Tree::edit_selected() { popup_rect.size = rect.size; // Account for icon. - popup_rect.position.x += c.get_icon_size().x; - popup_rect.size.x -= c.get_icon_size().x; + Size2 icon_size = _get_cell_icon_size(c); + popup_rect.position.x += icon_size.x; + popup_rect.size.x -= icon_size.x; text_editor->clear(); text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step))); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index ec639ce439..3ba9c4ef2a 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -530,21 +530,24 @@ private: Color font_outline_color; float base_scale = 1.0; + int font_outline_size = 0; int h_separation = 0; int v_separation = 0; int item_margin = 0; int button_margin = 0; + int icon_max_width = 0; Point2 offset; + int draw_relationship_lines = 0; int relationship_line_width = 0; int parent_hl_line_width = 0; int children_hl_line_width = 0; int parent_hl_line_margin = 0; int draw_guides = 0; + int scroll_border = 0; int scroll_speed = 0; - int font_outline_size = 0; } theme_cache; struct Cache { @@ -573,6 +576,7 @@ private: } cache; int _get_title_button_height() const; + Size2 _get_cell_icon_size(const TreeItem::Cell &p_cell) const; void _scroll_moved(float p_value); HScrollBar *h_scroll = nullptr; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 2e1ba96d11..4c7525c745 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -180,6 +180,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("icon_disabled_color", "Button", Color(1, 1, 1, 0.4)); theme->set_constant("h_separation", "Button", 2 * scale); + theme->set_constant("icon_max_width", "Button", 0); // MenuBar theme->set_stylebox("normal", "MenuBar", button_normal); @@ -688,6 +689,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("separator_outline_size", "PopupMenu", 0); theme->set_constant("item_start_padding", "PopupMenu", 2 * scale); theme->set_constant("item_end_padding", "PopupMenu", 2 * scale); + theme->set_constant("icon_max_width", "PopupMenu", 0); // GraphNode Ref<StyleBoxFlat> graphnode_normal = make_flat_stylebox(style_normal_color, 18, 42, 18, 12); @@ -780,6 +782,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("scroll_border", "Tree", 4); theme->set_constant("scroll_speed", "Tree", 12); theme->set_constant("outline_size", "Tree", 0); + theme->set_constant("icon_max_width", "Tree", 0); // ItemList @@ -842,6 +845,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("side_margin", "TabContainer", 8 * scale); theme->set_constant("icon_separation", "TabContainer", 4 * scale); + theme->set_constant("icon_max_width", "TabContainer", 0); theme->set_constant("outline_size", "TabContainer", 0); // TabBar @@ -869,6 +873,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("drop_mark_color", "TabBar", Color(1, 1, 1)); theme->set_constant("h_separation", "TabBar", 4 * scale); + theme->set_constant("icon_max_width", "TabBar", 0); theme->set_constant("outline_size", "TabBar", 0); // Separators diff --git a/thirdparty/README.md b/thirdparty/README.md index 92e0a91596..b015c2bd47 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -686,7 +686,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/Samsung/thorvg -- Version: 0.8.3 (a0fcf51f80a75f63a066df085f60cdaf715188b6, 2022) +- Version: 0.8.4 (b0b7f207c6235691d694fc3f76e0b96e4858e606, 2023) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/AUTHORS b/thirdparty/thorvg/AUTHORS index 0f8ba2dd7d..11f3f170a5 100644 --- a/thirdparty/thorvg/AUTHORS +++ b/thirdparty/thorvg/AUTHORS @@ -4,7 +4,7 @@ Junsu Choi <jsuya.choi@samsung.com> Pranay Samanta <pranay.ks@samsung.com> Mateusz Palkowski <m.palkowski@samsung.com> Subhransu Mohanty <sub.mohanty@samsung.com> -Mira Grudzinska <m.grudzinska@samsung.com> +Mira Grudzinska <veleveta@gmail.com> Michal Szczecinski <m.szczecinsk@partner.samsung.com> Shinwoo Kim <cinoo.kim@samsung.com> Piotr Kalota <p.kalota@samsung.com> diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index eda302aec0..78522d6d2d 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -13,5 +13,5 @@ #define THORVG_JPG_LOADER_SUPPORT 1 -#define THORVG_VERSION_STRING "0.8.3" +#define THORVG_VERSION_STRING "0.8.4" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index 7e8988a65e..b1f2e9e286 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -336,6 +336,20 @@ public: CompositeMethod composite(const Paint** target) const noexcept; /** + * @brief Gets the composition source object and the composition method. + * + * @param[out] source The paint of the composition source object. + * @param[out] method The method used to composite the source object with the target. + * + * @return Result::Success when the paint object used as a composition target, Result::InsufficientCondition otherwise. + * + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @BETA_API + */ + Result composite(const Paint** source, CompositeMethod* method) const noexcept; + + /** * @brief Return the unique id value of the paint instance. * * This method can be called for checking the current concrete instance type. diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp index c3872f3207..a1c0032a2e 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp @@ -76,7 +76,9 @@ struct SwShapeTask : SwTask void run(unsigned tid) override { - if (opacity == 0) return; //Invisible + auto compMethod = CompositeMethod::None; + auto usedAsClip = (sdata->composite(nullptr, &compMethod) == Result::Success) && (compMethod == CompositeMethod::ClipPath); + if (opacity == 0 && !usedAsClip) return; //Invisible uint8_t strokeAlpha = 0; auto visibleStroke = false; @@ -98,7 +100,7 @@ struct SwShapeTask : SwTask sdata->fillColor(nullptr, nullptr, nullptr, &alpha); alpha = static_cast<uint8_t>(static_cast<uint32_t>(alpha) * opacity / 255); visibleFill = (alpha > 0 || sdata->fill()); - if (visibleFill || visibleStroke) { + if (visibleFill || visibleStroke || usedAsClip) { shapeReset(&shape); if (!shapePrepare(&shape, sdata, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) goto err; } @@ -110,7 +112,7 @@ struct SwShapeTask : SwTask //Fill if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) { - if (visibleFill) { + if (visibleFill || usedAsClip) { /* We assume that if stroke width is bigger than 2, shape outline below stroke could be full covered by stroke drawing. Thus it turns off antialising in that condition. @@ -291,7 +293,7 @@ bool SwRenderer::viewport(const RenderRegion& vp) } -bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs) +bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t colorSpace) { if (!buffer || stride == 0 || w == 0 || h == 0 || w > stride) return false; @@ -301,7 +303,7 @@ bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t surface->stride = stride; surface->w = w; surface->h = h; - surface->cs = cs; + surface->cs = colorSpace; vport.x = vport.y = 0; vport.w = surface->w; @@ -644,6 +646,13 @@ SwRenderer::SwRenderer():mpool(globalMpool) } +uint32_t SwRenderer::colorSpace() +{ + if (surface) return surface->cs; + return tvg::SwCanvas::ARGB8888; +} + + bool SwRenderer::init(uint32_t threads) { if ((initEngineCnt++) > 0) return true; diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h index 574d9d488f..cab93f9e1c 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h @@ -48,7 +48,7 @@ public: bool clear() override; bool sync() override; - bool target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs); + bool target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t colorSpace); bool mempool(bool shared); Compositor* target(const RenderRegion& region) override; @@ -56,6 +56,8 @@ public: bool endComposite(Compositor* cmp) override; void clearCompositors(); + uint32_t colorSpace() override; + static SwRenderer* gen(); static bool init(uint32_t threads); static int32_t init(); diff --git a/thirdparty/thorvg/src/lib/tvgAccessor.cpp b/thirdparty/thorvg/src/lib/tvgAccessor.cpp index 092c8b0731..5ad24f4acf 100644 --- a/thirdparty/thorvg/src/lib/tvgAccessor.cpp +++ b/thirdparty/thorvg/src/lib/tvgAccessor.cpp @@ -72,7 +72,7 @@ Accessor::~Accessor() } -Accessor::Accessor() +Accessor::Accessor() : pImpl(nullptr) { } diff --git a/thirdparty/thorvg/src/lib/tvgLoadModule.h b/thirdparty/thorvg/src/lib/tvgLoadModule.h index 004983152b..0a154aa47d 100644 --- a/thirdparty/thorvg/src/lib/tvgLoadModule.h +++ b/thirdparty/thorvg/src/lib/tvgLoadModule.h @@ -36,6 +36,7 @@ public: float vw = 0; float vh = 0; float w = 0, h = 0; //default image size + uint32_t colorSpace = SwCanvas::ARGB8888; virtual ~LoadModule() {} @@ -48,7 +49,7 @@ public: virtual bool read() = 0; virtual bool close() = 0; - virtual unique_ptr<Surface> bitmap() { return nullptr; } + virtual unique_ptr<Surface> bitmap(uint32_t colorSpace) { return nullptr; } virtual unique_ptr<Paint> paint() { return nullptr; } }; diff --git a/thirdparty/thorvg/src/lib/tvgMath.h b/thirdparty/thorvg/src/lib/tvgMath.h index 6120216d74..74f34fb744 100644 --- a/thirdparty/thorvg/src/lib/tvgMath.h +++ b/thirdparty/thorvg/src/lib/tvgMath.h @@ -53,6 +53,12 @@ static inline bool mathRightAngle(const Matrix* m) } +static inline bool mathSkewed(const Matrix* m) +{ + return (fabsf(m->e21 + m->e12) > FLT_EPSILON); +} + + static inline bool mathIdentity(const Matrix* m) { if (!mathEqual(m->e11, 1.0f) || !mathZero(m->e12) || !mathZero(m->e13) || diff --git a/thirdparty/thorvg/src/lib/tvgPaint.cpp b/thirdparty/thorvg/src/lib/tvgPaint.cpp index d0e908ddf2..c90e95cd33 100644 --- a/thirdparty/thorvg/src/lib/tvgPaint.cpp +++ b/thirdparty/thorvg/src/lib/tvgPaint.cpp @@ -38,9 +38,9 @@ static bool _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, if (rTransform) rTransform->update(); - //No rotational. - if (pTransform && !mathRightAngle(&pTransform->m)) return false; - if (rTransform && !mathRightAngle(&rTransform->m)) return false; + //No rotation and no skewing + if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) return false; + if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) return false; //Perpendicular Rectangle? auto pt1 = pts + 0; @@ -384,6 +384,19 @@ CompositeMethod Paint::composite(const Paint** target) const noexcept } +Result Paint::composite(const Paint** source, CompositeMethod* method) const noexcept +{ + if (source) *source = pImpl->compSource; + auto met = (pImpl->compSource && pImpl->compSource->pImpl->compData ? + pImpl->compSource->pImpl->compData->method : CompositeMethod::None); + if (method) *method = met; + + if (pImpl->compSource != nullptr && met != CompositeMethod::None) + return Result::Success; + return Result::InsufficientCondition; +} + + Result Paint::opacity(uint8_t o) noexcept { if (pImpl->opacity == o) return Result::Success; diff --git a/thirdparty/thorvg/src/lib/tvgPaint.h b/thirdparty/thorvg/src/lib/tvgPaint.h index c170559a37..94239c30a1 100644 --- a/thirdparty/thorvg/src/lib/tvgPaint.h +++ b/thirdparty/thorvg/src/lib/tvgPaint.h @@ -62,6 +62,7 @@ namespace tvg StrategyMethod* smethod = nullptr; RenderTransform* rTransform = nullptr; Composite* compData = nullptr; + Paint* compSource = nullptr; uint32_t renderFlag = RenderUpdateFlag::None; uint32_t ctxFlag = ContextFlag::Invalid; uint32_t id; @@ -136,6 +137,7 @@ namespace tvg if (!target && method == CompositeMethod::None) return true; compData = static_cast<Composite*>(calloc(1, sizeof(Composite))); } + target->pImpl->compSource = source; compData->target = target; compData->source = source; compData->method = method; diff --git a/thirdparty/thorvg/src/lib/tvgPictureImpl.h b/thirdparty/thorvg/src/lib/tvgPictureImpl.h index b2e097d400..b6bc19fa9c 100644 --- a/thirdparty/thorvg/src/lib/tvgPictureImpl.h +++ b/thirdparty/thorvg/src/lib/tvgPictureImpl.h @@ -66,6 +66,7 @@ struct Picture::Impl void* rdata = nullptr; //engine data float w = 0, h = 0; bool resizing = false; + uint32_t rendererColorSpace = 0; ~Impl() { @@ -100,7 +101,7 @@ struct Picture::Impl } } free(surface); - if ((surface = loader->bitmap().release())) { + if ((surface = loader->bitmap(rendererColorSpace).release())) { loader->close(); return RenderUpdateFlag::Image; } @@ -124,6 +125,7 @@ struct Picture::Impl void* update(RenderMethod &renderer, const RenderTransform* pTransform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag pFlag) { + rendererColorSpace = renderer.colorSpace(); auto flag = reload(); if (surface) { diff --git a/thirdparty/thorvg/src/lib/tvgRender.h b/thirdparty/thorvg/src/lib/tvgRender.h index ed66f393da..f474d87895 100644 --- a/thirdparty/thorvg/src/lib/tvgRender.h +++ b/thirdparty/thorvg/src/lib/tvgRender.h @@ -106,6 +106,8 @@ public: virtual Compositor* target(const RenderRegion& region) = 0; virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) = 0; virtual bool endComposite(Compositor* cmp) = 0; + + virtual uint32_t colorSpace() = 0; }; } diff --git a/thirdparty/thorvg/src/lib/tvgScene.cpp b/thirdparty/thorvg/src/lib/tvgScene.cpp index 0beec47b38..9ed7d45d5c 100644 --- a/thirdparty/thorvg/src/lib/tvgScene.cpp +++ b/thirdparty/thorvg/src/lib/tvgScene.cpp @@ -25,7 +25,7 @@ /* External Class Implementation */ /************************************************************************/ -Scene::Scene() : pImpl(new Impl()) +Scene::Scene() : pImpl(new Impl(this)) { Paint::pImpl->id = TVG_CLASS_ID_SCENE; Paint::pImpl->method(new PaintMethod<Scene::Impl>(pImpl)); diff --git a/thirdparty/thorvg/src/lib/tvgSceneImpl.h b/thirdparty/thorvg/src/lib/tvgSceneImpl.h index 6a7614c667..b6c68262c6 100644 --- a/thirdparty/thorvg/src/lib/tvgSceneImpl.h +++ b/thirdparty/thorvg/src/lib/tvgSceneImpl.h @@ -60,6 +60,11 @@ struct Scene::Impl Array<Paint*> paints; uint8_t opacity; //for composition RenderMethod* renderer = nullptr; //keep it for explicit clear + Scene* scene = nullptr; + + Impl(Scene* s) : scene(s) + { + } ~Impl() { @@ -81,8 +86,14 @@ struct Scene::Impl bool needComposition(uint32_t opacity) { + if (opacity == 0 || paints.count == 0) return false; + + //Masking may require composition (even if opacity == 255) + auto compMethod = scene->composite(nullptr); + if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true; + //Half translucent requires intermediate composition. - if (opacity == 255 || opacity == 0) return false; + if (opacity == 255) return false; //If scene has several children or only scene, it may require composition. if (paints.count > 1) return true; diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp index 05db65cb23..1fb0681814 100644 --- a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp +++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp @@ -42,6 +42,24 @@ static void _premultiply(uint32_t* data, uint32_t w, uint32_t h) } +static inline uint32_t CHANGE_COLORSPACE(uint32_t c) +{ + return (c & 0xff000000) + ((c & 0x00ff0000)>>16) + (c & 0x0000ff00) + ((c & 0x000000ff)<<16); +} + + +static void _changeColorSpace(uint32_t* data, uint32_t w, uint32_t h) +{ + auto buffer = data; + for (uint32_t y = 0; y < h; ++y, buffer += w) { + auto src = buffer; + for (uint32_t x = 0; x < w; ++x, ++src) { + *src = CHANGE_COLORSPACE(*src); + } + } +} + + PngLoader::PngLoader() { image = static_cast<png_imagep>(calloc(1, sizeof(png_image))); @@ -110,16 +128,21 @@ bool PngLoader::close() return true; } -unique_ptr<Surface> PngLoader::bitmap() +unique_ptr<Surface> PngLoader::bitmap(uint32_t colorSpace) { if (!content) return nullptr; + if (this->colorSpace != colorSpace) { + this->colorSpace = colorSpace; + _changeColorSpace(content, w, h); + } auto surface = static_cast<Surface*>(malloc(sizeof(Surface))); - surface->buffer = (uint32_t*)(content); + surface->buffer = content; surface->stride = w; surface->w = w; surface->h = h; - surface->cs = SwCanvas::ARGB8888; + surface->cs = colorSpace; return unique_ptr<Surface>(surface); } + diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h index 8beff0a3b3..f8c0daa61c 100644 --- a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h +++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h @@ -36,11 +36,11 @@ public: bool read() override; bool close() override; - unique_ptr<Surface> bitmap() override; + unique_ptr<Surface> bitmap(uint32_t colorSpace) override; private: png_imagep image = nullptr; - const uint32_t* content = nullptr; + uint32_t* content = nullptr; }; #endif //_TVG_PNG_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp index d11dfc1e7c..f64b7110fe 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp @@ -28,6 +28,24 @@ /* Internal Class Implementation */ /************************************************************************/ +static inline uint32_t CHANGE_COLORSPACE(uint32_t c) +{ + return (c & 0xff000000) + ((c & 0x00ff0000)>>16) + (c & 0x0000ff00) + ((c & 0x000000ff)<<16); +} + + +static void _changeColorSpace(uint32_t* data, uint32_t w, uint32_t h) +{ + auto buffer = data; + for (uint32_t y = 0; y < h; ++y, buffer += w) { + auto src = buffer; + for (uint32_t x = 0; x < w; ++x, ++src) { + *src = CHANGE_COLORSPACE(*src); + } + } +} + + void JpgLoader::clear() { jpgdDelete(decoder); @@ -110,18 +128,22 @@ bool JpgLoader::close() } -unique_ptr<Surface> JpgLoader::bitmap() +unique_ptr<Surface> JpgLoader::bitmap(uint32_t colorSpace) { this->done(); if (!image) return nullptr; + if (this->colorSpace != colorSpace) { + this->colorSpace = colorSpace; + _changeColorSpace(reinterpret_cast<uint32_t*>(image), w, h); + } auto surface = static_cast<Surface*>(malloc(sizeof(Surface))); - surface->buffer = (uint32_t*)(image); + surface->buffer = reinterpret_cast<uint32_t*>(image); surface->stride = static_cast<uint32_t>(w); surface->w = static_cast<uint32_t>(w); surface->h = static_cast<uint32_t>(h); - surface->cs = SwCanvas::ARGB8888; + surface->cs = colorSpace; return unique_ptr<Surface>(surface); } diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h index d6b886cb29..c47cb6704f 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h @@ -44,7 +44,7 @@ public: bool read() override; bool close() override; - unique_ptr<Surface> bitmap() override; + unique_ptr<Surface> bitmap(uint32_t colorSpace) override; void run(unsigned tid) override; }; diff --git a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp index 889f130ce9..19c1dd6668 100644 --- a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp +++ b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp @@ -28,6 +28,23 @@ /* Internal Class Implementation */ /************************************************************************/ +static inline uint32_t CHANGE_COLORSPACE(uint32_t c) +{ + return (c & 0xff000000) + ((c & 0x00ff0000)>>16) + (c & 0x0000ff00) + ((c & 0x000000ff)<<16); +} + + +static void _changeColorSpace(uint32_t* data, uint32_t w, uint32_t h) +{ + auto buffer = data; + for (uint32_t y = 0; y < h; ++y, buffer += w) { + auto src = buffer; + for (uint32_t x = 0; x < w; ++x, ++src) { + *src = CHANGE_COLORSPACE(*src); + } + } +} + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -54,7 +71,7 @@ bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) if (!content) return false; memcpy((void*)content, data, sizeof(uint32_t) * w * h); } - else content = data; + else content = const_cast<uint32_t*>(data); return true; } @@ -72,16 +89,20 @@ bool RawLoader::close() } -unique_ptr<Surface> RawLoader::bitmap() +unique_ptr<Surface> RawLoader::bitmap(uint32_t colorSpace) { if (!content) return nullptr; + if (this->colorSpace != colorSpace) { + this->colorSpace = colorSpace; + _changeColorSpace(content, w, h); + } auto surface = static_cast<Surface*>(malloc(sizeof(Surface))); - surface->buffer = (uint32_t*)(content); - surface->stride = (uint32_t)w; - surface->w = (uint32_t)w; - surface->h = (uint32_t)h; - surface->cs = SwCanvas::ARGB8888; + surface->buffer = content; + surface->stride = static_cast<uint32_t>(w); + surface->w = static_cast<uint32_t>(w); + surface->h = static_cast<uint32_t>(h); + surface->cs = colorSpace; return unique_ptr<Surface>(surface); } diff --git a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h index e4f3423b41..8789b0cf51 100644 --- a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h +++ b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h @@ -25,7 +25,7 @@ class RawLoader : public LoadModule { public: - const uint32_t* content = nullptr; + uint32_t* content = nullptr; bool copy = false; ~RawLoader(); @@ -35,7 +35,7 @@ public: bool read() override; bool close() override; - unique_ptr<Surface> bitmap() override; + unique_ptr<Surface> bitmap(uint32_t colorSpace) override; }; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 737fd96455..bc350a0eb8 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -180,9 +180,9 @@ static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengt else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0) * svgParse->global.w; else //if other then it's radius { - float max = (float)svgParse->global.w; + float max = svgParse->global.w; if (max < svgParse->global.h) - max = (float)svgParse->global.h; + max = svgParse->global.h; parsedValue = (parsedValue / 100.0) * max; } } @@ -341,7 +341,7 @@ static void _parseDashArray(SvgLoaderData* loader, const char *str, SvgDash* das ++end; //Refers to the diagonal length of the viewport. //https://www.w3.org/TR/SVG2/coords.html#Units - parsedValue = (sqrtf(pow(loader->svgParse->global.w, 2) + pow(loader->svgParse->global.h, 2)) / sqrtf(2.0f)) * (parsedValue / 100.0f); + parsedValue = (sqrtf(powf(loader->svgParse->global.w, 2) + powf(loader->svgParse->global.h, 2)) / sqrtf(2.0f)) * (parsedValue / 100.0f); } (*dash).array.push(parsedValue); str = end; @@ -376,7 +376,7 @@ static char* _idFromUrl(const char* url) } -static unsigned char _parserColor(const char* value, char** end) +static unsigned char _parseColor(const char* value, char** end) { float r; @@ -586,11 +586,11 @@ static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** *b = strtol(tmp, nullptr, 16); } } else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') { - tr = _parserColor(str + 4, &red); + tr = _parseColor(str + 4, &red); if (red && *red == ',') { - tg = _parserColor(red + 1, &green); + tg = _parseColor(red + 1, &green); if (green && *green == ',') { - tb = _parserColor(green + 1, &blue); + tb = _parseColor(green + 1, &blue); if (blue && blue[0] == ')' && blue[1] == '\0') { *r = tr; *g = tg; @@ -840,13 +840,13 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value) if (_parseNumber(&value, &doc->vy)) { if (_parseNumber(&value, &doc->vw)) { _parseNumber(&value, &doc->vh); - loader->svgParse->global.h = (uint32_t)doc->vh; + loader->svgParse->global.h = doc->vh; } - loader->svgParse->global.w = (uint32_t)doc->vw; + loader->svgParse->global.w = doc->vw; } - loader->svgParse->global.y = (int)doc->vy; + loader->svgParse->global.y = doc->vy; } - loader->svgParse->global.x = (int)doc->vx; + loader->svgParse->global.x = doc->vx; } else if (!strcmp(key, "preserveAspectRatio")) { _parseAspectRatio(&value, &doc->align, &doc->meetOrSlice); } else if (!strcmp(key, "style")) { @@ -1300,11 +1300,11 @@ static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const cha if (loader->svgParse->global.w == 0) { if (doc->w < FLT_EPSILON) loader->svgParse->global.w = 1; - else loader->svgParse->global.w = (uint32_t)doc->w; + else loader->svgParse->global.w = doc->w; } if (loader->svgParse->global.h == 0) { if (doc->h < FLT_EPSILON) loader->svgParse->global.h = 1; - else loader->svgParse->global.h = (uint32_t)doc->h; + else loader->svgParse->global.h = doc->h; } return loader->svgParse->node; @@ -2375,7 +2375,7 @@ static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html - if (userSpace && !radial->isRPercentage) radial->r = radial->r / (sqrtf(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrtf(2.0)); + if (userSpace && !radial->isRPercentage) radial->r = radial->r / (sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0)); } @@ -3180,6 +3180,12 @@ SvgLoader::~SvgLoader() void SvgLoader::run(unsigned tid) { + //According to the SVG standard the value of the width/height of the viewbox set to 0 disables rendering + if (renderingDisabled) { + root = Scene::gen(); + return; + } + if (!simpleXmlParse(content, size, true, _svgLoaderParser, &(loaderData))) return; if (loaderData.doc) { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h index f224d1a4ac..c6fdde55af 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h @@ -52,6 +52,7 @@ public: private: AspectRatioAlign align = AspectRatioAlign::XMidYMid; AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet; + bool renderingDisabled = false; bool header(); void clear(); diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h index c657c0e21a..3588cabf0b 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h @@ -425,8 +425,7 @@ struct SvgParser SvgStopStyleFlags flags; struct { - int x, y; - uint32_t w, h; + float x, y, w, h; } global; struct { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp index 4cb4ffdaeb..254ee2d008 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -255,7 +255,6 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox node->style->clipPath.applying = true; auto comp = Shape::gen(); - comp->fill(255, 255, 255, 255); if (node->transform) comp->transform(*node->transform); auto child = compNode->child.data; @@ -348,7 +347,7 @@ static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const stri //If stroke property is nullptr then do nothing if (style->stroke.paint.none) { - //Do nothing + vg->stroke(0.0f); } else if (style->stroke.paint.gradient) { Box bBox = vBox; if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg); diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp index 1f1fe2a718..7fb108bc21 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp @@ -137,7 +137,11 @@ float svgUtilStrtof(const char *nPtr, char **endPtr) pow10 *= 10ULL; } } + } else if (isspace(*iter)) { //skip if there is a space after the dot. + a = iter; + goto success; } + val += static_cast<float>(decimalPart) / static_cast<float>(pow10); a = iter; } diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp index 231badd27d..0e2c3fa141 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp @@ -304,38 +304,38 @@ bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName) bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data) { const char *itr = buf, *itrEnd = buf + bufLength; - char* tmpBuf = (char*)alloca(bufLength + 1); + char* tmpBuf = (char*)malloc(bufLength + 1); - if (!buf || !func) return false; + if (!buf || !func || !tmpBuf) goto error; while (itr < itrEnd) { const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd); const char *key, *keyEnd, *value, *valueEnd; char* tval; - if (p == itrEnd) return true; + if (p == itrEnd) goto success; key = p; for (keyEnd = key; keyEnd < itrEnd; keyEnd++) { if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break; } - if (keyEnd == itrEnd) return false; + if (keyEnd == itrEnd) goto error; if (keyEnd == key) continue; if (*keyEnd == '=') value = keyEnd + 1; else { value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd); - if (!value) return false; + if (!value) goto error; value++; } keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key); value = _skipWhiteSpacesAndXmlEntities(value, itrEnd); - if (value == itrEnd) return false; + if (value == itrEnd) goto error; if ((*value == '"') || (*value == '\'')) { valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value); - if (!valueEnd) return false; + if (!valueEnd) goto error; value++; } else { valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd); @@ -364,7 +364,14 @@ bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttr } } } + +success: + free(tmpBuf); return true; + +error: + free(tmpBuf); + return false; } diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index 8cccc947ce..34bb3b8e59 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ -VERSION=0.8.3 +VERSION=0.8.4 rm -rf AUTHORS inc LICENSE src *.zip -curl -L -O https://github.com/Samsung/thorvg/archive/v$VERSION.zip +curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.zip bsdtar --strip-components=1 -xvf *.zip rm *.zip rm -rf .github docs pc res test tools tvgcompat .git* *.md *.txt wasm_build.sh |