diff options
Diffstat (limited to 'scene/theme/theme_db.cpp')
| -rw-r--r-- | scene/theme/theme_db.cpp | 236 |
1 files changed, 209 insertions, 27 deletions
diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp index ae2a629c1d..92f3dec5e2 100644 --- a/scene/theme/theme_db.cpp +++ b/scene/theme/theme_db.cpp @@ -32,6 +32,9 @@ #include "core/config/project_settings.h" #include "core/io/resource_loader.h" +#include "scene/gui/control.h" +#include "scene/main/node.h" +#include "scene/main/window.h" #include "scene/resources/font.h" #include "scene/resources/style_box.h" #include "scene/resources/texture.h" @@ -40,18 +43,18 @@ #include "servers/text_server.h" // Default engine theme creation and configuration. + void ThemeDB::initialize_theme() { + // Default theme-related project settings. + // Allow creating the default theme at a different scale to suit higher/lower base resolutions. float default_theme_scale = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/theme/default_theme_scale", PROPERTY_HINT_RANGE, "0.5,8,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), 1.0); - String theme_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); - - String font_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.otf,*.ttf,*.woff,*.woff2,*.fnt,*.font,*.pfb,*.pfm", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); + String project_theme_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); + String project_font_path = GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.otf,*.ttf,*.woff,*.woff2,*.fnt,*.font,*.pfb,*.pfm", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), ""); TextServer::FontAntialiasing font_antialiasing = (TextServer::FontAntialiasing)(int)GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/default_font_antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), 1); - TextServer::Hinting font_hinting = (TextServer::Hinting)(int)GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/default_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), TextServer::HINTING_LIGHT); - TextServer::SubpixelPositioning font_subpixel_positioning = (TextServer::SubpixelPositioning)(int)GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/default_font_subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED), TextServer::SUBPIXEL_POSITIONING_AUTO); const bool font_msdf = GLOBAL_DEF_RST("gui/theme/default_font_multichannel_signed_distance_field", false); @@ -60,35 +63,42 @@ void ThemeDB::initialize_theme() { GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "gui/theme/lcd_subpixel_layout", PROPERTY_HINT_ENUM, "Disabled,Horizontal RGB,Horizontal BGR,Vertical RGB,Vertical BGR"), 1); ProjectSettings::get_singleton()->set_restart_if_changed("gui/theme/lcd_subpixel_layout", false); - Ref<Font> font; - if (!font_path.is_empty()) { - font = ResourceLoader::load(font_path); - if (font.is_valid()) { - set_fallback_font(font); + // Attempt to load custom project theme and font. + + if (!project_theme_path.is_empty()) { + Ref<Theme> theme = ResourceLoader::load(project_theme_path); + if (theme.is_valid()) { + set_project_theme(theme); } else { - ERR_PRINT("Error loading custom font '" + font_path + "'"); + ERR_PRINT("Error loading custom project theme '" + project_theme_path + "'"); } } - // Always make the default theme to avoid invalid default font/icon/style in the given theme. - if (RenderingServer::get_singleton()) { - make_default_theme(default_theme_scale, font, font_subpixel_positioning, font_hinting, font_antialiasing, font_msdf, font_generate_mipmaps); - } - - if (!theme_path.is_empty()) { - Ref<Theme> theme = ResourceLoader::load(theme_path); - if (theme.is_valid()) { - set_project_theme(theme); + Ref<Font> project_font; + if (!project_font_path.is_empty()) { + project_font = ResourceLoader::load(project_font_path); + if (project_font.is_valid()) { + set_fallback_font(project_font); } else { - ERR_PRINT("Error loading custom theme '" + theme_path + "'"); + ERR_PRINT("Error loading custom project font '" + project_font_path + "'"); } } + + // Always generate the default theme to serve as a fallback for all required theme definitions. + + if (RenderingServer::get_singleton()) { + make_default_theme(default_theme_scale, project_font, font_subpixel_positioning, font_hinting, font_antialiasing, font_msdf, font_generate_mipmaps); + } + + _init_default_theme_context(); } void ThemeDB::initialize_theme_noproject() { if (RenderingServer::get_singleton()) { make_default_theme(1.0, Ref<Font>()); } + + _init_default_theme_context(); } void ThemeDB::finalize_theme() { @@ -96,6 +106,7 @@ void ThemeDB::finalize_theme() { WARN_PRINT("Finalizing theme when there is no RenderingServer is an error; check the order of operations."); } + _finalize_theme_contexts(); default_theme.unref(); fallback_font.unref(); @@ -103,7 +114,7 @@ void ThemeDB::finalize_theme() { fallback_stylebox.unref(); } -// Universal fallback Theme resources. +// Global Theme resources. void ThemeDB::set_default_theme(const Ref<Theme> &p_default) { default_theme = p_default; @@ -188,7 +199,135 @@ Ref<StyleBox> ThemeDB::get_fallback_stylebox() { return fallback_stylebox; } +void ThemeDB::get_native_type_dependencies(const StringName &p_base_type, List<StringName> *p_list) { + ERR_FAIL_NULL(p_list); + + // TODO: It may make sense to stop at Control/Window, because their parent classes cannot be used in + // a meaningful way. + StringName class_name = p_base_type; + while (class_name != StringName()) { + p_list->push_back(class_name); + class_name = ClassDB::get_parent_class_nocheck(class_name); + } +} + +// Global theme contexts. + +ThemeContext *ThemeDB::create_theme_context(Node *p_node, List<Ref<Theme>> &p_themes) { + ERR_FAIL_COND_V(!p_node->is_inside_tree(), nullptr); + ERR_FAIL_COND_V(theme_contexts.has(p_node), nullptr); + ERR_FAIL_COND_V(p_themes.is_empty(), nullptr); + + ThemeContext *context = memnew(ThemeContext); + context->node = p_node; + context->parent = get_nearest_theme_context(p_node); + context->set_themes(p_themes); + + theme_contexts[p_node] = context; + _propagate_theme_context(p_node, context); + + p_node->connect("tree_exited", callable_mp(this, &ThemeDB::destroy_theme_context).bind(p_node)); + + return context; +} + +void ThemeDB::destroy_theme_context(Node *p_node) { + ERR_FAIL_COND(!theme_contexts.has(p_node)); + + p_node->disconnect("tree_exited", callable_mp(this, &ThemeDB::destroy_theme_context)); + + ThemeContext *context = theme_contexts[p_node]; + + theme_contexts.erase(p_node); + _propagate_theme_context(p_node, context->parent); + + memdelete(context); +} + +void ThemeDB::_propagate_theme_context(Node *p_from_node, ThemeContext *p_context) { + Control *from_control = Object::cast_to<Control>(p_from_node); + Window *from_window = from_control ? nullptr : Object::cast_to<Window>(p_from_node); + + if (from_control) { + from_control->set_theme_context(p_context); + } else if (from_window) { + from_window->set_theme_context(p_context); + } + + for (int i = 0; i < p_from_node->get_child_count(); i++) { + Node *child_node = p_from_node->get_child(i); + + // If the child is the root of another global context, stop the propagation + // in this branch. + if (theme_contexts.has(child_node)) { + theme_contexts[child_node]->parent = p_context; + continue; + } + + _propagate_theme_context(child_node, p_context); + } +} + +void ThemeDB::_init_default_theme_context() { + default_theme_context = memnew(ThemeContext); + + List<Ref<Theme>> themes; + + // Only add the project theme to the default context when running projects. + +#ifdef TOOLS_ENABLED + if (!Engine::get_singleton()->is_editor_hint()) { + themes.push_back(project_theme); + } +#else + themes.push_back(project_theme); +#endif + + themes.push_back(default_theme); + default_theme_context->set_themes(themes); +} + +void ThemeDB::_finalize_theme_contexts() { + if (default_theme_context) { + memdelete(default_theme_context); + default_theme_context = nullptr; + } + while (theme_contexts.size()) { + HashMap<Node *, ThemeContext *>::Iterator E = theme_contexts.begin(); + memdelete(E->value); + theme_contexts.remove(E); + } +} + +ThemeContext *ThemeDB::get_theme_context(Node *p_node) const { + if (!theme_contexts.has(p_node)) { + return nullptr; + } + + return theme_contexts[p_node]; +} + +ThemeContext *ThemeDB::get_default_theme_context() const { + return default_theme_context; +} + +ThemeContext *ThemeDB::get_nearest_theme_context(Node *p_for_node) const { + ERR_FAIL_COND_V(!p_for_node->is_inside_tree(), nullptr); + + Node *parent_node = p_for_node->get_parent(); + while (parent_node) { + if (theme_contexts.has(parent_node)) { + return theme_contexts[parent_node]; + } + + parent_node = parent_node->get_parent(); + } + + return nullptr; +} + // Object methods. + void ThemeDB::_bind_methods() { ClassDB::bind_method(D_METHOD("get_default_theme"), &ThemeDB::get_default_theme); ClassDB::bind_method(D_METHOD("get_project_theme"), &ThemeDB::get_project_theme); @@ -214,7 +353,8 @@ void ThemeDB::_bind_methods() { ADD_SIGNAL(MethodInfo("fallback_changed")); } -// Memory management, reference, and initialization +// Memory management, reference, and initialization. + ThemeDB *ThemeDB::singleton = nullptr; ThemeDB *ThemeDB::get_singleton() { @@ -223,13 +363,15 @@ ThemeDB *ThemeDB::get_singleton() { ThemeDB::ThemeDB() { singleton = this; - - // Universal default values, final fallback for every theme. - fallback_base_scale = 1.0; - fallback_font_size = 16; } ThemeDB::~ThemeDB() { + // For technical reasons unit tests recreate and destroy the default + // theme over and over again. Make sure that finalize_theme() also + // frees any objects that can be recreated by initialize_theme*(). + + _finalize_theme_contexts(); + default_theme.unref(); project_theme.unref(); @@ -239,3 +381,43 @@ ThemeDB::~ThemeDB() { singleton = nullptr; } + +void ThemeContext::_emit_changed() { + emit_signal(SNAME("changed")); +} + +void ThemeContext::set_themes(List<Ref<Theme>> &p_themes) { + for (const Ref<Theme> &theme : themes) { + theme->disconnect_changed(callable_mp(this, &ThemeContext::_emit_changed)); + } + + themes.clear(); + + for (const Ref<Theme> &theme : p_themes) { + if (theme.is_null()) { + continue; + } + + themes.push_back(theme); + theme->connect_changed(callable_mp(this, &ThemeContext::_emit_changed)); + } + + _emit_changed(); +} + +List<Ref<Theme>> ThemeContext::get_themes() const { + return themes; +} + +Ref<Theme> ThemeContext::get_fallback_theme() const { + // We expect all contexts to be valid and non-empty, but just in case... + if (themes.size() == 0) { + return ThemeDB::get_singleton()->get_default_theme(); + } + + return themes.back()->get(); +} + +void ThemeContext::_bind_methods() { + ADD_SIGNAL(MethodInfo("changed")); +} |
