summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--scene/3d/label_3d.cpp45
-rw-r--r--scene/gui/control.cpp12
-rw-r--r--scene/gui/control.h3
-rw-r--r--scene/main/window.cpp13
-rw-r--r--scene/main/window.h3
-rw-r--r--scene/resources/font.cpp116
-rw-r--r--scene/resources/primitive_meshes.cpp30
-rw-r--r--scene/resources/theme.cpp6
-rw-r--r--scene/theme/theme_db.cpp218
-rw-r--r--scene/theme/theme_db.h59
-rw-r--r--scene/theme/theme_owner.cpp193
-rw-r--r--scene/theme/theme_owner.h11
-rw-r--r--tests/test_main.cpp4
13 files changed, 477 insertions, 236 deletions
diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp
index 3d304de1df..9eec2f5345 100644
--- a/scene/3d/label_3d.cpp
+++ b/scene/3d/label_3d.cpp
@@ -780,42 +780,31 @@ Ref<Font> Label3D::_get_font_or_default() const {
return font_override;
}
- // Check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_project_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
+ StringName theme_name = "font";
+ List<StringName> theme_types;
+ ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types);
+
+ ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_null()) {
+ continue;
+ }
for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- Ref<Font> f = ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
- if (f.is_valid()) {
- theme_font = f;
- theme_font->connect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed));
- }
- return f;
+ if (!theme->has_font(theme_name, E)) {
+ continue;
}
- }
- }
- // Lastly, fall back on the items defined in the default Theme, if they exist.
- {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_default_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
-
- for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
- if (f.is_valid()) {
- theme_font = f;
- theme_font->connect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed));
- }
- return f;
+ Ref<Font> f = theme->get_font(theme_name, E);
+ if (f.is_valid()) {
+ theme_font = f;
+ theme_font->connect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed));
}
+ return f;
}
}
- // If they don't exist, use any type to return the default/empty value.
- Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName());
+ Ref<Font> f = global_context->get_fallback_theme()->get_font(theme_name, StringName());
if (f.is_valid()) {
theme_font = f;
theme_font->connect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed));
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index b6f583a945..d97ce65afc 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -2465,6 +2465,11 @@ bool Control::has_theme_owner_node() const {
return data.theme_owner->has_owner_node();
}
+void Control::set_theme_context(ThemeContext *p_context, bool p_propagate) {
+ ERR_MAIN_THREAD_GUARD;
+ data.theme_owner->set_owner_context(p_context, p_propagate);
+}
+
void Control::set_theme(const Ref<Theme> &p_theme) {
ERR_MAIN_THREAD_GUARD;
if (data.theme == p_theme) {
@@ -3124,7 +3129,9 @@ void Control::_notification(int p_notification) {
notification(NOTIFICATION_TRANSLATION_CHANGED);
}
#endif
- notification(NOTIFICATION_THEME_CHANGED);
+
+ // Emits NOTIFICATION_THEME_CHANGED internally.
+ set_theme_context(ThemeDB::get_singleton()->get_nearest_theme_context(this));
} break;
case NOTIFICATION_POST_ENTER_TREE: {
@@ -3134,6 +3141,7 @@ void Control::_notification(int p_notification) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ set_theme_context(nullptr, false);
release_focus();
get_viewport()->_gui_remove_control(this);
} break;
@@ -3632,7 +3640,7 @@ void Control::_bind_methods() {
}
Control::Control() {
- data.theme_owner = memnew(ThemeOwner);
+ data.theme_owner = memnew(ThemeOwner(this));
}
Control::~Control() {
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 77c0c45034..bad78a66d3 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -42,6 +42,7 @@ class Viewport;
class Label;
class Panel;
class ThemeOwner;
+class ThemeContext;
class Control : public CanvasItem {
GDCLASS(Control, CanvasItem);
@@ -553,6 +554,8 @@ public:
Node *get_theme_owner_node() const;
bool has_theme_owner_node() const;
+ void set_theme_context(ThemeContext *p_context, bool p_propagate = true);
+
void set_theme(const Ref<Theme> &p_theme);
Ref<Theme> get_theme() const;
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index c79ac1f40b..ba53a5e3c3 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -1269,7 +1269,9 @@ void Window::_notification(int p_what) {
notification(NOTIFICATION_TRANSLATION_CHANGED);
}
#endif
- notification(NOTIFICATION_THEME_CHANGED);
+
+ // Emits NOTIFICATION_THEME_CHANGED internally.
+ set_theme_context(ThemeDB::get_singleton()->get_nearest_theme_context(this));
} break;
case NOTIFICATION_READY: {
@@ -1313,6 +1315,8 @@ void Window::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ set_theme_context(nullptr, false);
+
if (transient) {
_clear_transient();
}
@@ -1889,6 +1893,11 @@ bool Window::has_theme_owner_node() const {
return theme_owner->has_owner_node();
}
+void Window::set_theme_context(ThemeContext *p_context, bool p_propagate) {
+ ERR_MAIN_THREAD_GUARD;
+ theme_owner->set_owner_context(p_context, p_propagate);
+}
+
void Window::set_theme(const Ref<Theme> &p_theme) {
ERR_MAIN_THREAD_GUARD;
if (theme == p_theme) {
@@ -2887,7 +2896,7 @@ Window::Window() {
max_size_used = max_size; // Update max_size_used.
}
- theme_owner = memnew(ThemeOwner);
+ theme_owner = memnew(ThemeOwner(this));
RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_DISABLED);
}
diff --git a/scene/main/window.h b/scene/main/window.h
index 545b782995..689fa754cb 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -39,6 +39,7 @@ class Font;
class Shortcut;
class StyleBox;
class ThemeOwner;
+class ThemeContext;
class Window : public Viewport {
GDCLASS(Window, Viewport)
@@ -365,6 +366,8 @@ public:
Node *get_theme_owner_node() const;
bool has_theme_owner_node() const;
+ void set_theme_context(ThemeContext *p_context, bool p_propagate = true);
+
void set_theme(const Ref<Theme> &p_theme);
Ref<Theme> get_theme() const;
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 9d546c90c7..c8a98a6831 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -2779,48 +2779,25 @@ Ref<Font> FontVariation::_get_base_font_or_default() const {
return base_font;
}
- // Check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_project_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
+ StringName theme_name = "font";
+ List<StringName> theme_types;
+ ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types);
- for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- Ref<Font> f = ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
- if (f == this) {
- continue;
- }
- if (f.is_valid()) {
- theme_font = f;
- theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
- }
- return f;
- }
+ ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_null()) {
+ continue;
}
- }
-
- // Lastly, fall back on the items defined in the default Theme, if they exist.
- if (ThemeDB::get_singleton()->get_default_theme().is_valid()) {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_default_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
- if (f == this) {
- continue;
- }
- if (f.is_valid()) {
- theme_font = f;
- theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
- }
- return f;
+ if (!theme->has_font(theme_name, E)) {
+ continue;
}
- }
- // If they don't exist, use any type to return the default/empty value.
- Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName());
- if (f != this) {
+ Ref<Font> f = theme->get_font(theme_name, E);
+ if (f == this) {
+ continue;
+ }
if (f.is_valid()) {
theme_font = f;
theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
@@ -2829,6 +2806,15 @@ Ref<Font> FontVariation::_get_base_font_or_default() const {
}
}
+ Ref<Font> f = global_context->get_fallback_theme()->get_font(theme_name, StringName());
+ if (f != this) {
+ if (f.is_valid()) {
+ theme_font = f;
+ theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
+ }
+ return f;
+ }
+
return Ref<Font>();
}
@@ -3131,48 +3117,25 @@ Ref<Font> SystemFont::_get_base_font_or_default() const {
return base_font;
}
- // Check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_project_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
+ StringName theme_name = "font";
+ List<StringName> theme_types;
+ ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types);
- for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- Ref<Font> f = ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
- if (f == this) {
- continue;
- }
- if (f.is_valid()) {
- theme_font = f;
- theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
- }
- return f;
- }
+ ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_null()) {
+ continue;
}
- }
-
- // Lastly, fall back on the items defined in the default Theme, if they exist.
- if (ThemeDB::get_singleton()->get_default_theme().is_valid()) {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_default_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
- if (f == this) {
- continue;
- }
- if (f.is_valid()) {
- theme_font = f;
- theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
- }
- return f;
+ if (!theme->has_font(theme_name, E)) {
+ continue;
}
- }
- // If they don't exist, use any type to return the default/empty value.
- Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName());
- if (f != this) {
+ Ref<Font> f = theme->get_font(theme_name, E);
+ if (f == this) {
+ continue;
+ }
if (f.is_valid()) {
theme_font = f;
theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
@@ -3181,6 +3144,15 @@ Ref<Font> SystemFont::_get_base_font_or_default() const {
}
}
+ Ref<Font> f = global_context->get_fallback_theme()->get_font(theme_name, StringName());
+ if (f != this) {
+ if (f.is_valid()) {
+ theme_font = f;
+ theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED);
+ }
+ return f;
+ }
+
return Ref<Font>();
}
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index a64ae07f05..6d848f5494 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -3466,32 +3466,24 @@ Ref<Font> TextMesh::_get_font_or_default() const {
return font_override;
}
- // Check the project-defined Theme resource.
- if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_project_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
-
- for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- return ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
- }
+ StringName theme_name = "font";
+ List<StringName> theme_types;
+ ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types);
+
+ ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
+ for (const Ref<Theme> &theme : global_context->get_themes()) {
+ if (theme.is_null()) {
+ continue;
}
- }
-
- // Lastly, fall back on the items defined in the default Theme, if they exist.
- {
- List<StringName> theme_types;
- ThemeDB::get_singleton()->get_default_theme()->get_type_dependencies(get_class_name(), StringName(), &theme_types);
for (const StringName &E : theme_types) {
- if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) {
- return ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
+ if (theme->has_font(theme_name, E)) {
+ return theme->get_font(theme_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(Theme::DATA_TYPE_FONT, "font", StringName());
+ return global_context->get_fallback_theme()->get_font(theme_name, StringName());
}
void TextMesh::set_font_size(int p_size) {
diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp
index 799a8471b9..d2a1519d49 100644
--- a/scene/resources/theme.cpp
+++ b/scene/resources/theme.cpp
@@ -1263,11 +1263,7 @@ void Theme::get_type_dependencies(const StringName &p_base_type, const StringNam
}
// Continue building the chain using native class hierarchy.
- 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);
- }
+ ThemeDB::get_singleton()->get_native_type_dependencies(p_base_type, p_list);
}
// Internal methods for getting lists as a Vector of String (compatible with public API).
diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp
index ae2a629c1d..f24569a02b 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,117 @@ 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;
+ themes.push_back(project_theme);
+ 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_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 +335,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 +345,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 +363,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..b1e50e8342 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,45 @@ 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_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() {}
};
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index a4306db6f4..3a80fc8c00 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -238,7 +238,9 @@ struct GodotTestCaseListener : public doctest::IReporter {
RenderingServerDefault::get_singleton()->set_render_loop_enabled(false);
// ThemeDB requires RenderingServer to initialize the default theme.
- // So we have to do this for each test case.
+ // So we have to do this for each test case. Also make sure there is
+ // no residual theme from something else.
+ ThemeDB::get_singleton()->finalize_theme();
ThemeDB::get_singleton()->initialize_theme_noproject();
physics_server_3d = PhysicsServer3DManager::get_singleton()->new_default_server();