Merge pull request #75472 from YuriSizov/editor-iconography

Improve editor support for icons of custom, scripted, and GDExtension classes
This commit is contained in:
Rémi Verschelde 2023-04-01 11:39:42 +02:00 committed by GitHub
commit 21d080ead4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 527 additions and 228 deletions

View File

@ -549,6 +549,15 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String
return Ref<Resource>();
}
// Handle icons if any are specified.
if (config->has_section("icons")) {
List<String> keys;
config->get_section_keys("icons", &keys);
for (const String &key : keys) {
lib->class_icon_paths[key] = config->get_value("icons", key);
}
}
return lib;
}

View File

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

View File

@ -50,6 +50,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &
extension->initialize_library(GDExtension::InitializationLevel(i));
}
}
for (const KeyValue<String, String> &kv : extension->class_icon_paths) {
gdextension_class_icon_paths[kv.key] = kv.value;
}
gdextension_map[p_path] = extension;
return LOAD_STATUS_OK;
}
@ -74,6 +79,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String
extension->deinitialize_library(GDExtension::InitializationLevel(i));
}
}
for (const KeyValue<String, String> &kv : extension->class_icon_paths) {
gdextension_class_icon_paths.erase(kv.key);
}
gdextension_map.erase(p_path);
return LOAD_STATUS_OK;
}
@ -95,6 +105,19 @@ Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) {
return E->value;
}
bool GDExtensionManager::class_has_icon_path(const String &p_class) const {
// TODO: Check that the icon belongs to a registered class somehow.
return gdextension_class_icon_paths.has(p_class);
}
String GDExtensionManager::class_get_icon_path(const String &p_class) const {
// TODO: Check that the icon belongs to a registered class somehow.
if (gdextension_class_icon_paths.has(p_class)) {
return gdextension_class_icon_paths[p_class];
}
return "";
}
void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
ERR_FAIL_COND(int32_t(p_level) - 1 != level);
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {

View File

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

View File

@ -48,7 +48,7 @@
When this property is enabled, text that is too large to fit the button is clipped, when disabled the Button will always be wide enough to hold the text.
</member>
<member name="expand_icon" type="bool" setter="set_expand_icon" getter="is_expand_icon" default="false">
When enabled, the button's icon will expand/shrink to fit the button's size while keeping its aspect.
When enabled, the button's icon will expand/shrink to fit the button's size while keeping its aspect. See also [theme_item icon_max_width].
</member>
<member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false">
Flat buttons don't display decoration.
@ -116,6 +116,9 @@
<theme_item name="h_separation" data_type="constant" type="int" default="2">
The horizontal space between [Button]'s icon and text. Negative values will be treated as [code]0[/code] when used.
</theme_item>
<theme_item name="icon_max_width" data_type="constant" type="int" default="0">
The maximum allowed width of the [Button]'s icon. This limit is applied on top of the default size of the icon, or its expanded size if [member expand_icon] is [code]true[/code]. The height is adjusted according to the icon's ratio.
</theme_item>
<theme_item name="outline_size" data_type="constant" type="int" default="0">
The size of the text outline.
[b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended.

View File

@ -202,6 +202,13 @@
Returns the icon of the item at the given [param index].
</description>
</method>
<method name="get_item_icon_max_width" qualifiers="const">
<return type="int" />
<param index="0" name="index" type="int" />
<description>
Returns the maximum allowed width of the icon for the item at the given [param index].
</description>
</method>
<method name="get_item_id" qualifiers="const">
<return type="int" />
<param index="0" name="index" type="int" />
@ -397,6 +404,14 @@
Replaces the [Texture2D] icon of the item at the given [param index].
</description>
</method>
<method name="set_item_icon_max_width">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="width" type="int" />
<description>
Sets the maximum allowed width of the icon for the item at the given [param index]. This limit is applied on top of the default size of the icon and on top of [theme_item icon_max_width]. The height is adjusted according to the icon's ratio.
</description>
</method>
<method name="set_item_id">
<return type="void" />
<param index="0" name="index" type="int" />
@ -573,6 +588,9 @@
<theme_item name="h_separation" data_type="constant" type="int" default="4">
The horizontal space between the item's elements.
</theme_item>
<theme_item name="icon_max_width" data_type="constant" type="int" default="0">
The maximum allowed width of the item's icon. This limit is applied on top of the default size of the icon, but before the value set with [method set_item_icon_max_width]. The height is adjusted according to the icon's ratio.
</theme_item>
<theme_item name="indent" data_type="constant" type="int" default="10">
Width of the single indentation level.
</theme_item>

View File

@ -46,14 +46,21 @@
<return type="Texture2D" />
<param index="0" name="tab_idx" type="int" />
<description>
Returns the [Texture2D] for the right button of the tab at index [param tab_idx] or [code]null[/code] if the button has no [Texture2D].
Returns the icon for the right button of the tab at index [param tab_idx] or [code]null[/code] if the right button has no icon.
</description>
</method>
<method name="get_tab_icon" qualifiers="const">
<return type="Texture2D" />
<param index="0" name="tab_idx" type="int" />
<description>
Returns the [Texture2D] for the tab at index [param tab_idx] or [code]null[/code] if the tab has no [Texture2D].
Returns the icon for the tab at index [param tab_idx] or [code]null[/code] if the tab has no icon.
</description>
</method>
<method name="get_tab_icon_max_width" qualifiers="const">
<return type="int" />
<param index="0" name="tab_idx" type="int" />
<description>
Returns the maximum allowed width of the icon for the tab at index [param tab_idx].
</description>
</method>
<method name="get_tab_idx_at_point" qualifiers="const">
@ -158,6 +165,14 @@
Sets an [param icon] for the tab at index [param tab_idx].
</description>
</method>
<method name="set_tab_icon_max_width">
<return type="void" />
<param index="0" name="tab_idx" type="int" />
<param index="1" name="width" type="int" />
<description>
Sets the maximum allowed width of the icon for the tab at index [param tab_idx]. This limit is applied on top of the default size of the icon and on top of [theme_item icon_max_width]. The height is adjusted according to the icon's ratio.
</description>
</method>
<method name="set_tab_language">
<return type="void" />
<param index="0" name="tab_idx" type="int" />
@ -323,6 +338,9 @@
<theme_item name="h_separation" data_type="constant" type="int" default="4">
The horizontal separation between the elements inside tabs.
</theme_item>
<theme_item name="icon_max_width" data_type="constant" type="int" default="0">
The maximum allowed width of the tab's icon. This limit is applied on top of the default size of the icon, but before the value set with [method set_tab_icon_max_width]. The height is adjusted according to the icon's ratio.
</theme_item>
<theme_item name="outline_size" data_type="constant" type="int" default="0">
The size of the tab text outline.
[b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended.

View File

@ -209,6 +209,9 @@
<theme_item name="font_unselected_color" data_type="color" type="Color" default="Color(0.7, 0.7, 0.7, 1)">
Font color of the other, unselected tabs.
</theme_item>
<theme_item name="icon_max_width" data_type="constant" type="int" default="0">
The maximum allowed width of the tab's icon. This limit is applied on top of the default size of the icon, but before the value set with [method TabBar.set_tab_icon_max_width]. The height is adjusted according to the icon's ratio.
</theme_item>
<theme_item name="icon_separation" data_type="constant" type="int" default="4">
Space between tab's name and its icon.
</theme_item>

View File

@ -518,6 +518,9 @@
<theme_item name="h_separation" data_type="constant" type="int" default="4">
The horizontal space between item cells. This is also used as the margin at the start of an item when folding is disabled.
</theme_item>
<theme_item name="icon_max_width" data_type="constant" type="int" default="0">
The maximum allowed width of the icon in item's cells. This limit is applied on top of the default size of the icon, but before the value set with [method TreeItem.set_icon_max_width]. The height is adjusted according to the icon's ratio.
</theme_item>
<theme_item name="item_margin" data_type="constant" type="int" default="16">
The horizontal margin at the start of an item. This is used when folding is enabled for the item.
</theme_item>

View File

@ -183,7 +183,7 @@
<return type="int" />
<param index="0" name="column" type="int" />
<description>
Returns the column's icon's maximum width.
Returns the maximum allowed width of the icon in the given [param column].
</description>
</method>
<method name="get_icon_modulate" qualifiers="const">
@ -545,7 +545,7 @@
<param index="0" name="column" type="int" />
<param index="1" name="width" type="int" />
<description>
Sets the given column's icon's maximum width.
Sets the maximum allowed width of the icon in the given [param column]. This limit is applied on top of the default size of the icon and on top of [theme_item Tree.icon_max_width]. The height is adjusted according to the icon's ratio.
</description>
</method>
<method name="set_icon_modulate">

View File

@ -462,6 +462,11 @@ void CreateDialog::_notification(int p_what) {
} break;
case NOTIFICATION_THEME_CHANGED: {
const int icon_width = get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"));
search_options->add_theme_constant_override("icon_max_width", icon_width);
favorites->add_theme_constant_override("icon_max_width", icon_width);
recent->set_fixed_icon_size(Size2(icon_width, icon_width));
_update_theme();
} break;
}

View File

@ -31,10 +31,13 @@
#include "editor_data.h"
#include "core/config/project_settings.h"
#include "core/extension/gdextension_manager.h"
#include "core/io/file_access.h"
#include "core/io/image_loader.h"
#include "core/io/resource_loader.h"
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "editor/editor_scale.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/script_editor_plugin.h"
#include "scene/resources/packed_scene.h"
@ -457,10 +460,10 @@ void EditorData::add_custom_type(const String &p_type, const String &p_inherits,
ct.name = p_type;
ct.icon = p_icon;
ct.script = p_script;
if (!custom_types.has(p_inherits)) {
custom_types[p_inherits] = Vector<CustomType>();
}
custom_types[p_inherits].push_back(ct);
}
@ -1028,8 +1031,66 @@ void EditorData::script_class_load_icon_paths() {
}
}
Ref<Texture2D> EditorData::extension_class_get_icon(const String &p_class) const {
if (GDExtensionManager::get_singleton()->class_has_icon_path(p_class)) {
String icon_path = GDExtensionManager::get_singleton()->class_get_icon_path(p_class);
Ref<Texture2D> icon = _load_script_icon(icon_path);
if (icon.is_valid()) {
return icon;
}
}
return nullptr;
}
Ref<Texture2D> EditorData::_load_script_icon(const String &p_path) const {
if (!p_path.is_empty() && ResourceLoader::exists(p_path)) {
Ref<Texture2D> icon = ResourceLoader::load(p_path);
if (icon.is_valid()) {
return icon;
}
}
return nullptr;
}
Ref<Texture2D> EditorData::get_script_icon(const Ref<Script> &p_script) {
// Take from the local cache, if available.
if (_script_icon_cache.has(p_script) && _script_icon_cache[p_script].is_valid()) {
return _script_icon_cache[p_script];
}
Ref<Script> base_scr = p_script;
while (base_scr.is_valid()) {
// Check for scripted classes.
StringName class_name = script_class_get_name(base_scr->get_path());
String icon_path = script_class_get_icon_path(class_name);
Ref<Texture2D> icon = _load_script_icon(icon_path);
if (icon.is_valid()) {
_script_icon_cache[p_script] = icon;
return icon;
}
// Check for legacy custom classes defined by plugins.
// TODO: Should probably be deprecated in 4.x
const EditorData::CustomType *ctype = get_custom_type_by_path(base_scr->get_path());
if (ctype && ctype->icon.is_valid()) {
_script_icon_cache[p_script] = ctype->icon;
return ctype->icon;
}
// Move to the base class.
base_scr = base_scr->get_base_script();
}
// If no icon found, cache it as null.
_script_icon_cache[p_script] = Ref<Texture>();
return nullptr;
}
void EditorData::clear_script_icon_cache() {
_script_icon_cache.clear();
}
EditorData::EditorData() {
current_edited_scene = -1;
undo_redo_manager = memnew(EditorUndoRedoManager);
script_class_load_icon_paths();
}

View File

@ -144,6 +144,9 @@ private:
HashMap<StringName, String> _script_class_icon_paths;
HashMap<String, StringName> _script_class_file_to_path;
HashMap<Ref<Script>, Ref<Texture>> _script_icon_cache;
Ref<Texture2D> _load_script_icon(const String &p_path) const;
public:
EditorPlugin *get_editor(Object *p_object);
@ -240,6 +243,11 @@ public:
void script_class_save_icon_paths();
void script_class_load_icon_paths();
Ref<Texture2D> extension_class_get_icon(const String &p_class) const;
Ref<Texture2D> get_script_icon(const Ref<Script> &p_script);
void clear_script_icon_cache();
EditorData();
~EditorData();
};

View File

@ -263,16 +263,8 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) {
class_desc->pop();
}
void EditorHelp::_add_type_icon(const String &p_type, int p_size) {
Ref<Texture2D> icon;
if (has_theme_icon(p_type, SNAME("EditorIcons"))) {
icon = get_theme_icon(p_type, SNAME("EditorIcons"));
} else if (ClassDB::class_exists(p_type) && ClassDB::is_parent_class(p_type, "Object")) {
icon = get_theme_icon(SNAME("Object"), SNAME("EditorIcons"));
} else {
icon = get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"));
}
void EditorHelp::_add_type_icon(const String &p_type, int p_size, const String &p_fallback) {
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(p_type, p_fallback);
Vector2i size = Vector2i(icon->get_width(), icon->get_height());
if (p_size > 0) {
// Ensures icon scales proportionally on both axis, based on icon height.
@ -644,7 +636,7 @@ void EditorHelp::_update_doc() {
section_line.push_back(Pair<String, int>(TTR("Top"), 0));
_push_title_font();
class_desc->add_text(TTR("Class:") + " ");
_add_type_icon(edited_class, theme_cache.doc_title_font_size);
_add_type_icon(edited_class, theme_cache.doc_title_font_size, "Object");
class_desc->add_text(" ");
class_desc->push_color(theme_cache.headline_color);
_add_text(edited_class);
@ -676,7 +668,7 @@ void EditorHelp::_update_doc() {
String inherits = cd.inherits;
while (!inherits.is_empty()) {
_add_type_icon(inherits);
_add_type_icon(inherits, theme_cache.doc_font_size, "ArrowRight");
class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type().
_add_type(inherits);
@ -709,7 +701,7 @@ void EditorHelp::_update_doc() {
if (prev) {
class_desc->add_text(" , ");
}
_add_type_icon(E.value.name);
_add_type_icon(E.value.name, theme_cache.doc_font_size, "ArrowRight");
class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type().
_add_type(E.value.name);
prev = true;

View File

@ -159,7 +159,7 @@ class EditorHelp : public VBoxContainer {
//void _button_pressed(int p_idx);
void _add_type(const String &p_type, const String &p_enum = String());
void _add_type_icon(const String &p_type, int p_size = 0);
void _add_type_icon(const String &p_type, int p_size = 0, const String &p_fallback = "");
void _add_method(const DocData::MethodDoc &p_method, bool p_overview = true);
void _add_bulletpoint();

View File

@ -1134,17 +1134,21 @@ void EditorInspectorCategory::_notification(int p_what) {
int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
int hs = get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
int icon_size = get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"));
int w = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
if (icon.is_valid()) {
w += hs + icon->get_width();
w += hs + icon_size;
}
int ofs = (get_size().width - w) / 2;
if (icon.is_valid()) {
draw_texture(icon, Point2(ofs, (get_size().height - icon->get_height()) / 2).floor());
ofs += hs + icon->get_width();
Size2 rect_size = Size2(icon_size, icon_size);
Point2 rect_pos = Point2(ofs, (get_size().height - icon_size) / 2).floor();
draw_texture_rect(icon, Rect2(rect_pos, rect_size));
ofs += hs + icon_size;
}
Color color = get_theme_color(SNAME("font_color"), SNAME("Tree"));
@ -2753,46 +2757,28 @@ void EditorInspector::update_tree() {
String label = p.name;
doc_name = p.name;
// Set the category icon.
// Use category's owner script to update some of its information.
if (!EditorNode::get_editor_data().is_type_recognized(type) && p.hint_string.length() && FileAccess::exists(p.hint_string)) {
// If we have a category inside a script, search for the first script with a valid icon.
StringName script_name;
Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script");
StringName base_type;
StringName name;
if (scr.is_valid()) {
base_type = scr->get_instance_base_type();
name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());
script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());
// Update the docs reference and the label based on the script.
Vector<DocData::ClassDoc> docs = scr->get_documentation();
if (!docs.is_empty()) {
doc_name = docs[0].name;
}
if (name != StringName() && label != name) {
label = name;
if (script_name != StringName() && label != script_name) {
label = script_name;
}
}
while (scr.is_valid()) {
name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
if (name != StringName() && !icon_path.is_empty()) {
category->icon = ResourceLoader::load(icon_path, "Texture");
break;
}
const EditorData::CustomType *ctype = EditorNode::get_editor_data().get_custom_type_by_path(scr->get_path());
if (ctype) {
category->icon = ctype->icon;
break;
}
scr = scr->get_base_script();
}
if (category->icon.is_null() && has_theme_icon(base_type, SNAME("EditorIcons"))) {
category->icon = get_theme_icon(base_type, SNAME("EditorIcons"));
}
}
if (category->icon.is_null()) {
if (!type.is_empty()) { // Can happen for built-in scripts.
category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object");
}
// Find the corresponding icon.
category->icon = EditorNode::get_singleton()->get_class_icon(script_name, "Object");
} else if (!type.is_empty()) {
category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object");
}
// Set the category label.

View File

@ -34,7 +34,6 @@
#include "core/input/input.h"
#include "core/io/config_file.h"
#include "core/io/file_access.h"
#include "core/io/image_loader.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/class_db.h"
@ -682,10 +681,6 @@ void EditorNode::_notification(int p_what) {
editor_data.clear_edited_scenes();
} break;
case Control::NOTIFICATION_THEME_CHANGED: {
scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
} break;
case NOTIFICATION_READY: {
{
_initializing_plugins = true;
@ -773,6 +768,9 @@ void EditorNode::_notification(int p_what) {
bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), SNAME("EditorStyles")));
tabbar_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("tabbar_background"), SNAME("TabContainer")));
scene_tabs->add_theme_constant_override("icon_max_width", gui_base->get_theme_constant(SNAME("class_icon_size"), SNAME("Editor")));
scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
}
@ -3682,7 +3680,7 @@ void EditorNode::_remove_edited_scene(bool p_change_tab) {
void EditorNode::_remove_scene(int index, bool p_change_tab) {
// Clear icon cache in case some scripts are no longer needed.
script_icon_cache.clear();
editor_data.clear_script_icon_cache();
if (editor_data.get_edited_scene() == index) {
// Scene to remove is current scene.
@ -4446,18 +4444,6 @@ StringName EditorNode::get_object_custom_type_name(const Object *p_object) const
return StringName();
}
Ref<ImageTexture> EditorNode::_load_custom_class_icon(const String &p_path) const {
if (p_path.length()) {
Ref<Image> img = memnew(Image);
Error err = ImageLoader::load_image(p_path, img);
if (err == OK) {
img->resize(16 * EDSCALE, 16 * EDSCALE, Image::INTERPOLATE_LANCZOS);
return ImageTexture::create_from_image(img);
}
}
return nullptr;
}
void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_name) {
if (p_custom_action_name == "select_current") {
Node *scene = editor_data.get_edited_scene_root();
@ -4480,106 +4466,86 @@ void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_na
}
}
Ref<Texture2D> EditorNode::_get_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();
// 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;
}
// 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);
// 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;
}
// 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");
}
}
// 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;
}
// 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;
}
// Look up the class name or the fallback name in the editor theme.
// This is only relevant for built-in classes.
if (gui_base) {
if (gui_base->has_theme_icon(p_class, SNAME("EditorIcons"))) {
return gui_base->get_theme_icon(p_class, SNAME("EditorIcons"));
}
if (p_fallback.length() && gui_base->has_theme_icon(p_fallback, SNAME("EditorIcons"))) {
return gui_base->get_theme_icon(p_fallback, SNAME("EditorIcons"));
}
}
return nullptr;
}
Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String &p_fallback) {
ERR_FAIL_COND_V(!p_object || !gui_base, nullptr);
ERR_FAIL_NULL_V_MSG(p_object, nullptr, "Object cannot be null.");
Ref<Script> scr = p_object->get_script();
if (scr.is_null() && p_object->is_class("Script")) {
scr = p_object;
}
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;
}
// 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();
}
// 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];
}
// TODO: Should probably be deprecated in 4.x.
if (p_object->has_meta("_editor_icon")) {
return p_object->get_meta("_editor_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"));
}
if (p_fallback.length()) {
return gui_base->get_theme_icon(p_fallback, SNAME("EditorIcons"));
}
return nullptr;
return _get_class_or_script_icon(p_object->get_class(), scr, p_fallback);
}
Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) const {
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.");
Ref<Script> scr;
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());
}
scr = EditorNode::get_editor_data().script_class_load_script(p_class);
}
if (const EditorData::CustomType *ctype = EditorNode::get_editor_data().get_custom_type_by_name(p_class)) {
return ctype->icon;
}
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 _get_class_or_script_icon(p_class, scr, p_fallback);
}
bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringName &p_class) {
@ -7159,6 +7125,7 @@ EditorNode::EditorNode() {
scene_tabs->set_select_with_rmb(true);
scene_tabs->add_tab("unsaved");
scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button").operator int());
scene_tabs->add_theme_constant_override("icon_max_width", gui_base->get_theme_constant(SNAME("class_icon_size"), SNAME("Editor")));
scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE);
scene_tabs->set_drag_to_rearrange_enabled(true);
scene_tabs->set_auto_translate(false);

View File

@ -513,7 +513,6 @@ private:
PrintHandlerList print_handler;
HashMap<String, Ref<Texture2D>> icon_type_cache;
HashMap<Ref<Script>, Ref<Texture>> script_icon_cache;
static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS];
static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS];
@ -697,7 +696,8 @@ private:
void _feature_profile_changed();
bool _is_class_editor_disabled_by_feature_profile(const StringName &p_class);
Ref<ImageTexture> _load_custom_class_icon(const String &p_path) const;
Ref<Texture2D> _get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback = "Object");
void _pick_main_scene_custom_action(const String &p_custom_action_name);
@ -879,7 +879,7 @@ public:
Ref<Script> get_object_custom_type_base(const Object *p_object) const;
StringName get_object_custom_type_name(const Object *p_object) const;
Ref<Texture2D> get_object_icon(const Object *p_object, const String &p_fallback = "Object");
Ref<Texture2D> get_class_icon(const String &p_class, const String &p_fallback = "Object") const;
Ref<Texture2D> get_class_icon(const String &p_class, const String &p_fallback = "Object");
bool is_object_of_custom_type(const Object *p_object, const StringName &p_class);

View File

@ -201,8 +201,12 @@ void EditorPath::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
update_path();
sub_objects_icon->set_texture(get_theme_icon(SNAME("arrow"), SNAME("OptionButton")));
int icon_size = get_theme_constant(SNAME("class_icon_size"), SNAME("Editor"));
current_object_icon->set_custom_minimum_size(Size2(icon_size, icon_size));
current_object_label->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts")));
sub_objects_icon->set_texture(get_theme_icon(SNAME("arrow"), SNAME("OptionButton")));
sub_objects_menu->add_theme_constant_override("icon_max_width", icon_size);
} break;
case NOTIFICATION_READY: {
@ -227,7 +231,8 @@ EditorPath::EditorPath(EditorSelectionHistory *p_history) {
main_mc->add_child(main_hb);
current_object_icon = memnew(TextureRect);
current_object_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
current_object_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
current_object_icon->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
main_hb->add_child(current_object_icon);
current_object_label = memnew(Label);

View File

@ -69,10 +69,9 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) {
for (int i = 0; i < p_efsd->get_file_count(); i++) {
String file = p_efsd->get_file_path(i);
String engine_type = p_efsd->get_file_type(i);
String script_type = p_efsd->get_file_resource_script_class(i);
String actual_type = script_type.is_empty() ? engine_type : script_type;
// Iterate all possible base types.
for (String &parent_type : base_types) {
if (ClassDB::is_parent_class(engine_type, parent_type) || EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type)) {
@ -81,7 +80,7 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) {
// Store refs to used icons.
String ext = file.get_extension();
if (!icons.has(ext)) {
icons.insert(ext, get_theme_icon((has_theme_icon(actual_type, SNAME("EditorIcons")) ? actual_type : "Object"), SNAME("EditorIcons")));
icons.insert(ext, EditorNode::get_singleton()->get_class_icon(actual_type, "Object"));
}
// Stop testing base types as soon as we got a match.

View File

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

View File

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

View File

@ -1278,13 +1278,6 @@ void SceneTreeDock::_notification(int p_what) {
spatial_editor_plugin->get_spatial_editor()->connect("item_lock_status_changed", callable_mp(scene_tree, &SceneTreeEditor::_update_tree).bind(false));
spatial_editor_plugin->get_spatial_editor()->connect("item_group_status_changed", callable_mp(scene_tree, &SceneTreeEditor::_update_tree).bind(false));
button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
button_instance->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
button_create_script->set_icon(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons")));
button_detach_script->set_icon(get_theme_icon(SNAME("ScriptRemove"), SNAME("EditorIcons")));
button_tree_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
filter->set_clear_button_enabled(true);
// create_root_dialog
@ -1366,19 +1359,35 @@ void SceneTreeDock::_notification(int p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
scene_tree->set_auto_expand_selected(EDITOR_GET("docks/scene_tree/auto_expand_to_selected"), false);
} break;
case NOTIFICATION_THEME_CHANGED: {
button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
button_instance->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")));
button_create_script->set_icon(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons")));
button_detach_script->set_icon(get_theme_icon(SNAME("ScriptRemove"), SNAME("EditorIcons")));
button_tree_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
button_2d->set_icon(get_theme_icon(SNAME("Node2D"), SNAME("EditorIcons")));
button_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons")));
button_ui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons")));
button_custom->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
button_clipboard->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")));
filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
filter->set_clear_button_enabled(true);
// These buttons are created on READY, because reasons...
if (button_2d) {
button_2d->set_icon(get_theme_icon(SNAME("Node2D"), SNAME("EditorIcons")));
}
if (button_3d) {
button_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons")));
}
if (button_ui) {
button_ui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons")));
}
if (button_custom) {
button_custom->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
}
if (button_clipboard) {
button_clipboard->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")));
}
menu_subresources->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), SNAME("Editor")));
} break;
case NOTIFICATION_PROCESS: {

View File

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

View File

@ -83,6 +83,7 @@ void Button::_update_theme_item_cache() {
theme_cache.icon = get_theme_icon(SNAME("icon"));
theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width"));
}
void Button::_notification(int p_what) {
@ -252,7 +253,6 @@ void Button::_notification(int p_what) {
float icon_ofs_region = 0.0;
Point2 style_offset;
Size2 icon_size = _icon->get_size();
if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
style_offset.x = style->get_margin(SIDE_LEFT);
if (_internal_margin[SIDE_LEFT] > 0) {
@ -268,6 +268,7 @@ void Button::_notification(int p_what) {
}
style_offset.y = style->get_margin(SIDE_TOP);
Size2 icon_size = _icon->get_size();
if (expand_icon) {
Size2 _size = get_size() - style->get_offset() * 2;
int icon_text_separation = text.is_empty() ? 0 : theme_cache.h_separation;
@ -285,6 +286,7 @@ void Button::_notification(int p_what) {
icon_size = Size2(icon_width, icon_height);
}
icon_size = _fit_icon_size(icon_size);
if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
icon_region = Rect2(style_offset + Point2(icon_ofs_region, Math::floor((valign - icon_size.y) * 0.5)), icon_size);
@ -365,6 +367,18 @@ void Button::_notification(int p_what) {
}
}
Size2 Button::_fit_icon_size(const Size2 &p_size) const {
int max_width = theme_cache.icon_max_width;
Size2 icon_size = p_size;
if (max_width > 0 && icon_size.width > max_width) {
icon_size.height = icon_size.height * max_width / icon_size.width;
icon_size.width = max_width;
}
return icon_size;
}
Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Texture2D> p_icon) const {
Ref<TextParagraph> paragraph;
if (p_text.is_empty()) {
@ -380,15 +394,16 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu
}
if (!expand_icon && p_icon.is_valid()) {
minsize.height = MAX(minsize.height, p_icon->get_height());
Size2 icon_size = _fit_icon_size(p_icon->get_size());
minsize.height = MAX(minsize.height, icon_size.height);
if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) {
minsize.width += p_icon->get_width();
minsize.width += icon_size.width;
if (!xl_text.is_empty() || !p_text.is_empty()) {
minsize.width += MAX(0, theme_cache.h_separation);
}
} else {
minsize.width = MAX(minsize.width, p_icon->get_width());
minsize.width = MAX(minsize.width, icon_size.width);
}
}

View File

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

View File

@ -47,6 +47,26 @@ String PopupMenu::_get_accel_text(const Item &p_item) const {
return String();
}
Size2 PopupMenu::_get_item_icon_size(int p_item) const {
const PopupMenu::Item &item = items[p_item];
Size2 icon_size = item.get_icon_size();
int max_width = 0;
if (theme_cache.icon_max_width > 0) {
max_width = theme_cache.icon_max_width;
}
if (item.icon_max_width > 0 && (max_width == 0 || item.icon_max_width < max_width)) {
max_width = item.icon_max_width;
}
if (max_width > 0 && icon_size.width > max_width) {
icon_size.height = icon_size.height * max_width / icon_size.width;
icon_size.width = max_width;
}
return icon_size;
}
Size2 PopupMenu::_get_contents_minimum_size() const {
Size2 minsize = theme_cache.panel_style->get_minimum_size(); // Accounts for margin in the margin container
minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content
@ -61,7 +81,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
Size2 item_size;
const_cast<PopupMenu *>(this)->_shape_item(i);
Size2 icon_size = items[i].get_icon_size();
Size2 icon_size = _get_item_icon_size(i);
item_size.height = _get_item_height(i);
icon_w = MAX(icon_size.width, icon_w);
@ -109,7 +129,8 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
int PopupMenu::_get_item_height(int p_item) const {
ERR_FAIL_INDEX_V(p_item, items.size(), 0);
int icon_height = items[p_item].get_icon_size().height;
Size2 icon_size = _get_item_icon_size(p_item);
int icon_height = icon_size.height;
if (items[p_item].checkable_type && !items[p_item].separator) {
icon_height = MAX(icon_height, MAX(theme_cache.checked->get_height(), theme_cache.radio_checked->get_height()));
}
@ -540,7 +561,8 @@ void PopupMenu::_draw_items() {
continue;
}
icon_ofs = MAX(items[i].get_icon_size().width, icon_ofs);
Size2 icon_size = _get_item_icon_size(i);
icon_ofs = MAX(icon_size.width, icon_ofs);
if (items[i].checkable_type) {
has_check = true;
@ -569,7 +591,7 @@ void PopupMenu::_draw_items() {
_shape_item(i);
Point2 item_ofs = ofs;
Size2 icon_size = items[i].get_icon_size();
Size2 icon_size = _get_item_icon_size(i);
float h = _get_item_height(i);
if (i == mouse_over) {
@ -631,21 +653,26 @@ void PopupMenu::_draw_items() {
// Icon
if (!items[i].icon.is_null()) {
const Point2 icon_offset = Point2(0, Math::floor((h - icon_size.height) / 2.0));
Point2 icon_pos;
if (items[i].separator) {
separator_ofs -= (icon_size.width + theme_cache.h_separation) / 2;
if (rtl) {
items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
icon_pos = Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y);
} else {
items[i].icon->draw(ci, item_ofs + Size2(separator_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
icon_pos = item_ofs + Size2(separator_ofs, 0);
}
} else {
if (rtl) {
items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
icon_pos = Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y);
} else {
items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
icon_pos = item_ofs + Size2(check_ofs, 0);
}
}
items[i].icon->draw_rect(ci, Rect2(icon_pos + icon_offset, icon_size), false, icon_color);
}
// Submenu arrow on right hand side.
@ -802,6 +829,7 @@ void PopupMenu::_update_theme_item_cache() {
theme_cache.indent = get_theme_constant(SNAME("indent"));
theme_cache.item_start_padding = get_theme_constant(SNAME("item_start_padding"));
theme_cache.item_end_padding = get_theme_constant(SNAME("item_end_padding"));
theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width"));
theme_cache.checked = get_theme_icon(SNAME("checked"));
theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled"));
@ -946,8 +974,10 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
notify_property_list_changed();
_menu_changed();
@ -958,8 +988,10 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.icon = p_icon;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
notify_property_list_changed();
_menu_changed();
@ -970,8 +1002,10 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -982,8 +1016,10 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
}
@ -992,8 +1028,10 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1004,8 +1042,10 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1016,8 +1056,10 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.max_states = p_max_states;
item.state = p_default_state;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1035,8 +1077,10 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
Item item;
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1046,8 +1090,10 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
item.icon = p_icon;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1057,8 +1103,10 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1069,8 +1117,10 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1080,8 +1130,10 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1092,8 +1144,10 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1105,8 +1159,10 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
item.id = p_id == -1 ? items.size() : p_id;
item.submenu = p_submenu;
items.push_back(item);
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@ -1176,6 +1232,23 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
_menu_changed();
}
void PopupMenu::set_item_icon_max_width(int p_idx, int p_width) {
if (p_idx < 0) {
p_idx += get_item_count();
}
ERR_FAIL_INDEX(p_idx, items.size());
if (items[p_idx].icon_max_width == p_width) {
return;
}
items.write[p_idx].icon_max_width = p_width;
control->queue_redraw();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
if (p_idx < 0) {
p_idx += get_item_count();
@ -1314,6 +1387,11 @@ Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const {
return items[p_idx].icon;
}
int PopupMenu::get_item_icon_max_width(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
return items[p_idx].icon_max_width;
}
Key PopupMenu::get_item_accelerator(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE);
return items[p_idx].accel;
@ -2023,6 +2101,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction);
ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language);
ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon);
ClassDB::bind_method(D_METHOD("set_item_icon_max_width", "index", "width"), &PopupMenu::set_item_icon_max_width);
ClassDB::bind_method(D_METHOD("set_item_checked", "index", "checked"), &PopupMenu::set_item_checked);
ClassDB::bind_method(D_METHOD("set_item_id", "index", "id"), &PopupMenu::set_item_id);
ClassDB::bind_method(D_METHOD("set_item_accelerator", "index", "accel"), &PopupMenu::set_item_accelerator);
@ -2045,6 +2124,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction);
ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language);
ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon);
ClassDB::bind_method(D_METHOD("get_item_icon_max_width", "index"), &PopupMenu::get_item_icon_max_width);
ClassDB::bind_method(D_METHOD("is_item_checked", "index"), &PopupMenu::is_item_checked);
ClassDB::bind_method(D_METHOD("get_item_id", "index"), &PopupMenu::get_item_id);
ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index);

View File

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

View File

@ -63,10 +63,10 @@ Size2 TabBar::get_minimum_size() const {
}
ms.width += style->get_minimum_size().width;
Ref<Texture2D> tex = tabs[i].icon;
if (tex.is_valid()) {
ms.height = MAX(ms.height, tex->get_size().height + y_margin);
ms.width += tex->get_size().width + theme_cache.h_separation;
if (tabs[i].icon.is_valid()) {
const Size2 icon_size = _get_tab_icon_size(i);
ms.height = MAX(ms.height, icon_size.height + y_margin);
ms.width += icon_size.width + theme_cache.h_separation;
}
if (!tabs[i].text.is_empty()) {
@ -304,6 +304,7 @@ void TabBar::_update_theme_item_cache() {
Control::_update_theme_item_cache();
theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width"));
theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected"));
theme_cache.tab_selected_style = get_theme_stylebox(SNAME("tab_selected"));
@ -492,9 +493,11 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in
// Draw the icon.
Ref<Texture2D> icon = tabs[p_index].icon;
if (icon.is_valid()) {
icon->draw(ci, Point2i(rtl ? p_x - icon->get_width() : p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2));
const Size2 icon_size = _get_tab_icon_size(p_index);
const Point2 icon_pos = Point2i(rtl ? p_x - icon_size.width : p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon_size.height) / 2);
icon->draw_rect(ci, Rect2(icon_pos, icon_size));
p_x = rtl ? p_x - icon->get_width() - theme_cache.h_separation : p_x + icon->get_width() + theme_cache.h_separation;
p_x = rtl ? p_x - icon_size.width - theme_cache.h_separation : p_x + icon_size.width + theme_cache.h_separation;
}
// Draw the text.
@ -719,6 +722,29 @@ Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const {
return tabs[p_tab].icon;
}
void TabBar::set_tab_icon_max_width(int p_tab, int p_width) {
ERR_FAIL_INDEX(p_tab, tabs.size());
if (tabs[p_tab].icon_max_width == p_width) {
return;
}
tabs.write[p_tab].icon_max_width = p_width;
_update_cache();
_ensure_no_over_offset();
if (scroll_to_selected) {
ensure_tab_visible(current);
}
queue_redraw();
update_minimum_size();
}
int TabBar::get_tab_icon_max_width(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), 0);
return tabs[p_tab].icon_max_width;
}
void TabBar::set_tab_disabled(int p_tab, bool p_disabled) {
ERR_FAIL_INDEX(p_tab, tabs.size());
@ -1023,9 +1049,14 @@ Variant TabBar::get_drag_data(const Point2 &p_point) {
HBoxContainer *drag_preview = memnew(HBoxContainer);
if (!tabs[tab_over].icon.is_null()) {
const Size2 icon_size = _get_tab_icon_size(tab_over);
TextureRect *tf = memnew(TextureRect);
tf->set_texture(tabs[tab_over].icon);
tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
tf->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
tf->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
tf->set_custom_minimum_size(icon_size);
drag_preview->add_child(tf);
}
@ -1270,9 +1301,9 @@ int TabBar::get_tab_width(int p_idx) const {
}
int x = style->get_minimum_size().width;
Ref<Texture2D> tex = tabs[p_idx].icon;
if (tex.is_valid()) {
x += tex->get_width() + theme_cache.h_separation;
if (tabs[p_idx].icon.is_valid()) {
const Size2 icon_size = _get_tab_icon_size(p_idx);
x += icon_size.width + theme_cache.h_separation;
}
if (!tabs[p_idx].text.is_empty()) {
@ -1305,6 +1336,27 @@ int TabBar::get_tab_width(int p_idx) const {
return x;
}
Size2 TabBar::_get_tab_icon_size(int p_index) const {
ERR_FAIL_INDEX_V(p_index, tabs.size(), Size2());
const TabBar::Tab &tab = tabs[p_index];
Size2 icon_size = tab.icon->get_size();
int icon_max_width = 0;
if (theme_cache.icon_max_width > 0) {
icon_max_width = theme_cache.icon_max_width;
}
if (tab.icon_max_width > 0 && (icon_max_width == 0 || tab.icon_max_width < icon_max_width)) {
icon_max_width = tab.icon_max_width;
}
if (icon_max_width > 0 && icon_size.width > icon_max_width) {
icon_size.height = icon_size.height * icon_max_width / icon_size.width;
icon_size.width = icon_max_width;
}
return icon_size;
}
void TabBar::_ensure_no_over_offset() {
if (!is_inside_tree() || !buttons_visible) {
return;
@ -1547,6 +1599,8 @@ void TabBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language);
ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon);
ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabBar::get_tab_icon);
ClassDB::bind_method(D_METHOD("set_tab_icon_max_width", "tab_idx", "width"), &TabBar::set_tab_icon_max_width);
ClassDB::bind_method(D_METHOD("get_tab_icon_max_width", "tab_idx"), &TabBar::get_tab_icon_max_width);
ClassDB::bind_method(D_METHOD("set_tab_button_icon", "tab_idx", "icon"), &TabBar::set_tab_button_icon);
ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &TabBar::get_tab_button_icon);
ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabBar::set_tab_disabled);

View File

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

View File

@ -146,6 +146,7 @@ void TabContainer::_update_theme_item_cache() {
// TabBar overrides.
theme_cache.icon_separation = get_theme_constant(SNAME("icon_separation"));
theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width"));
theme_cache.outline_size = get_theme_constant(SNAME("outline_size"));
theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected"));
@ -245,6 +246,7 @@ void TabContainer::_on_theme_changed() {
tab_bar->add_theme_font_size_override(SNAME("font_size"), theme_cache.tab_font_size);
tab_bar->add_theme_constant_override(SNAME("h_separation"), theme_cache.icon_separation);
tab_bar->add_theme_constant_override(SNAME("icon_max_width"), theme_cache.icon_max_width);
tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size);
_update_margins();

View File

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

View File

@ -1340,10 +1340,7 @@ Size2 TreeItem::get_minimum_size(int p_column) {
size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.h_separation;
}
if (cell.icon.is_valid()) {
Size2i icon_size = cell.get_icon_size();
if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) {
icon_size.width = cell.icon_max_w;
}
Size2i icon_size = parent_tree->_get_cell_icon_size(cell);
size.width += icon_size.width + parent_tree->theme_cache.h_separation;
size.height = MAX(size.height, icon_size.height);
}
@ -1628,6 +1625,7 @@ void Tree::_update_theme_item_cache() {
theme_cache.v_separation = get_theme_constant(SNAME("v_separation"));
theme_cache.item_margin = get_theme_constant(SNAME("item_margin"));
theme_cache.button_margin = get_theme_constant(SNAME("button_margin"));
theme_cache.icon_max_width = get_theme_constant(SNAME("icon_max_width"));
theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color"));
theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size"));
@ -1654,6 +1652,25 @@ void Tree::_update_theme_item_cache() {
theme_cache.base_scale = get_theme_default_base_scale();
}
Size2 Tree::_get_cell_icon_size(const TreeItem::Cell &p_cell) const {
Size2i icon_size = p_cell.get_icon_size();
int max_width = 0;
if (theme_cache.icon_max_width > 0) {
max_width = theme_cache.icon_max_width;
}
if (p_cell.icon_max_w > 0 && (max_width == 0 || p_cell.icon_max_w < max_width)) {
max_width = p_cell.icon_max_w;
}
if (max_width > 0 && icon_size.width > max_width) {
icon_size.height = icon_size.height * max_width / icon_size.width;
icon_size.width = max_width;
}
return icon_size;
}
int Tree::compute_item_height(TreeItem *p_item) const {
if ((p_item == root && hide_root) || !p_item->is_visible()) {
return 0;
@ -1688,10 +1705,7 @@ int Tree::compute_item_height(TreeItem *p_item) const {
case TreeItem::CELL_MODE_ICON: {
Ref<Texture2D> icon = p_item->cells[i].icon;
if (!icon.is_null()) {
Size2i s = p_item->cells[i].get_icon_size();
if (p_item->cells[i].icon_max_w > 0 && s.width > p_item->cells[i].icon_max_w) {
s.height = s.height * p_item->cells[i].icon_max_w / s.width;
}
Size2i s = _get_cell_icon_size(p_item->cells[i]);
if (s.height > height) {
height = s.height;
}
@ -1745,10 +1759,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
int w = 0;
if (!p_cell.icon.is_null()) {
Size2i bmsize = p_cell.get_icon_size();
if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) {
bmsize.width = p_cell.icon_max_w;
}
Size2i bmsize = _get_cell_icon_size(p_cell);
w += bmsize.width + theme_cache.h_separation;
if (rect.size.width > 0 && (w + ts.width) > rect.size.width) {
ts.width = rect.size.width - w;
@ -1788,12 +1799,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co
}
if (!p_cell.icon.is_null()) {
Size2i bmsize = p_cell.get_icon_size();
if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) {
bmsize.height = bmsize.height * p_cell.icon_max_w / bmsize.width;
bmsize.width = p_cell.icon_max_w;
}
Size2i bmsize = _get_cell_icon_size(p_cell);
p_cell.draw_icon(ci, rect.position + Size2i(0, Math::floor((real_t)(rect.size.y - bmsize.y) / 2)), bmsize, p_icon_color);
rect.position.x += bmsize.x + theme_cache.h_separation;
@ -2206,12 +2212,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[i].icon.is_null()) {
break;
}
Size2i icon_size = p_item->cells[i].get_icon_size();
if (p_item->cells[i].icon_max_w > 0 && icon_size.width > p_item->cells[i].icon_max_w) {
icon_size.height = icon_size.height * p_item->cells[i].icon_max_w / icon_size.width;
icon_size.width = p_item->cells[i].icon_max_w;
}
Size2i icon_size = _get_cell_icon_size(p_item->cells[i]);
Point2i icon_ofs = (item_rect.size - icon_size) / 2;
icon_ofs += item_rect.position;
@ -3795,8 +3796,9 @@ bool Tree::edit_selected() {
popup_rect.size = rect.size;
// Account for icon.
popup_rect.position.x += c.get_icon_size().x;
popup_rect.size.x -= c.get_icon_size().x;
Size2 icon_size = _get_cell_icon_size(c);
popup_rect.position.x += icon_size.x;
popup_rect.size.x -= icon_size.x;
text_editor->clear();
text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step)));

View File

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

View File

@ -180,6 +180,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("icon_disabled_color", "Button", Color(1, 1, 1, 0.4));
theme->set_constant("h_separation", "Button", 2 * scale);
theme->set_constant("icon_max_width", "Button", 0);
// MenuBar
theme->set_stylebox("normal", "MenuBar", button_normal);
@ -688,6 +689,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("separator_outline_size", "PopupMenu", 0);
theme->set_constant("item_start_padding", "PopupMenu", 2 * scale);
theme->set_constant("item_end_padding", "PopupMenu", 2 * scale);
theme->set_constant("icon_max_width", "PopupMenu", 0);
// GraphNode
Ref<StyleBoxFlat> graphnode_normal = make_flat_stylebox(style_normal_color, 18, 42, 18, 12);
@ -780,6 +782,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("scroll_border", "Tree", 4);
theme->set_constant("scroll_speed", "Tree", 12);
theme->set_constant("outline_size", "Tree", 0);
theme->set_constant("icon_max_width", "Tree", 0);
// ItemList
@ -842,6 +845,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("side_margin", "TabContainer", 8 * scale);
theme->set_constant("icon_separation", "TabContainer", 4 * scale);
theme->set_constant("icon_max_width", "TabContainer", 0);
theme->set_constant("outline_size", "TabContainer", 0);
// TabBar
@ -869,6 +873,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("drop_mark_color", "TabBar", Color(1, 1, 1));
theme->set_constant("h_separation", "TabBar", 4 * scale);
theme->set_constant("icon_max_width", "TabBar", 0);
theme->set_constant("outline_size", "TabBar", 0);
// Separators