summaryrefslogtreecommitdiffstats
path: root/scene/theme
diff options
context:
space:
mode:
Diffstat (limited to 'scene/theme')
-rw-r--r--scene/theme/theme_db.cpp236
-rw-r--r--scene/theme/theme_db.h60
-rw-r--r--scene/theme/theme_owner.cpp193
-rw-r--r--scene/theme/theme_owner.h11
4 files changed, 393 insertions, 107 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"));
+}
diff --git a/scene/theme/theme_db.h b/scene/theme/theme_db.h
index f7866cb3ea..40ae30ff81 100644
--- a/scene/theme/theme_db.h
+++ b/scene/theme/theme_db.h
@@ -35,26 +35,39 @@
#include "core/object/ref_counted.h"
class Font;
+class Node;
class StyleBox;
class Texture2D;
class Theme;
+class ThemeContext;
class ThemeDB : public Object {
GDCLASS(ThemeDB, Object);
static ThemeDB *singleton;
- // Universal Theme resources used when no other theme has the item.
+ // Global Theme resources used by the default theme context.
+
Ref<Theme> default_theme;
Ref<Theme> project_theme;
// Universal default values, final fallback for every theme.
- float fallback_base_scale;
+
+ float fallback_base_scale = 1.0;
Ref<Font> fallback_font;
- int fallback_font_size;
+ int fallback_font_size = 16;
Ref<Texture2D> fallback_icon;
Ref<StyleBox> fallback_stylebox;
+ // Global theme contexts used to scope global Theme resources.
+
+ ThemeContext *default_theme_context = nullptr;
+ HashMap<Node *, ThemeContext *> theme_contexts;
+
+ void _propagate_theme_context(Node *p_from_node, ThemeContext *p_context);
+ void _init_default_theme_context();
+ void _finalize_theme_contexts();
+
protected:
static void _bind_methods();
@@ -63,7 +76,7 @@ public:
void initialize_theme_noproject();
void finalize_theme();
- // Universal Theme resources
+ // Global Theme resources.
void set_default_theme(const Ref<Theme> &p_default);
Ref<Theme> get_default_theme();
@@ -71,7 +84,7 @@ public:
void set_project_theme(const Ref<Theme> &p_project_default);
Ref<Theme> get_project_theme();
- // Universal default values.
+ // Universal fallback values.
void set_fallback_base_scale(float p_base_scale);
float get_fallback_base_scale();
@@ -88,9 +101,46 @@ public:
void set_fallback_stylebox(const Ref<StyleBox> &p_stylebox);
Ref<StyleBox> get_fallback_stylebox();
+ void get_native_type_dependencies(const StringName &p_base_type, List<StringName> *p_list);
+
+ // Global theme contexts.
+
+ ThemeContext *create_theme_context(Node *p_node, List<Ref<Theme>> &p_themes);
+ void destroy_theme_context(Node *p_node);
+
+ ThemeContext *get_theme_context(Node *p_node) const;
+ ThemeContext *get_default_theme_context() const;
+ ThemeContext *get_nearest_theme_context(Node *p_for_node) const;
+
+ // Memory management, reference, and initialization.
+
static ThemeDB *get_singleton();
ThemeDB();
~ThemeDB();
};
+class ThemeContext : public Object {
+ GDCLASS(ThemeContext, Object);
+
+ friend class ThemeDB;
+
+ Node *node = nullptr;
+ ThemeContext *parent = nullptr;
+
+ // Themes are stacked in the order of relevance, for easy iteration.
+ // This means that the first theme is the one you should check first,
+ // and the last theme is the fallback theme where every lookup ends.
+ List<Ref<Theme>> themes;
+
+ void _emit_changed();
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_themes(List<Ref<Theme>> &p_themes);
+ List<Ref<Theme>> get_themes() const;
+ Ref<Theme> get_fallback_theme() const;
+};
+
#endif // THEME_DB_H
diff --git a/scene/theme/theme_owner.cpp b/scene/theme/theme_owner.cpp
index 40855c6a3e..1ba9a055fc 100644
--- a/scene/theme/theme_owner.cpp
+++ b/scene/theme/theme_owner.cpp
@@ -66,6 +66,52 @@ bool ThemeOwner::has_owner_node() const {
return bool(owner_control || owner_window);
}
+void ThemeOwner::set_owner_context(ThemeContext *p_context, bool p_propagate) {
+ ThemeContext *default_context = ThemeDB::get_singleton()->get_default_theme_context();
+
+ if (owner_context && owner_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) {
+ owner_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
+ } else if (default_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) {
+ default_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
+ }
+
+ owner_context = p_context;
+
+ if (owner_context) {
+ owner_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
+ } else {
+ default_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
+ }
+
+ if (p_propagate) {
+ _owner_context_changed();
+ }
+}
+
+void ThemeOwner::_owner_context_changed() {
+ if (!holder->is_inside_tree()) {
+ // We ignore theme changes outside of tree, because NOTIFICATION_ENTER_TREE covers everything.
+ return;
+ }
+
+ Control *c = Object::cast_to<Control>(holder);
+ Window *w = c == nullptr ? Object::cast_to<Window>(holder) : nullptr;
+
+ if (c) {
+ c->notification(Control::NOTIFICATION_THEME_CHANGED);
+ } else if (w) {
+ w->notification(Window::NOTIFICATION_THEME_CHANGED);
+ }
+}
+
+ThemeContext *ThemeOwner::_get_active_owner_context() const {
+ if (owner_context) {
+ return owner_context;
+ }
+
+ return ThemeDB::get_singleton()->get_default_theme_context();
+}
+
// Theme propagation.
void ThemeOwner::assign_theme_on_parented(Node *p_for_node) {
@@ -158,9 +204,7 @@ void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const Strin
const Window *for_w = Object::cast_to<Window>(p_for_node);
ERR_FAIL_COND_MSG(!for_c && !for_w, "Only Control and Window nodes and derivatives can be polled for theming.");
- Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme();
- Ref<Theme> project_theme = ThemeDB::get_singleton()->get_project_theme();
-
+ StringName type_name = p_for_node->get_class_name();
StringName type_variation;
if (for_c) {
type_variation = for_c->get_theme_type_variation();
@@ -168,31 +212,23 @@ void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const Strin
type_variation = for_w->get_theme_type_variation();
}
- if (p_theme_type == StringName() || p_theme_type == p_for_node->get_class_name() || p_theme_type == type_variation) {
- if (project_theme.is_valid() && project_theme->get_type_variation_base(type_variation) != StringName()) {
- project_theme->get_type_dependencies(p_for_node->get_class_name(), type_variation, r_list);
- } else {
- default_theme->get_type_dependencies(p_for_node->get_class_name(), type_variation, r_list);
+ // If we are looking for dependencies of the current class (or a variantion of it), check themes from the context.
+ if (p_theme_type == StringName() || p_theme_type == type_name || p_theme_type == type_variation) {
+ ThemeContext *global_context = _get_active_owner_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_valid() && theme->get_type_variation_base(type_variation) != StringName()) {
+ theme->get_type_dependencies(type_name, type_variation, r_list);
+ return;
+ }
}
- } else {
- default_theme->get_type_dependencies(p_theme_type, StringName(), r_list);
- }
-}
-Node *ThemeOwner::_get_next_owner_node(Node *p_from_node) const {
- Node *parent = p_from_node->get_parent();
-
- Control *parent_c = Object::cast_to<Control>(parent);
- if (parent_c) {
- return parent_c->get_theme_owner_node();
- } else {
- Window *parent_w = Object::cast_to<Window>(parent);
- if (parent_w) {
- return parent_w->get_theme_owner_node();
- }
+ // If nothing was found, get the native dependencies for the current class.
+ ThemeDB::get_singleton()->get_native_type_dependencies(type_name, r_list);
+ return;
}
- return nullptr;
+ // Otherwise, get the native dependencies for the provided theme type.
+ ThemeDB::get_singleton()->get_native_type_dependencies(p_theme_type, r_list);
}
Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
@@ -215,24 +251,20 @@ Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const S
owner_node = _get_next_owner_node(owner_node);
}
- // Secondly, check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- for (const StringName &E : p_theme_types) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(p_data_type, p_name, E)) {
- return ThemeDB::get_singleton()->get_project_theme()->get_theme_item(p_data_type, p_name, E);
+ // Second, check global themes from the appropriate context.
+ ThemeContext *global_context = _get_active_owner_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_valid()) {
+ for (const StringName &E : p_theme_types) {
+ if (theme->has_theme_item(p_data_type, p_name, E)) {
+ return theme->get_theme_item(p_data_type, p_name, E);
+ }
}
}
}
- // Lastly, fall back on the items defined in the default Theme, if they exist.
- for (const StringName &E : p_theme_types) {
- if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(p_data_type, p_name, E)) {
- return ThemeDB::get_singleton()->get_default_theme()->get_theme_item(p_data_type, p_name, E);
- }
- }
-
- // If they don't exist, use any type to return the default/empty value.
- return ThemeDB::get_singleton()->get_default_theme()->get_theme_item(p_data_type, p_name, p_theme_types[0]);
+ // Finally, if no match exists, use any type to return the default/empty value.
+ return global_context->get_fallback_theme()->get_theme_item(p_data_type, p_name, StringName());
}
bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
@@ -255,22 +287,19 @@ bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const Stri
owner_node = _get_next_owner_node(owner_node);
}
- // Secondly, check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- for (const StringName &E : p_theme_types) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(p_data_type, p_name, E)) {
- return true;
+ // Second, check global themes from the appropriate context.
+ ThemeContext *global_context = _get_active_owner_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_valid()) {
+ for (const StringName &E : p_theme_types) {
+ if (theme->has_theme_item(p_data_type, p_name, E)) {
+ return true;
+ }
}
}
}
- // Lastly, fall back on the items defined in the default Theme, if they exist.
- for (const StringName &E : p_theme_types) {
- if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(p_data_type, p_name, E)) {
- return true;
- }
- }
-
+ // Finally, if no match exists, return false.
return false;
}
@@ -290,17 +319,17 @@ float ThemeOwner::get_theme_default_base_scale() {
owner_node = _get_next_owner_node(owner_node);
}
- // Secondly, check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_default_base_scale()) {
- return ThemeDB::get_singleton()->get_project_theme()->get_default_base_scale();
+ // Second, check global themes from the appropriate context.
+ ThemeContext *global_context = _get_active_owner_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_valid()) {
+ if (theme->has_default_base_scale()) {
+ return theme->get_default_base_scale();
+ }
}
}
- // Lastly, fall back on the default Theme.
- if (ThemeDB::get_singleton()->get_default_theme()->has_default_base_scale()) {
- return ThemeDB::get_singleton()->get_default_theme()->get_default_base_scale();
- }
+ // Finally, if no match exists, return the universal default.
return ThemeDB::get_singleton()->get_fallback_base_scale();
}
@@ -320,17 +349,17 @@ Ref<Font> ThemeOwner::get_theme_default_font() {
owner_node = _get_next_owner_node(owner_node);
}
- // Secondly, check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_default_font()) {
- return ThemeDB::get_singleton()->get_project_theme()->get_default_font();
+ // Second, check global themes from the appropriate context.
+ ThemeContext *global_context = _get_active_owner_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_valid()) {
+ if (theme->has_default_font()) {
+ return theme->get_default_font();
+ }
}
}
- // Lastly, fall back on the default Theme.
- if (ThemeDB::get_singleton()->get_default_theme()->has_default_font()) {
- return ThemeDB::get_singleton()->get_default_theme()->get_default_font();
- }
+ // Finally, if no match exists, return the universal default.
return ThemeDB::get_singleton()->get_fallback_font();
}
@@ -350,17 +379,17 @@ int ThemeOwner::get_theme_default_font_size() {
owner_node = _get_next_owner_node(owner_node);
}
- // Secondly, check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_default_font_size()) {
- return ThemeDB::get_singleton()->get_project_theme()->get_default_font_size();
+ // Second, check global themes from the appropriate context.
+ ThemeContext *global_context = _get_active_owner_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_valid()) {
+ if (theme->has_default_font_size()) {
+ return theme->get_default_font_size();
+ }
}
}
- // Lastly, fall back on the default Theme.
- if (ThemeDB::get_singleton()->get_default_theme()->has_default_font_size()) {
- return ThemeDB::get_singleton()->get_default_theme()->get_default_font_size();
- }
+ // Finally, if no match exists, return the universal default.
return ThemeDB::get_singleton()->get_fallback_font_size();
}
@@ -377,3 +406,19 @@ Ref<Theme> ThemeOwner::_get_owner_node_theme(Node *p_owner_node) const {
return Ref<Theme>();
}
+
+Node *ThemeOwner::_get_next_owner_node(Node *p_from_node) const {
+ Node *parent = p_from_node->get_parent();
+
+ Control *parent_c = Object::cast_to<Control>(parent);
+ if (parent_c) {
+ return parent_c->get_theme_owner_node();
+ } else {
+ Window *parent_w = Object::cast_to<Window>(parent);
+ if (parent_w) {
+ return parent_w->get_theme_owner_node();
+ }
+ }
+
+ return nullptr;
+}
diff --git a/scene/theme/theme_owner.h b/scene/theme/theme_owner.h
index 7ebd53fde8..4923ccb00b 100644
--- a/scene/theme/theme_owner.h
+++ b/scene/theme/theme_owner.h
@@ -36,11 +36,18 @@
class Control;
class Node;
+class ThemeContext;
class Window;
class ThemeOwner : public Object {
+ Node *holder = nullptr;
+
Control *owner_control = nullptr;
Window *owner_window = nullptr;
+ ThemeContext *owner_context = nullptr;
+
+ void _owner_context_changed();
+ ThemeContext *_get_active_owner_context() const;
Node *_get_next_owner_node(Node *p_from_node) const;
Ref<Theme> _get_owner_node_theme(Node *p_owner_node) const;
@@ -52,6 +59,8 @@ public:
Node *get_owner_node() const;
bool has_owner_node() const;
+ void set_owner_context(ThemeContext *p_context, bool p_propagate = true);
+
// Theme propagation.
void assign_theme_on_parented(Node *p_for_node);
@@ -69,7 +78,7 @@ public:
Ref<Font> get_theme_default_font();
int get_theme_default_font_size();
- ThemeOwner() {}
+ ThemeOwner(Node *p_holder) { holder = p_holder; }
~ThemeOwner() {}
};