mirror of
https://github.com/godotengine/godot.git
synced 2024-11-22 04:06:14 +00:00
Merge pull request #75472 from YuriSizov/editor-iconography
Improve editor support for icons of custom, scripted, and GDExtension classes
This commit is contained in:
commit
21d080ead4
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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)));
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user