Browse Source

Merge pull request #75472 from YuriSizov/editor-iconography

Improve editor support for icons of custom, scripted, and GDExtension classes
Rémi Verschelde 2 years ago
parent
commit
21d080ead4

+ 9 - 0
core/extension/gdextension.cpp

@@ -549,6 +549,15 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String
 		return Ref<Resource>();
 		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;
 	return lib;
 }
 }
 
 

+ 2 - 0
core/extension/gdextension.h

@@ -67,6 +67,8 @@ protected:
 	static void _bind_methods();
 	static void _bind_methods();
 
 
 public:
 public:
+	HashMap<String, String> class_icon_paths;
+
 	static String get_extension_list_config_file();
 	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);
 	static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
 
 

+ 23 - 0
core/extension/gdextension_manager.cpp

@@ -50,6 +50,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &
 			extension->initialize_library(GDExtension::InitializationLevel(i));
 			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;
 	gdextension_map[p_path] = extension;
 	return LOAD_STATUS_OK;
 	return LOAD_STATUS_OK;
 }
 }
@@ -74,6 +79,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String
 			extension->deinitialize_library(GDExtension::InitializationLevel(i));
 			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);
 	gdextension_map.erase(p_path);
 	return LOAD_STATUS_OK;
 	return LOAD_STATUS_OK;
 }
 }
@@ -95,6 +105,19 @@ Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) {
 	return E->value;
 	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) {
 void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
 	ERR_FAIL_COND(int32_t(p_level) - 1 != level);
 	ERR_FAIL_COND(int32_t(p_level) - 1 != level);
 	for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
 	for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {

+ 4 - 0
core/extension/gdextension_manager.h

@@ -38,6 +38,7 @@ class GDExtensionManager : public Object {
 
 
 	int32_t level = -1;
 	int32_t level = -1;
 	HashMap<String, Ref<GDExtension>> gdextension_map;
 	HashMap<String, Ref<GDExtension>> gdextension_map;
+	HashMap<String, String> gdextension_class_icon_paths;
 
 
 	static void _bind_methods();
 	static void _bind_methods();
 
 
@@ -59,6 +60,9 @@ public:
 	Vector<String> get_loaded_extensions() const;
 	Vector<String> get_loaded_extensions() const;
 	Ref<GDExtension> get_extension(const String &p_path);
 	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 initialize_extensions(GDExtension::InitializationLevel p_level);
 	void deinitialize_extensions(GDExtension::InitializationLevel p_level);
 	void deinitialize_extensions(GDExtension::InitializationLevel p_level);
 
 

+ 4 - 1
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.
 			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>
 		<member name="expand_icon" type="bool" setter="set_expand_icon" getter="is_expand_icon" default="false">
 		<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>
 		<member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false">
 		<member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false">
 			Flat buttons don't display decoration.
 			Flat buttons don't display decoration.
@@ -116,6 +116,9 @@
 		<theme_item name="h_separation" data_type="constant" type="int" default="2">
 		<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.
 			The horizontal space between [Button]'s icon and text. Negative values will be treated as [code]0[/code] when used.
 		</theme_item>
 		</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">
 		<theme_item name="outline_size" data_type="constant" type="int" default="0">
 			The size of the text outline.
 			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.
 			[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.

+ 18 - 0
doc/classes/PopupMenu.xml

@@ -202,6 +202,13 @@
 				Returns the icon of the item at the given [param index].
 				Returns the icon of the item at the given [param index].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="get_item_id" qualifiers="const">
 			<return type="int" />
 			<return type="int" />
 			<param index="0" name="index" 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].
 				Replaces the [Texture2D] icon of the item at the given [param index].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="set_item_id">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="index" type="int" />
 			<param index="0" name="index" type="int" />
@@ -573,6 +588,9 @@
 		<theme_item name="h_separation" data_type="constant" type="int" default="4">
 		<theme_item name="h_separation" data_type="constant" type="int" default="4">
 			The horizontal space between the item's elements.
 			The horizontal space between the item's elements.
 		</theme_item>
 		</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">
 		<theme_item name="indent" data_type="constant" type="int" default="10">
 			Width of the single indentation level.
 			Width of the single indentation level.
 		</theme_item>
 		</theme_item>

+ 20 - 2
doc/classes/TabBar.xml

@@ -46,14 +46,21 @@
 			<return type="Texture2D" />
 			<return type="Texture2D" />
 			<param index="0" name="tab_idx" type="int" />
 			<param index="0" name="tab_idx" type="int" />
 			<description>
 			<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>
 			</description>
 		</method>
 		</method>
 		<method name="get_tab_icon" qualifiers="const">
 		<method name="get_tab_icon" qualifiers="const">
 			<return type="Texture2D" />
 			<return type="Texture2D" />
 			<param index="0" name="tab_idx" type="int" />
 			<param index="0" name="tab_idx" type="int" />
 			<description>
 			<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>
 			</description>
 		</method>
 		</method>
 		<method name="get_tab_idx_at_point" qualifiers="const">
 		<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].
 				Sets an [param icon] for the tab at index [param tab_idx].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="set_tab_language">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="tab_idx" type="int" />
 			<param index="0" name="tab_idx" type="int" />
@@ -323,6 +338,9 @@
 		<theme_item name="h_separation" data_type="constant" type="int" default="4">
 		<theme_item name="h_separation" data_type="constant" type="int" default="4">
 			The horizontal separation between the elements inside tabs.
 			The horizontal separation between the elements inside tabs.
 		</theme_item>
 		</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">
 		<theme_item name="outline_size" data_type="constant" type="int" default="0">
 			The size of the tab text outline.
 			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.
 			[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.

+ 3 - 0
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)">
 		<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.
 			Font color of the other, unselected tabs.
 		</theme_item>
 		</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">
 		<theme_item name="icon_separation" data_type="constant" type="int" default="4">
 			Space between tab's name and its icon.
 			Space between tab's name and its icon.
 		</theme_item>
 		</theme_item>

+ 3 - 0
doc/classes/Tree.xml

@@ -518,6 +518,9 @@
 		<theme_item name="h_separation" data_type="constant" type="int" default="4">
 		<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.
 			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>
+		<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">
 		<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.
 			The horizontal margin at the start of an item. This is used when folding is enabled for the item.
 		</theme_item>
 		</theme_item>

+ 2 - 2
doc/classes/TreeItem.xml

@@ -183,7 +183,7 @@
 			<return type="int" />
 			<return type="int" />
 			<param index="0" name="column" type="int" />
 			<param index="0" name="column" type="int" />
 			<description>
 			<description>
-				Returns the column's icon's maximum width.
+				Returns the maximum allowed width of the icon in the given [param column].
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="get_icon_modulate" qualifiers="const">
 		<method name="get_icon_modulate" qualifiers="const">
@@ -545,7 +545,7 @@
 			<param index="0" name="column" type="int" />
 			<param index="0" name="column" type="int" />
 			<param index="1" name="width" type="int" />
 			<param index="1" name="width" type="int" />
 			<description>
 			<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>
 			</description>
 		</method>
 		</method>
 		<method name="set_icon_modulate">
 		<method name="set_icon_modulate">

+ 5 - 0
editor/create_dialog.cpp

@@ -462,6 +462,11 @@ void CreateDialog::_notification(int p_what) {
 		} break;
 		} break;
 
 
 		case NOTIFICATION_THEME_CHANGED: {
 		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();
 			_update_theme();
 		} break;
 		} break;
 	}
 	}

+ 63 - 2
editor/editor_data.cpp

@@ -31,10 +31,13 @@
 #include "editor_data.h"
 #include "editor_data.h"
 
 
 #include "core/config/project_settings.h"
 #include "core/config/project_settings.h"
+#include "core/extension/gdextension_manager.h"
 #include "core/io/file_access.h"
 #include "core/io/file_access.h"
+#include "core/io/image_loader.h"
 #include "core/io/resource_loader.h"
 #include "core/io/resource_loader.h"
 #include "editor/editor_node.h"
 #include "editor/editor_node.h"
 #include "editor/editor_plugin.h"
 #include "editor/editor_plugin.h"
+#include "editor/editor_scale.h"
 #include "editor/editor_undo_redo_manager.h"
 #include "editor/editor_undo_redo_manager.h"
 #include "editor/plugins/script_editor_plugin.h"
 #include "editor/plugins/script_editor_plugin.h"
 #include "scene/resources/packed_scene.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.name = p_type;
 	ct.icon = p_icon;
 	ct.icon = p_icon;
 	ct.script = p_script;
 	ct.script = p_script;
+
 	if (!custom_types.has(p_inherits)) {
 	if (!custom_types.has(p_inherits)) {
 		custom_types[p_inherits] = Vector<CustomType>();
 		custom_types[p_inherits] = Vector<CustomType>();
 	}
 	}
-
 	custom_types[p_inherits].push_back(ct);
 	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() {
 EditorData::EditorData() {
-	current_edited_scene = -1;
 	undo_redo_manager = memnew(EditorUndoRedoManager);
 	undo_redo_manager = memnew(EditorUndoRedoManager);
 	script_class_load_icon_paths();
 	script_class_load_icon_paths();
 }
 }

+ 8 - 0
editor/editor_data.h

@@ -144,6 +144,9 @@ private:
 
 
 	HashMap<StringName, String> _script_class_icon_paths;
 	HashMap<StringName, String> _script_class_icon_paths;
 	HashMap<String, StringName> _script_class_file_to_path;
 	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:
 public:
 	EditorPlugin *get_editor(Object *p_object);
 	EditorPlugin *get_editor(Object *p_object);
@@ -240,6 +243,11 @@ public:
 	void script_class_save_icon_paths();
 	void script_class_save_icon_paths();
 	void script_class_load_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();
 	~EditorData();
 	~EditorData();
 };
 };

+ 5 - 13
editor/editor_help.cpp

@@ -263,16 +263,8 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) {
 	class_desc->pop();
 	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());
 	Vector2i size = Vector2i(icon->get_width(), icon->get_height());
 	if (p_size > 0) {
 	if (p_size > 0) {
 		// Ensures icon scales proportionally on both axis, based on icon height.
 		// 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));
 	section_line.push_back(Pair<String, int>(TTR("Top"), 0));
 	_push_title_font();
 	_push_title_font();
 	class_desc->add_text(TTR("Class:") + " ");
 	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->add_text(" ");
 	class_desc->push_color(theme_cache.headline_color);
 	class_desc->push_color(theme_cache.headline_color);
 	_add_text(edited_class);
 	_add_text(edited_class);
@@ -676,7 +668,7 @@ void EditorHelp::_update_doc() {
 		String inherits = cd.inherits;
 		String inherits = cd.inherits;
 
 
 		while (!inherits.is_empty()) {
 		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().
 			class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type().
 			_add_type(inherits);
 			_add_type(inherits);
 
 
@@ -709,7 +701,7 @@ void EditorHelp::_update_doc() {
 				if (prev) {
 				if (prev) {
 					class_desc->add_text(" , ");
 					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().
 				class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type().
 				_add_type(E.value.name);
 				_add_type(E.value.name);
 				prev = true;
 				prev = true;

+ 1 - 1
editor/editor_help.h

@@ -159,7 +159,7 @@ class EditorHelp : public VBoxContainer {
 
 
 	//void _button_pressed(int p_idx);
 	//void _button_pressed(int p_idx);
 	void _add_type(const String &p_type, const String &p_enum = String());
 	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_method(const DocData::MethodDoc &p_method, bool p_overview = true);
 
 
 	void _add_bulletpoint();
 	void _add_bulletpoint();

+ 19 - 33
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 font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
 
 
 			int hs = get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
 			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;
 			int w = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
 			if (icon.is_valid()) {
 			if (icon.is_valid()) {
-				w += hs + icon->get_width();
+				w += hs + icon_size;
 			}
 			}
 
 
 			int ofs = (get_size().width - w) / 2;
 			int ofs = (get_size().width - w) / 2;
 
 
 			if (icon.is_valid()) {
 			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"));
 			Color color = get_theme_color(SNAME("font_color"), SNAME("Tree"));
@@ -2753,46 +2757,28 @@ void EditorInspector::update_tree() {
 			String label = p.name;
 			String label = p.name;
 			doc_name = 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 (!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");
 				Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script");
-				StringName base_type;
-				StringName name;
 				if (scr.is_valid()) {
 				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();
 					Vector<DocData::ClassDoc> docs = scr->get_documentation();
 					if (!docs.is_empty()) {
 					if (!docs.is_empty()) {
 						doc_name = docs[0].name;
 						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.
 			// Set the category label.

+ 62 - 95
editor/editor_node.cpp

@@ -34,7 +34,6 @@
 #include "core/input/input.h"
 #include "core/input/input.h"
 #include "core/io/config_file.h"
 #include "core/io/config_file.h"
 #include "core/io/file_access.h"
 #include "core/io/file_access.h"
-#include "core/io/image_loader.h"
 #include "core/io/resource_loader.h"
 #include "core/io/resource_loader.h"
 #include "core/io/resource_saver.h"
 #include "core/io/resource_saver.h"
 #include "core/object/class_db.h"
 #include "core/object/class_db.h"
@@ -682,10 +681,6 @@ void EditorNode::_notification(int p_what) {
 			editor_data.clear_edited_scenes();
 			editor_data.clear_edited_scenes();
 		} break;
 		} break;
 
 
-		case Control::NOTIFICATION_THEME_CHANGED: {
-			scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
-		} break;
-
 		case NOTIFICATION_READY: {
 		case NOTIFICATION_READY: {
 			{
 			{
 				_initializing_plugins = true;
 				_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")));
 				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")));
 				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")));
 				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) {
 void EditorNode::_remove_scene(int index, bool p_change_tab) {
 	// Clear icon cache in case some scripts are no longer needed.
 	// 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) {
 	if (editor_data.get_edited_scene() == index) {
 		// Scene to remove is current scene.
 		// Scene to remove is current scene.
@@ -4446,18 +4444,6 @@ StringName EditorNode::get_object_custom_type_name(const Object *p_object) const
 	return StringName();
 	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) {
 void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_name) {
 	if (p_custom_action_name == "select_current") {
 	if (p_custom_action_name == "select_current") {
 		Node *scene = editor_data.get_edited_scene_root();
 		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;
 	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) {
 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->set_select_with_rmb(true);
 	scene_tabs->add_tab("unsaved");
 	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->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_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE);
 	scene_tabs->set_drag_to_rearrange_enabled(true);
 	scene_tabs->set_drag_to_rearrange_enabled(true);
 	scene_tabs->set_auto_translate(false);
 	scene_tabs->set_auto_translate(false);

+ 3 - 3
editor/editor_node.h

@@ -513,7 +513,6 @@ private:
 	PrintHandlerList print_handler;
 	PrintHandlerList print_handler;
 
 
 	HashMap<String, Ref<Texture2D>> icon_type_cache;
 	HashMap<String, Ref<Texture2D>> icon_type_cache;
-	HashMap<Ref<Script>, Ref<Texture>> script_icon_cache;
 
 
 	static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS];
 	static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS];
 	static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS];
 	static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS];
@@ -697,7 +696,8 @@ private:
 
 
 	void _feature_profile_changed();
 	void _feature_profile_changed();
 	bool _is_class_editor_disabled_by_feature_profile(const StringName &p_class);
 	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);
 	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;
 	Ref<Script> get_object_custom_type_base(const Object *p_object) const;
 	StringName get_object_custom_type_name(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_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);
 	bool is_object_of_custom_type(const Object *p_object, const StringName &p_class);
 
 

+ 7 - 2
editor/editor_path.cpp

@@ -201,8 +201,12 @@ void EditorPath::_notification(int p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			update_path();
 			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")));
 			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;
 		} break;
 
 
 		case NOTIFICATION_READY: {
 		case NOTIFICATION_READY: {
@@ -227,7 +231,8 @@ EditorPath::EditorPath(EditorSelectionHistory *p_history) {
 	main_mc->add_child(main_hb);
 	main_mc->add_child(main_hb);
 
 
 	current_object_icon = memnew(TextureRect);
 	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);
 	main_hb->add_child(current_object_icon);
 
 
 	current_object_label = memnew(Label);
 	current_object_label = memnew(Label);

+ 2 - 3
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++) {
 	for (int i = 0; i < p_efsd->get_file_count(); i++) {
 		String file = p_efsd->get_file_path(i);
 		String file = p_efsd->get_file_path(i);
 		String engine_type = p_efsd->get_file_type(i);
 		String engine_type = p_efsd->get_file_type(i);
-
 		String script_type = p_efsd->get_file_resource_script_class(i);
 		String script_type = p_efsd->get_file_resource_script_class(i);
-
 		String actual_type = script_type.is_empty() ? engine_type : script_type;
 		String actual_type = script_type.is_empty() ? engine_type : script_type;
+
 		// Iterate all possible base types.
 		// Iterate all possible base types.
 		for (String &parent_type : 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)) {
 			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.
 				// Store refs to used icons.
 				String ext = file.get_extension();
 				String ext = file.get_extension();
 				if (!icons.has(ext)) {
 				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.
 				// Stop testing base types as soon as we got a match.

+ 2 - 0
editor/editor_resource_picker.cpp

@@ -784,6 +784,7 @@ void EditorResourcePicker::_notification(int p_what) {
 			[[fallthrough]];
 			[[fallthrough]];
 		}
 		}
 		case NOTIFICATION_THEME_CHANGED: {
 		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")));
 			edit_button->set_icon(get_theme_icon(SNAME("select_arrow"), SNAME("Tree")));
 		} break;
 		} break;
 
 
@@ -923,6 +924,7 @@ EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) {
 	assign_button = memnew(Button);
 	assign_button = memnew(Button);
 	assign_button->set_flat(true);
 	assign_button->set_flat(true);
 	assign_button->set_h_size_flags(SIZE_EXPAND_FILL);
 	assign_button->set_h_size_flags(SIZE_EXPAND_FILL);
+	assign_button->set_expand_icon(true);
 	assign_button->set_clip_text(true);
 	assign_button->set_clip_text(true);
 	assign_button->set_auto_translate(false);
 	assign_button->set_auto_translate(false);
 	SET_DRAG_FORWARDING_GCD(assign_button, EditorResourcePicker);
 	SET_DRAG_FORWARDING_GCD(assign_button, EditorResourcePicker);

+ 2 - 0
editor/editor_themes.cpp

@@ -587,9 +587,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 	} else {
 	} else {
 		theme->set_color("highend_color", "Editor", Color(1.0, 0.0, 0.0));
 		theme->set_color("highend_color", "Editor", Color(1.0, 0.0, 0.0));
 	}
 	}
+
 	const int thumb_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
 	const int thumb_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
 	theme->set_constant("scale", "Editor", EDSCALE);
 	theme->set_constant("scale", "Editor", EDSCALE);
 	theme->set_constant("thumb_size", "Editor", thumb_size);
 	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("dark_theme", "Editor", dark_theme);
 	theme->set_constant("color_picker_button_height", "Editor", 28 * EDSCALE);
 	theme->set_constant("color_picker_button_height", "Editor", 28 * EDSCALE);
 
 

+ 22 - 13
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_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));
 			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);
 			filter->set_clear_button_enabled(true);
 
 
 			// create_root_dialog
 			// create_root_dialog
@@ -1366,19 +1359,35 @@ void SceneTreeDock::_notification(int p_what) {
 
 
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 			scene_tree->set_auto_expand_selected(EDITOR_GET("docks/scene_tree/auto_expand_to_selected"), false);
 			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_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
 			button_instance->set_icon(get_theme_icon(SNAME("Instance"), 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_create_script->set_icon(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons")));
 			button_detach_script->set_icon(get_theme_icon(SNAME("ScriptRemove"), 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_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_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;
 		} break;
 
 
 		case NOTIFICATION_PROCESS: {
 		case NOTIFICATION_PROCESS: {

+ 2 - 0
editor/scene_tree_editor.cpp

@@ -878,6 +878,8 @@ void SceneTreeEditor::_notification(int p_what) {
 		} break;
 		} break;
 
 
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
+			tree->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), SNAME("Editor")));
+
 			_update_tree();
 			_update_tree();
 		} break;
 		} break;
 	}
 	}

+ 19 - 4
scene/gui/button.cpp

@@ -83,6 +83,7 @@ void Button::_update_theme_item_cache() {
 	theme_cache.icon = get_theme_icon(SNAME("icon"));
 	theme_cache.icon = get_theme_icon(SNAME("icon"));
 
 
 	theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
 	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) {
 void Button::_notification(int p_what) {
@@ -252,7 +253,6 @@ void Button::_notification(int p_what) {
 
 
 				float icon_ofs_region = 0.0;
 				float icon_ofs_region = 0.0;
 				Point2 style_offset;
 				Point2 style_offset;
-				Size2 icon_size = _icon->get_size();
 				if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
 				if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
 					style_offset.x = style->get_margin(SIDE_LEFT);
 					style_offset.x = style->get_margin(SIDE_LEFT);
 					if (_internal_margin[SIDE_LEFT] > 0) {
 					if (_internal_margin[SIDE_LEFT] > 0) {
@@ -268,6 +268,7 @@ void Button::_notification(int p_what) {
 				}
 				}
 				style_offset.y = style->get_margin(SIDE_TOP);
 				style_offset.y = style->get_margin(SIDE_TOP);
 
 
+				Size2 icon_size = _icon->get_size();
 				if (expand_icon) {
 				if (expand_icon) {
 					Size2 _size = get_size() - style->get_offset() * 2;
 					Size2 _size = get_size() - style->get_offset() * 2;
 					int icon_text_separation = text.is_empty() ? 0 : theme_cache.h_separation;
 					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 = Size2(icon_width, icon_height);
 				}
 				}
+				icon_size = _fit_icon_size(icon_size);
 
 
 				if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
 				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);
 					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 {
 Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Texture2D> p_icon) const {
 	Ref<TextParagraph> paragraph;
 	Ref<TextParagraph> paragraph;
 	if (p_text.is_empty()) {
 	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()) {
 	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) {
 		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()) {
 			if (!xl_text.is_empty() || !p_text.is_empty()) {
 				minsize.width += MAX(0, theme_cache.h_separation);
 				minsize.width += MAX(0, theme_cache.h_separation);
 			}
 			}
 		} else {
 		} else {
-			minsize.width = MAX(minsize.width, p_icon->get_width());
+			minsize.width = MAX(minsize.width, icon_size.width);
 		}
 		}
 	}
 	}
 
 

+ 3 - 0
scene/gui/button.h

@@ -89,8 +89,11 @@ private:
 		Ref<Texture2D> icon;
 		Ref<Texture2D> icon;
 
 
 		int h_separation = 0;
 		int h_separation = 0;
+		int icon_max_width = 0;
 	} theme_cache;
 	} theme_cache;
 
 
+	Size2 _fit_icon_size(const Size2 &p_size) const;
+
 	void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = "");
 	void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = "");
 
 
 protected:
 protected:

+ 88 - 8
scene/gui/popup_menu.cpp

@@ -47,6 +47,26 @@ String PopupMenu::_get_accel_text(const Item &p_item) const {
 	return String();
 	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 PopupMenu::_get_contents_minimum_size() const {
 	Size2 minsize = theme_cache.panel_style->get_minimum_size(); // Accounts for margin in the margin container
 	Size2 minsize = theme_cache.panel_style->get_minimum_size(); // Accounts for margin in the margin container
 	minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content
 	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;
 		Size2 item_size;
 		const_cast<PopupMenu *>(this)->_shape_item(i);
 		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);
 		item_size.height = _get_item_height(i);
 		icon_w = MAX(icon_size.width, icon_w);
 		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 {
 int PopupMenu::_get_item_height(int p_item) const {
 	ERR_FAIL_INDEX_V(p_item, items.size(), 0);
 	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) {
 	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()));
 		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;
 			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) {
 		if (items[i].checkable_type) {
 			has_check = true;
 			has_check = true;
@@ -569,7 +591,7 @@ void PopupMenu::_draw_items() {
 		_shape_item(i);
 		_shape_item(i);
 
 
 		Point2 item_ofs = ofs;
 		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);
 		float h = _get_item_height(i);
 
 
 		if (i == mouse_over) {
 		if (i == mouse_over) {
@@ -631,21 +653,26 @@ void PopupMenu::_draw_items() {
 
 
 		// Icon
 		// Icon
 		if (!items[i].icon.is_null()) {
 		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) {
 			if (items[i].separator) {
 				separator_ofs -= (icon_size.width + theme_cache.h_separation) / 2;
 				separator_ofs -= (icon_size.width + theme_cache.h_separation) / 2;
 
 
 				if (rtl) {
 				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 {
 				} 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 {
 			} else {
 				if (rtl) {
 				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 {
 				} 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.
 		// 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.indent = get_theme_constant(SNAME("indent"));
 	theme_cache.item_start_padding = get_theme_constant(SNAME("item_start_padding"));
 	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.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 = get_theme_icon(SNAME("checked"));
 	theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled"));
 	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 item;
 	ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
 	ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	notify_property_list_changed();
 	notify_property_list_changed();
 	_menu_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_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
 	item.icon = p_icon;
 	item.icon = p_icon;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	notify_property_list_changed();
 	notify_property_list_changed();
 	_menu_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_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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.icon = p_icon;
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	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_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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.icon = p_icon;
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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.max_states = p_max_states;
 	item.state = p_default_state;
 	item.state = p_default_state;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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 item;
 	ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
 	ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
 	item.icon = p_icon;
 	item.icon = p_icon;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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.icon = p_icon;
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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.icon = p_icon;
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_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.id = p_id == -1 ? items.size() : p_id;
 	item.submenu = p_submenu;
 	item.submenu = p_submenu;
 	items.push_back(item);
 	items.push_back(item);
+
 	_shape_item(items.size() - 1);
 	_shape_item(items.size() - 1);
 	control->queue_redraw();
 	control->queue_redraw();
+
 	child_controls_changed();
 	child_controls_changed();
 	_menu_changed();
 	_menu_changed();
 }
 }
@@ -1176,6 +1232,23 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
 	_menu_changed();
 	_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) {
 void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
 	if (p_idx < 0) {
 	if (p_idx < 0) {
 		p_idx += get_item_count();
 		p_idx += get_item_count();
@@ -1314,6 +1387,11 @@ Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const {
 	return items[p_idx].icon;
 	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 {
 Key PopupMenu::get_item_accelerator(int p_idx) const {
 	ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE);
 	ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE);
 	return items[p_idx].accel;
 	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_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_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", "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_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_id", "index", "id"), &PopupMenu::set_item_id);
 	ClassDB::bind_method(D_METHOD("set_item_accelerator", "index", "accel"), &PopupMenu::set_item_accelerator);
 	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_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_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", "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("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_id", "index"), &PopupMenu::get_item_id);
 	ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index);
 	ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index);

+ 5 - 0
scene/gui/popup_menu.h

@@ -42,6 +42,7 @@ class PopupMenu : public Popup {
 
 
 	struct Item {
 	struct Item {
 		Ref<Texture2D> icon;
 		Ref<Texture2D> icon;
+		int icon_max_width = 0;
 		String text;
 		String text;
 		String xl_text;
 		String xl_text;
 		Ref<TextLine> text_buf;
 		Ref<TextLine> text_buf;
@@ -103,6 +104,7 @@ class PopupMenu : public Popup {
 
 
 	int _get_item_height(int p_item) const;
 	int _get_item_height(int p_item) const;
 	int _get_items_total_height() const;
 	int _get_items_total_height() const;
+	Size2 _get_item_icon_size(int p_item) const;
 
 
 	void _shape_item(int p_item);
 	void _shape_item(int p_item);
 
 
@@ -144,6 +146,7 @@ class PopupMenu : public Popup {
 		int indent = 0;
 		int indent = 0;
 		int item_start_padding = 0;
 		int item_start_padding = 0;
 		int item_end_padding = 0;
 		int item_end_padding = 0;
+		int icon_max_width = 0;
 
 
 		Ref<Texture2D> checked;
 		Ref<Texture2D> checked;
 		Ref<Texture2D> checked_disabled;
 		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_text_direction(int p_idx, Control::TextDirection p_text_direction);
 	void set_item_language(int p_idx, const String &p_language);
 	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(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_checked(int p_idx, bool p_checked);
 	void set_item_id(int p_idx, int p_id);
 	void set_item_id(int p_idx, int p_id);
 	void set_item_accelerator(int p_idx, Key p_accel);
 	void set_item_accelerator(int p_idx, Key p_accel);
@@ -245,6 +249,7 @@ public:
 	String get_item_language(int p_idx) const;
 	String get_item_language(int p_idx) const;
 	int get_item_idx_from_text(const String &text) const;
 	int get_item_idx_from_text(const String &text) const;
 	Ref<Texture2D> get_item_icon(int p_idx) 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;
 	bool is_item_checked(int p_idx) const;
 	int get_item_id(int p_idx) const;
 	int get_item_id(int p_idx) const;
 	int get_item_index(int p_id) const;
 	int get_item_index(int p_id) const;

+ 64 - 10
scene/gui/tab_bar.cpp

@@ -63,10 +63,10 @@ Size2 TabBar::get_minimum_size() const {
 		}
 		}
 		ms.width += style->get_minimum_size().width;
 		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()) {
 		if (!tabs[i].text.is_empty()) {
@@ -304,6 +304,7 @@ void TabBar::_update_theme_item_cache() {
 	Control::_update_theme_item_cache();
 	Control::_update_theme_item_cache();
 
 
 	theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
 	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_unselected_style = get_theme_stylebox(SNAME("tab_unselected"));
 	theme_cache.tab_selected_style = get_theme_stylebox(SNAME("tab_selected"));
 	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.
 	// Draw the icon.
 	Ref<Texture2D> icon = tabs[p_index].icon;
 	Ref<Texture2D> icon = tabs[p_index].icon;
 	if (icon.is_valid()) {
 	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.
 	// Draw the text.
@@ -719,6 +722,29 @@ Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const {
 	return tabs[p_tab].icon;
 	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) {
 void TabBar::set_tab_disabled(int p_tab, bool p_disabled) {
 	ERR_FAIL_INDEX(p_tab, tabs.size());
 	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);
 	HBoxContainer *drag_preview = memnew(HBoxContainer);
 
 
 	if (!tabs[tab_over].icon.is_null()) {
 	if (!tabs[tab_over].icon.is_null()) {
+		const Size2 icon_size = _get_tab_icon_size(tab_over);
+
 		TextureRect *tf = memnew(TextureRect);
 		TextureRect *tf = memnew(TextureRect);
 		tf->set_texture(tabs[tab_over].icon);
 		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);
 		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;
 	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()) {
 	if (!tabs[p_idx].text.is_empty()) {
@@ -1305,6 +1336,27 @@ int TabBar::get_tab_width(int p_idx) const {
 	return x;
 	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() {
 void TabBar::_ensure_no_over_offset() {
 	if (!is_inside_tree() || !buttons_visible) {
 	if (!is_inside_tree() || !buttons_visible) {
 		return;
 		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("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("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("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("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("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);
 	ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabBar::set_tab_disabled);

+ 7 - 0
scene/gui/tab_bar.h

@@ -62,6 +62,8 @@ private:
 
 
 		Ref<TextLine> text_buf;
 		Ref<TextLine> text_buf;
 		Ref<Texture2D> icon;
 		Ref<Texture2D> icon;
+		int icon_max_width = 0;
+
 		bool disabled = false;
 		bool disabled = false;
 		bool hidden = false;
 		bool hidden = false;
 		int ofs_cache = 0;
 		int ofs_cache = 0;
@@ -106,6 +108,7 @@ private:
 
 
 	struct ThemeCache {
 	struct ThemeCache {
 		int h_separation = 0;
 		int h_separation = 0;
+		int icon_max_width = 0;
 
 
 		Ref<StyleBox> tab_unselected_style;
 		Ref<StyleBox> tab_unselected_style;
 		Ref<StyleBox> tab_selected_style;
 		Ref<StyleBox> tab_selected_style;
@@ -133,6 +136,7 @@ private:
 	} theme_cache;
 	} theme_cache;
 
 
 	int get_tab_width(int p_idx) const;
 	int get_tab_width(int p_idx) const;
+	Size2 _get_tab_icon_size(int p_idx) const;
 	void _ensure_no_over_offset();
 	void _ensure_no_over_offset();
 
 
 	void _update_hover();
 	void _update_hover();
@@ -171,6 +175,9 @@ public:
 	void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon);
 	void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon);
 	Ref<Texture2D> get_tab_icon(int p_tab) const;
 	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);
 	void set_tab_disabled(int p_tab, bool p_disabled);
 	bool is_tab_disabled(int p_tab) const;
 	bool is_tab_disabled(int p_tab) const;
 
 

+ 2 - 0
scene/gui/tab_container.cpp

@@ -146,6 +146,7 @@ void TabContainer::_update_theme_item_cache() {
 
 
 	// TabBar overrides.
 	// TabBar overrides.
 	theme_cache.icon_separation = get_theme_constant(SNAME("icon_separation"));
 	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.outline_size = get_theme_constant(SNAME("outline_size"));
 
 
 	theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected"));
 	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_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("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);
 	tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size);
 
 
 	_update_margins();
 	_update_margins();

+ 1 - 0
scene/gui/tab_container.h

@@ -59,6 +59,7 @@ class TabContainer : public Container {
 
 
 		// TabBar overrides.
 		// TabBar overrides.
 		int icon_separation = 0;
 		int icon_separation = 0;
+		int icon_max_width = 0;
 		int outline_size = 0;
 		int outline_size = 0;
 
 
 		Ref<StyleBox> tab_unselected_style;
 		Ref<StyleBox> tab_unselected_style;

+ 28 - 26
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;
 			size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.h_separation;
 		}
 		}
 		if (cell.icon.is_valid()) {
 		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.width += icon_size.width + parent_tree->theme_cache.h_separation;
 			size.height = MAX(size.height, icon_size.height);
 			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.v_separation = get_theme_constant(SNAME("v_separation"));
 	theme_cache.item_margin = get_theme_constant(SNAME("item_margin"));
 	theme_cache.item_margin = get_theme_constant(SNAME("item_margin"));
 	theme_cache.button_margin = get_theme_constant(SNAME("button_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_color = get_theme_color(SNAME("font_outline_color"));
 	theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size"));
 	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();
 	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 {
 int Tree::compute_item_height(TreeItem *p_item) const {
 	if ((p_item == root && hide_root) || !p_item->is_visible()) {
 	if ((p_item == root && hide_root) || !p_item->is_visible()) {
 		return 0;
 		return 0;
@@ -1688,10 +1705,7 @@ int Tree::compute_item_height(TreeItem *p_item) const {
 			case TreeItem::CELL_MODE_ICON: {
 			case TreeItem::CELL_MODE_ICON: {
 				Ref<Texture2D> icon = p_item->cells[i].icon;
 				Ref<Texture2D> icon = p_item->cells[i].icon;
 				if (!icon.is_null()) {
 				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) {
 					if (s.height > height) {
 						height = s.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;
 	int w = 0;
 	if (!p_cell.icon.is_null()) {
 	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;
 		w += bmsize.width + theme_cache.h_separation;
 		if (rect.size.width > 0 && (w + ts.width) > rect.size.width) {
 		if (rect.size.width > 0 && (w + ts.width) > rect.size.width) {
 			ts.width = rect.size.width - w;
 			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()) {
 	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);
 		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;
 		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()) {
 					if (p_item->cells[i].icon.is_null()) {
 						break;
 						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;
 					Point2i icon_ofs = (item_rect.size - icon_size) / 2;
 					icon_ofs += item_rect.position;
 					icon_ofs += item_rect.position;
 
 
@@ -3795,8 +3796,9 @@ bool Tree::edit_selected() {
 		popup_rect.size = rect.size;
 		popup_rect.size = rect.size;
 
 
 		// Account for icon.
 		// 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->clear();
 		text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step)));
 		text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step)));

+ 5 - 1
scene/gui/tree.h

@@ -530,21 +530,24 @@ private:
 		Color font_outline_color;
 		Color font_outline_color;
 
 
 		float base_scale = 1.0;
 		float base_scale = 1.0;
+		int font_outline_size = 0;
 
 
 		int h_separation = 0;
 		int h_separation = 0;
 		int v_separation = 0;
 		int v_separation = 0;
 		int item_margin = 0;
 		int item_margin = 0;
 		int button_margin = 0;
 		int button_margin = 0;
+		int icon_max_width = 0;
 		Point2 offset;
 		Point2 offset;
+
 		int draw_relationship_lines = 0;
 		int draw_relationship_lines = 0;
 		int relationship_line_width = 0;
 		int relationship_line_width = 0;
 		int parent_hl_line_width = 0;
 		int parent_hl_line_width = 0;
 		int children_hl_line_width = 0;
 		int children_hl_line_width = 0;
 		int parent_hl_line_margin = 0;
 		int parent_hl_line_margin = 0;
 		int draw_guides = 0;
 		int draw_guides = 0;
+
 		int scroll_border = 0;
 		int scroll_border = 0;
 		int scroll_speed = 0;
 		int scroll_speed = 0;
-		int font_outline_size = 0;
 	} theme_cache;
 	} theme_cache;
 
 
 	struct Cache {
 	struct Cache {
@@ -573,6 +576,7 @@ private:
 	} cache;
 	} cache;
 
 
 	int _get_title_button_height() const;
 	int _get_title_button_height() const;
+	Size2 _get_cell_icon_size(const TreeItem::Cell &p_cell) const;
 
 
 	void _scroll_moved(float p_value);
 	void _scroll_moved(float p_value);
 	HScrollBar *h_scroll = nullptr;
 	HScrollBar *h_scroll = nullptr;

+ 5 - 0
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_color("icon_disabled_color", "Button", Color(1, 1, 1, 0.4));
 
 
 	theme->set_constant("h_separation", "Button", 2 * scale);
 	theme->set_constant("h_separation", "Button", 2 * scale);
+	theme->set_constant("icon_max_width", "Button", 0);
 
 
 	// MenuBar
 	// MenuBar
 	theme->set_stylebox("normal", "MenuBar", button_normal);
 	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("separator_outline_size", "PopupMenu", 0);
 	theme->set_constant("item_start_padding", "PopupMenu", 2 * scale);
 	theme->set_constant("item_start_padding", "PopupMenu", 2 * scale);
 	theme->set_constant("item_end_padding", "PopupMenu", 2 * scale);
 	theme->set_constant("item_end_padding", "PopupMenu", 2 * scale);
+	theme->set_constant("icon_max_width", "PopupMenu", 0);
 
 
 	// GraphNode
 	// GraphNode
 	Ref<StyleBoxFlat> graphnode_normal = make_flat_stylebox(style_normal_color, 18, 42, 18, 12);
 	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_border", "Tree", 4);
 	theme->set_constant("scroll_speed", "Tree", 12);
 	theme->set_constant("scroll_speed", "Tree", 12);
 	theme->set_constant("outline_size", "Tree", 0);
 	theme->set_constant("outline_size", "Tree", 0);
+	theme->set_constant("icon_max_width", "Tree", 0);
 
 
 	// ItemList
 	// 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("side_margin", "TabContainer", 8 * scale);
 	theme->set_constant("icon_separation", "TabContainer", 4 * scale);
 	theme->set_constant("icon_separation", "TabContainer", 4 * scale);
+	theme->set_constant("icon_max_width", "TabContainer", 0);
 	theme->set_constant("outline_size", "TabContainer", 0);
 	theme->set_constant("outline_size", "TabContainer", 0);
 
 
 	// TabBar
 	// 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_color("drop_mark_color", "TabBar", Color(1, 1, 1));
 
 
 	theme->set_constant("h_separation", "TabBar", 4 * scale);
 	theme->set_constant("h_separation", "TabBar", 4 * scale);
+	theme->set_constant("icon_max_width", "TabBar", 0);
 	theme->set_constant("outline_size", "TabBar", 0);
 	theme->set_constant("outline_size", "TabBar", 0);
 
 
 	// Separators
 	// Separators