Merge pull request #81130 from YuriSizov/theme-context-for-global-themes
Implement a system to contextualize global themes
This commit is contained in:
commit
8dc15e8429
22 changed files with 748 additions and 396 deletions
|
@ -63,6 +63,7 @@
|
|||
#include "scene/resources/image_texture.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
#include "scene/resources/portable_compressed_texture.h"
|
||||
#include "scene/theme/theme_db.h"
|
||||
#include "servers/display_server.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
#include "servers/physics_server_2d.h"
|
||||
|
@ -454,6 +455,119 @@ void EditorNode::_select_default_main_screen_plugin() {
|
|||
editor_select(-1);
|
||||
}
|
||||
|
||||
void EditorNode::_update_theme(bool p_skip_creation) {
|
||||
if (!p_skip_creation) {
|
||||
theme = create_custom_theme(theme);
|
||||
DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));
|
||||
}
|
||||
|
||||
List<Ref<Theme>> editor_themes;
|
||||
editor_themes.push_back(theme);
|
||||
editor_themes.push_back(ThemeDB::get_singleton()->get_default_theme());
|
||||
|
||||
ThemeContext *node_tc = ThemeDB::get_singleton()->get_theme_context(this);
|
||||
if (node_tc) {
|
||||
node_tc->set_themes(editor_themes);
|
||||
} else {
|
||||
ThemeDB::get_singleton()->create_theme_context(this, editor_themes);
|
||||
}
|
||||
|
||||
Window *window = get_window();
|
||||
if (window) {
|
||||
ThemeContext *window_tc = ThemeDB::get_singleton()->get_theme_context(window);
|
||||
if (window_tc) {
|
||||
window_tc->set_themes(editor_themes);
|
||||
} else {
|
||||
ThemeDB::get_singleton()->create_theme_context(window, editor_themes);
|
||||
}
|
||||
}
|
||||
|
||||
if (CanvasItemEditor::get_singleton()->get_theme_preview() == CanvasItemEditor::THEME_PREVIEW_EDITOR) {
|
||||
update_preview_themes(CanvasItemEditor::THEME_PREVIEW_EDITOR);
|
||||
}
|
||||
|
||||
gui_base->add_theme_style_override("panel", theme->get_stylebox(SNAME("Background"), EditorStringName(EditorStyles)));
|
||||
main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, theme->get_constant(SNAME("window_border_margin"), EditorStringName(Editor)));
|
||||
main_vbox->add_theme_constant_override("separation", theme->get_constant(SNAME("top_bar_separation"), EditorStringName(Editor)));
|
||||
|
||||
scene_root_parent->add_theme_style_override("panel", theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
|
||||
bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
main_menu->add_theme_style_override("hover", theme->get_stylebox(SNAME("MenuHover"), EditorStringName(EditorStyles)));
|
||||
prev_scene->set_icon(theme->get_icon(SNAME("PrevScene"), EditorStringName(EditorIcons)));
|
||||
distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
|
||||
bottom_panel_raise->set_icon(theme->get_icon(SNAME("ExpandBottomDock"), EditorStringName(EditorIcons)));
|
||||
|
||||
if (gui_base->is_layout_rtl()) {
|
||||
dock_tab_move_left->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
|
||||
dock_tab_move_right->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
|
||||
} else {
|
||||
dock_tab_move_left->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
|
||||
dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
|
||||
}
|
||||
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_REPORT_A_BUG), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_COPY_SYSTEM_INFO), theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SUGGEST_A_FEATURE), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SEND_DOCS_FEEDBACK), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_COMMUNITY), theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), theme->get_icon(SNAME("Godot"), EditorStringName(EditorIcons)));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), theme->get_icon(SNAME("Heart"), EditorStringName(EditorIcons)));
|
||||
|
||||
for (int i = 0; i < main_editor_buttons.size(); i++) {
|
||||
main_editor_buttons.write[i]->add_theme_font_override("font", theme->get_font(SNAME("main_button_font"), EditorStringName(EditorFonts)));
|
||||
main_editor_buttons.write[i]->add_theme_font_size_override("font_size", theme->get_font_size(SNAME("main_button_font_size"), EditorStringName(EditorFonts)));
|
||||
}
|
||||
|
||||
if (EditorDebuggerNode::get_singleton()->is_visible()) {
|
||||
bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < main_editor_buttons.size(); i++) {
|
||||
Button *tb = main_editor_buttons[i];
|
||||
EditorPlugin *p_editor = editor_table[i];
|
||||
Ref<Texture2D> icon = p_editor->get_icon();
|
||||
|
||||
if (icon.is_valid()) {
|
||||
tb->set_icon(icon);
|
||||
} else if (theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
|
||||
tb->set_icon(theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorNode::update_preview_themes(int p_mode) {
|
||||
if (!scene_root->is_inside_tree()) {
|
||||
return; // Too early.
|
||||
}
|
||||
|
||||
List<Ref<Theme>> preview_themes;
|
||||
|
||||
switch (p_mode) {
|
||||
case CanvasItemEditor::THEME_PREVIEW_PROJECT:
|
||||
preview_themes.push_back(ThemeDB::get_singleton()->get_project_theme());
|
||||
break;
|
||||
|
||||
case CanvasItemEditor::THEME_PREVIEW_EDITOR:
|
||||
preview_themes.push_back(get_editor_theme());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
preview_themes.push_back(ThemeDB::get_singleton()->get_default_theme());
|
||||
|
||||
ThemeContext *preview_context = ThemeDB::get_singleton()->get_theme_context(scene_root);
|
||||
if (preview_context) {
|
||||
preview_context->set_themes(preview_themes);
|
||||
} else {
|
||||
ThemeDB::get_singleton()->create_theme_context(scene_root, preview_themes);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorNode::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_PROCESS: {
|
||||
|
@ -487,7 +601,7 @@ void EditorNode::_notification(int p_what) {
|
|||
|
||||
// Update the icon itself only when the spinner is visible.
|
||||
if (EDITOR_GET("interface/editor/show_update_spinner")) {
|
||||
update_spinner->set_icon(gui_base->get_editor_theme_icon("Progress" + itos(update_spinner_step + 1)));
|
||||
update_spinner->set_icon(theme->get_icon("Progress" + itos(update_spinner_step + 1), EditorStringName(EditorIcons)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -507,9 +621,11 @@ void EditorNode::_notification(int p_what) {
|
|||
if (window) {
|
||||
// Handle macOS fullscreen and extend-to-title changes.
|
||||
window->connect("titlebar_changed", callable_mp(this, &EditorNode::_titlebar_resized));
|
||||
window->set_theme(theme);
|
||||
}
|
||||
|
||||
// Theme has already been created in the constructor, so we can skip that step.
|
||||
_update_theme(true);
|
||||
|
||||
OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/low_processor_mode_sleep_usec")));
|
||||
get_tree()->get_root()->set_as_audio_listener_3d(false);
|
||||
get_tree()->get_root()->set_as_audio_listener_2d(false);
|
||||
|
@ -524,6 +640,7 @@ void EditorNode::_notification(int p_what) {
|
|||
command_palette->register_shortcuts_as_command();
|
||||
|
||||
MessageQueue::get_singleton()->push_callable(callable_mp(this, &EditorNode::_begin_first_scan));
|
||||
|
||||
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
|
||||
} break;
|
||||
|
||||
|
@ -585,6 +702,10 @@ void EditorNode::_notification(int p_what) {
|
|||
|
||||
_titlebar_resized();
|
||||
|
||||
// Set up a theme context for the 2D preview viewport using the stored preview theme.
|
||||
CanvasItemEditor::ThemePreviewMode theme_preview_mode = (CanvasItemEditor::ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", CanvasItemEditor::THEME_PREVIEW_PROJECT);
|
||||
update_preview_themes(theme_preview_mode);
|
||||
|
||||
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
|
||||
} break;
|
||||
|
||||
|
@ -632,73 +753,14 @@ void EditorNode::_notification(int p_what) {
|
|||
EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/scale_gizmo_handles");
|
||||
|
||||
if (theme_changed) {
|
||||
theme = create_custom_theme(theme_base->get_theme());
|
||||
DisplayServer::set_early_window_clear_color_override(true, theme->get_color(SNAME("background"), EditorStringName(Editor)));
|
||||
|
||||
theme_base->set_theme(theme);
|
||||
gui_base->set_theme(theme);
|
||||
get_window()->set_theme(theme);
|
||||
|
||||
gui_base->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("Background"), EditorStringName(EditorStyles)));
|
||||
main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, gui_base->get_theme_constant(SNAME("window_border_margin"), EditorStringName(Editor)));
|
||||
main_vbox->add_theme_constant_override("separation", gui_base->get_theme_constant(SNAME("top_bar_separation"), EditorStringName(Editor)));
|
||||
scene_root_parent->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
|
||||
bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), EditorStringName(EditorStyles)));
|
||||
_update_theme();
|
||||
}
|
||||
|
||||
scene_tabs->update_scene_tabs();
|
||||
recent_scenes->reset_size();
|
||||
|
||||
// Update debugger area.
|
||||
if (EditorDebuggerNode::get_singleton()->is_visible()) {
|
||||
bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
|
||||
// Update icons.
|
||||
for (int i = 0; i < singleton->main_editor_buttons.size(); i++) {
|
||||
Button *tb = singleton->main_editor_buttons[i];
|
||||
EditorPlugin *p_editor = singleton->editor_table[i];
|
||||
Ref<Texture2D> icon = p_editor->get_icon();
|
||||
|
||||
if (icon.is_valid()) {
|
||||
tb->set_icon(icon);
|
||||
} else if (singleton->gui_base->has_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
|
||||
tb->set_icon(singleton->gui_base->get_editor_theme_icon(p_editor->get_name()));
|
||||
}
|
||||
}
|
||||
|
||||
_build_icon_type_cache();
|
||||
|
||||
prev_scene->set_icon(gui_base->get_editor_theme_icon(SNAME("PrevScene")));
|
||||
distraction_free->set_icon(gui_base->get_editor_theme_icon(SNAME("DistractionFree")));
|
||||
|
||||
bottom_panel_raise->set_icon(gui_base->get_editor_theme_icon(SNAME("ExpandBottomDock")));
|
||||
|
||||
if (gui_base->is_layout_rtl()) {
|
||||
dock_tab_move_left->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
|
||||
dock_tab_move_right->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
|
||||
} else {
|
||||
dock_tab_move_left->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
|
||||
dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
|
||||
}
|
||||
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), gui_base->get_editor_theme_icon(SNAME("HelpSearch")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), gui_base->get_editor_theme_icon(SNAME("ExternalLink")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), gui_base->get_editor_theme_icon(SNAME("ExternalLink")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_REPORT_A_BUG), gui_base->get_editor_theme_icon(SNAME("ExternalLink")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_COPY_SYSTEM_INFO), gui_base->get_editor_theme_icon(SNAME("ActionCopy")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_editor_theme_icon(SNAME("ExternalLink")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_editor_theme_icon(SNAME("ExternalLink")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_COMMUNITY), gui_base->get_editor_theme_icon(SNAME("ExternalLink")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), gui_base->get_editor_theme_icon(SNAME("Godot")));
|
||||
help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_editor_theme_icon(SNAME("Heart")));
|
||||
|
||||
for (int i = 0; i < main_editor_buttons.size(); i++) {
|
||||
main_editor_buttons.write[i]->add_theme_font_override("font", gui_base->get_theme_font(SNAME("main_button_font"), EditorStringName(EditorFonts)));
|
||||
main_editor_buttons.write[i]->add_theme_font_size_override("font_size", gui_base->get_theme_font_size(SNAME("main_button_font_size"), EditorStringName(EditorFonts)));
|
||||
}
|
||||
|
||||
HashSet<String> updated_textfile_extensions;
|
||||
bool extensions_match = true;
|
||||
const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
|
||||
|
@ -735,8 +797,7 @@ void EditorNode::_update_update_spinner() {
|
|||
// Make the icon modulate color overbright because icons are not completely white on a dark theme.
|
||||
// On a light theme, icons are dark, so we need to modulate them with an even brighter color.
|
||||
const bool dark_theme = EditorSettings::get_singleton()->is_dark_theme();
|
||||
update_spinner->set_self_modulate(
|
||||
gui_base->get_theme_color(SNAME("error_color"), EditorStringName(Editor)) * (dark_theme ? Color(1.1, 1.1, 1.1) : Color(4.25, 4.25, 4.25)));
|
||||
update_spinner->set_self_modulate(theme->get_color(SNAME("error_color"), EditorStringName(Editor)) * (dark_theme ? Color(1.1, 1.1, 1.1) : Color(4.25, 4.25, 4.25)));
|
||||
} else {
|
||||
update_spinner->set_tooltip_text(TTR("Spins when the editor window redraws."));
|
||||
update_spinner->set_self_modulate(Color(1, 1, 1));
|
||||
|
@ -3131,12 +3192,12 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed
|
|||
tb->set_icon(icon);
|
||||
// Make sure the control is updated if the icon is reimported.
|
||||
icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size));
|
||||
} else if (singleton->gui_base->has_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
|
||||
tb->set_icon(singleton->gui_base->get_editor_theme_icon(p_editor->get_name()));
|
||||
} else if (singleton->theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
|
||||
tb->set_icon(singleton->theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
|
||||
}
|
||||
|
||||
tb->add_theme_font_override("font", singleton->gui_base->get_theme_font(SNAME("main_button_font"), EditorStringName(EditorFonts)));
|
||||
tb->add_theme_font_size_override("font_size", singleton->gui_base->get_theme_font_size(SNAME("main_button_font_size"), EditorStringName(EditorFonts)));
|
||||
tb->add_theme_font_override("font", singleton->theme->get_font(SNAME("main_button_font"), EditorStringName(EditorFonts)));
|
||||
tb->add_theme_font_size_override("font_size", singleton->theme->get_font_size(SNAME("main_button_font_size"), EditorStringName(EditorFonts)));
|
||||
|
||||
singleton->main_editor_buttons.push_back(tb);
|
||||
singleton->main_editor_button_hb->add_child(tb);
|
||||
|
@ -4067,14 +4128,14 @@ void EditorNode::notify_all_debug_sessions_exited() {
|
|||
|
||||
void EditorNode::add_io_error(const String &p_error) {
|
||||
DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id());
|
||||
singleton->load_errors->add_image(singleton->gui_base->get_editor_theme_icon(SNAME("Error")));
|
||||
singleton->load_errors->add_image(singleton->theme->get_icon(SNAME("Error"), EditorStringName(EditorIcons)));
|
||||
singleton->load_errors->add_text(p_error + "\n");
|
||||
EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
|
||||
}
|
||||
|
||||
void EditorNode::add_io_warning(const String &p_warning) {
|
||||
DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id());
|
||||
singleton->load_errors->add_image(singleton->gui_base->get_editor_theme_icon(SNAME("Warning")));
|
||||
singleton->load_errors->add_image(singleton->theme->get_icon(SNAME("Warning"), EditorStringName(EditorIcons)));
|
||||
singleton->load_errors->add_text(p_warning + "\n");
|
||||
EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
|
||||
}
|
||||
|
@ -4212,8 +4273,8 @@ Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, cons
|
|||
// scripts extending other scripts and for built-in classes.
|
||||
String script_class_name = p_script->get_language()->get_global_class_name(p_script->get_path());
|
||||
String base_type = ScriptServer::get_global_class_native_base(script_class_name);
|
||||
if (gui_base && gui_base->has_theme_icon(base_type, EditorStringName(EditorIcons))) {
|
||||
return gui_base->get_editor_theme_icon(base_type);
|
||||
if (theme.is_valid() && theme->has_icon(base_type, EditorStringName(EditorIcons))) {
|
||||
return theme->get_icon(base_type, EditorStringName(EditorIcons));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4236,22 +4297,22 @@ Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, cons
|
|||
|
||||
// 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, EditorStringName(EditorIcons))) {
|
||||
return gui_base->get_editor_theme_icon(p_class);
|
||||
if (theme.is_valid()) {
|
||||
if (theme->has_icon(p_class, EditorStringName(EditorIcons))) {
|
||||
return theme->get_icon(p_class, EditorStringName(EditorIcons));
|
||||
}
|
||||
|
||||
if (!p_fallback.is_empty() && gui_base->has_theme_icon(p_fallback, EditorStringName(EditorIcons))) {
|
||||
return gui_base->get_editor_theme_icon(p_fallback);
|
||||
if (!p_fallback.is_empty() && theme->has_icon(p_fallback, EditorStringName(EditorIcons))) {
|
||||
return theme->get_icon(p_fallback, EditorStringName(EditorIcons));
|
||||
}
|
||||
|
||||
// If the fallback is empty or wasn't found, use the default fallback.
|
||||
if (ClassDB::class_exists(p_class)) {
|
||||
bool instantiable = !ClassDB::is_virtual(p_class) && ClassDB::can_instantiate(p_class);
|
||||
if (ClassDB::is_parent_class(p_class, SNAME("Node"))) {
|
||||
return gui_base->get_editor_theme_icon(instantiable ? "Node" : "NodeDisabled");
|
||||
return theme->get_icon(instantiable ? "Node" : "NodeDisabled", EditorStringName(EditorIcons));
|
||||
} else {
|
||||
return gui_base->get_editor_theme_icon(instantiable ? "Object" : "ObjectDisabled");
|
||||
return theme->get_icon(instantiable ? "Object" : "ObjectDisabled", EditorStringName(EditorIcons));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4464,12 +4525,12 @@ Ref<Texture2D> EditorNode::_file_dialog_get_icon(const String &p_path) {
|
|||
|
||||
void EditorNode::_build_icon_type_cache() {
|
||||
List<StringName> tl;
|
||||
theme_base->get_theme()->get_icon_list(EditorStringName(EditorIcons), &tl);
|
||||
theme->get_icon_list(EditorStringName(EditorIcons), &tl);
|
||||
for (const StringName &E : tl) {
|
||||
if (!ClassDB::class_exists(E)) {
|
||||
continue;
|
||||
}
|
||||
icon_type_cache[E] = theme_base->get_theme()->get_icon(E, EditorStringName(EditorIcons));
|
||||
icon_type_cache[E] = theme->get_icon(E, EditorStringName(EditorIcons));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4728,7 +4789,7 @@ void EditorNode::_dock_select_draw() {
|
|||
|
||||
Color used = Color(0.6, 0.6, 0.6, 0.8);
|
||||
Color used_selected = Color(0.8, 0.8, 0.8, 0.8);
|
||||
Color tab_selected = theme_base->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));
|
||||
Color tab_selected = theme->get_color(SNAME("mono_color"), EditorStringName(Editor));
|
||||
Color unused = used;
|
||||
unused.a = 0.4;
|
||||
Color unusable = unused;
|
||||
|
@ -5666,9 +5727,9 @@ void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) {
|
|||
}
|
||||
if (EditorDebuggerNode::get_singleton() == bottom_panel_items[p_idx].control) {
|
||||
// This is the debug panel which uses tabs, so the top section should be smaller.
|
||||
bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
|
||||
bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
|
||||
} else {
|
||||
bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
|
||||
center_split->set_collapsed(false);
|
||||
|
@ -5677,7 +5738,7 @@ void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) {
|
|||
}
|
||||
bottom_panel_raise->show();
|
||||
} else {
|
||||
bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
bottom_panel_items[p_idx].button->set_pressed(false);
|
||||
bottom_panel_items[p_idx].control->set_visible(false);
|
||||
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
|
||||
|
@ -5766,7 +5827,7 @@ Variant EditorNode::drag_resource(const Ref<Resource> &p_res, Control *p_from) {
|
|||
|
||||
{
|
||||
// TODO: make proper previews
|
||||
Ref<ImageTexture> texture = gui_base->get_editor_theme_icon(SNAME("FileBigThumb"));
|
||||
Ref<ImageTexture> texture = theme->get_icon(SNAME("FileBigThumb"), EditorStringName(EditorIcons));
|
||||
Ref<Image> img = texture->get_image();
|
||||
img = img->duplicate();
|
||||
img->resize(48, 48); // meh
|
||||
|
@ -5816,10 +5877,10 @@ Variant EditorNode::drag_files_and_dirs(const Vector<String> &p_paths, Control *
|
|||
|
||||
if (p_paths[i].ends_with("/")) {
|
||||
label->set_text(p_paths[i].substr(0, p_paths[i].length() - 1).get_file());
|
||||
icon->set_texture(gui_base->get_editor_theme_icon(SNAME("Folder")));
|
||||
icon->set_texture(theme->get_icon(SNAME("Folder"), EditorStringName(EditorIcons)));
|
||||
} else {
|
||||
label->set_text(p_paths[i].get_file());
|
||||
icon->set_texture(gui_base->get_editor_theme_icon(SNAME("File")));
|
||||
icon->set_texture(theme->get_icon(SNAME("File"), EditorStringName(EditorIcons)));
|
||||
}
|
||||
icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
|
||||
icon->set_size(Size2(16, 16));
|
||||
|
@ -6875,32 +6936,22 @@ EditorNode::EditorNode() {
|
|||
textfile_extensions.insert(E);
|
||||
}
|
||||
|
||||
theme_base = memnew(Control);
|
||||
add_child(theme_base);
|
||||
theme_base->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
|
||||
gui_base = memnew(Panel);
|
||||
theme_base->add_child(gui_base);
|
||||
gui_base->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
|
||||
theme_base->set_theme(theme);
|
||||
gui_base->set_theme(theme);
|
||||
gui_base->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("Background"), EditorStringName(EditorStyles)));
|
||||
|
||||
resource_preview = memnew(EditorResourcePreview);
|
||||
add_child(resource_preview);
|
||||
progress_dialog = memnew(ProgressDialog);
|
||||
progress_dialog->set_unparent_when_invisible(true);
|
||||
|
||||
gui_base = memnew(Panel);
|
||||
add_child(gui_base);
|
||||
|
||||
// Take up all screen.
|
||||
gui_base->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
||||
gui_base->set_anchor(SIDE_RIGHT, Control::ANCHOR_END);
|
||||
gui_base->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
|
||||
gui_base->set_end(Point2(0, 0));
|
||||
|
||||
main_vbox = memnew(VBoxContainer);
|
||||
gui_base->add_child(main_vbox);
|
||||
main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, gui_base->get_theme_constant(SNAME("window_border_margin"), EditorStringName(Editor)));
|
||||
main_vbox->add_theme_constant_override("separation", gui_base->get_theme_constant(SNAME("top_bar_separation"), EditorStringName(Editor)));
|
||||
|
||||
title_bar = memnew(EditorTitleBar);
|
||||
main_vbox->add_child(title_bar);
|
||||
|
@ -6978,11 +7029,6 @@ EditorNode::EditorNode() {
|
|||
HBoxContainer *dock_hb = memnew(HBoxContainer);
|
||||
dock_tab_move_left = memnew(Button);
|
||||
dock_tab_move_left->set_flat(true);
|
||||
if (gui_base->is_layout_rtl()) {
|
||||
dock_tab_move_left->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
|
||||
} else {
|
||||
dock_tab_move_left->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
|
||||
}
|
||||
dock_tab_move_left->set_focus_mode(Control::FOCUS_NONE);
|
||||
dock_tab_move_left->connect("pressed", callable_mp(this, &EditorNode::_dock_move_left));
|
||||
dock_hb->add_child(dock_tab_move_left);
|
||||
|
@ -6995,11 +7041,6 @@ EditorNode::EditorNode() {
|
|||
|
||||
dock_tab_move_right = memnew(Button);
|
||||
dock_tab_move_right->set_flat(true);
|
||||
if (gui_base->is_layout_rtl()) {
|
||||
dock_tab_move_right->set_icon(theme->get_icon(SNAME("Back"), EditorStringName(EditorIcons)));
|
||||
} else {
|
||||
dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), EditorStringName(EditorIcons)));
|
||||
}
|
||||
dock_tab_move_right->set_focus_mode(Control::FOCUS_NONE);
|
||||
dock_tab_move_right->connect("pressed", callable_mp(this, &EditorNode::_dock_move_right));
|
||||
|
||||
|
@ -7065,14 +7106,13 @@ EditorNode::EditorNode() {
|
|||
ED_SHORTCUT_OVERRIDE("editor/distraction_free_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::D);
|
||||
distraction_free->set_shortcut(ED_GET_SHORTCUT("editor/distraction_free_mode"));
|
||||
distraction_free->set_tooltip_text(TTR("Toggle distraction-free mode."));
|
||||
distraction_free->set_icon(gui_base->get_editor_theme_icon(SNAME("DistractionFree")));
|
||||
distraction_free->set_toggle_mode(true);
|
||||
scene_tabs->add_extra_button(distraction_free);
|
||||
distraction_free->connect("pressed", callable_mp(this, &EditorNode::_toggle_distraction_free_mode));
|
||||
|
||||
scene_root_parent = memnew(PanelContainer);
|
||||
scene_root_parent->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
|
||||
scene_root_parent->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
|
||||
scene_root_parent->add_theme_style_override("panel", theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
|
||||
scene_root_parent->set_draw_behind_parent(true);
|
||||
srt->add_child(scene_root_parent);
|
||||
scene_root_parent->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
@ -7080,7 +7120,6 @@ EditorNode::EditorNode() {
|
|||
scene_root = memnew(SubViewport);
|
||||
scene_root->set_embedding_subwindows(true);
|
||||
scene_root->set_disable_3d(true);
|
||||
|
||||
scene_root->set_disable_input(true);
|
||||
scene_root->set_as_audio_listener_2d(true);
|
||||
|
||||
|
@ -7103,7 +7142,7 @@ EditorNode::EditorNode() {
|
|||
main_menu = memnew(MenuBar);
|
||||
title_bar->add_child(main_menu);
|
||||
|
||||
main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), EditorStringName(EditorStyles)));
|
||||
main_menu->add_theme_style_override("hover", theme->get_stylebox(SNAME("MenuHover"), EditorStringName(EditorStyles)));
|
||||
main_menu->set_flat(true);
|
||||
main_menu->set_start_index(0); // Main menu, add to the start of global menu.
|
||||
main_menu->set_prefer_global_menu(global_menu);
|
||||
|
@ -7116,7 +7155,6 @@ EditorNode::EditorNode() {
|
|||
|
||||
prev_scene = memnew(Button);
|
||||
prev_scene->set_flat(true);
|
||||
prev_scene->set_icon(gui_base->get_editor_theme_icon(SNAME("PrevScene")));
|
||||
prev_scene->set_tooltip_text(TTR("Go to previously opened scene."));
|
||||
prev_scene->set_disabled(true);
|
||||
prev_scene->connect("pressed", callable_mp(this, &EditorNode::_menu_option).bind(FILE_OPEN_PREV));
|
||||
|
@ -7277,8 +7315,8 @@ EditorNode::EditorNode() {
|
|||
|
||||
if (can_expand && global_menu) {
|
||||
project_title = memnew(Label);
|
||||
project_title->add_theme_font_override("font", gui_base->get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
|
||||
project_title->add_theme_font_size_override("font_size", gui_base->get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)));
|
||||
project_title->add_theme_font_override("font", theme->get_font(SNAME("bold"), EditorStringName(EditorFonts)));
|
||||
project_title->add_theme_font_size_override("font_size", theme->get_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)));
|
||||
project_title->set_focus_mode(Control::FOCUS_NONE);
|
||||
project_title->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
|
||||
project_title->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
|
@ -7354,22 +7392,22 @@ EditorNode::EditorNode() {
|
|||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/editor_help", TTR("Search Help"), Key::F1);
|
||||
ED_SHORTCUT_OVERRIDE("editor/editor_help", "macos", KeyModifierMask::ALT | Key::SPACE);
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("HelpSearch")), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("HelpSearch"), EditorStringName(EditorIcons)), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH);
|
||||
help_menu->add_separator();
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("ExternalLink")), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("ExternalLink")), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA);
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("ExternalLink")), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("ActionCopy")), ED_SHORTCUT_AND_COMMAND("editor/copy_system_info", TTR("Copy System Info")), HELP_COPY_SYSTEM_INFO);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/copy_system_info", TTR("Copy System Info")), HELP_COPY_SYSTEM_INFO);
|
||||
help_menu->set_item_tooltip(-1, TTR("Copies the system info as a single-line text into the clipboard."));
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("ExternalLink")), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("ExternalLink")), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("ExternalLink")), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY);
|
||||
help_menu->add_separator();
|
||||
if (!global_menu || !OS::get_singleton()->has_feature("macos")) {
|
||||
// On macOS "Quit" and "About" options are in the "app" menu.
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("Godot")), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("Godot"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT);
|
||||
}
|
||||
help_menu->add_icon_shortcut(gui_base->get_editor_theme_icon(SNAME("Heart")), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT);
|
||||
help_menu->add_icon_shortcut(theme->get_icon(SNAME("Heart"), EditorStringName(EditorIcons)), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT);
|
||||
|
||||
// Spacer to center 2D / 3D / Script buttons.
|
||||
Control *right_spacer = memnew(Control);
|
||||
|
@ -7391,8 +7429,8 @@ EditorNode::EditorNode() {
|
|||
renderer->set_fit_to_longest_item(false);
|
||||
renderer->set_focus_mode(Control::FOCUS_NONE);
|
||||
renderer->connect("item_selected", callable_mp(this, &EditorNode::_renderer_selected));
|
||||
renderer->add_theme_font_override("font", gui_base->get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
|
||||
renderer->add_theme_font_size_override("font_size", gui_base->get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)));
|
||||
renderer->add_theme_font_override("font", theme->get_font(SNAME("bold"), EditorStringName(EditorFonts)));
|
||||
renderer->add_theme_font_size_override("font_size", theme->get_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)));
|
||||
renderer->set_tooltip_text(TTR("Choose a renderer."));
|
||||
|
||||
right_menu_hb->add_child(renderer);
|
||||
|
@ -7453,7 +7491,7 @@ EditorNode::EditorNode() {
|
|||
|
||||
update_spinner = memnew(MenuButton);
|
||||
right_menu_hb->add_child(update_spinner);
|
||||
update_spinner->set_icon(gui_base->get_editor_theme_icon(SNAME("Progress1")));
|
||||
update_spinner->set_icon(theme->get_icon(SNAME("Progress1"), EditorStringName(EditorIcons)));
|
||||
update_spinner->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
|
||||
PopupMenu *p = update_spinner->get_popup();
|
||||
p->add_radio_check_item(TTR("Update Continuously"), SETTINGS_UPDATE_CONTINUOUSLY);
|
||||
|
@ -7536,7 +7574,7 @@ EditorNode::EditorNode() {
|
|||
// Bottom panels.
|
||||
|
||||
bottom_panel = memnew(PanelContainer);
|
||||
bottom_panel->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
bottom_panel->add_theme_style_override("panel", theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
center_split->add_child(bottom_panel);
|
||||
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
|
||||
|
||||
|
@ -7581,14 +7619,11 @@ EditorNode::EditorNode() {
|
|||
bottom_panel_hb->add_child(h_spacer);
|
||||
|
||||
bottom_panel_raise = memnew(Button);
|
||||
bottom_panel_raise->set_flat(true);
|
||||
bottom_panel_raise->set_icon(gui_base->get_editor_theme_icon(SNAME("ExpandBottomDock")));
|
||||
|
||||
bottom_panel_raise->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
|
||||
|
||||
bottom_panel_hb->add_child(bottom_panel_raise);
|
||||
bottom_panel_raise->hide();
|
||||
bottom_panel_raise->set_flat(true);
|
||||
bottom_panel_raise->set_toggle_mode(true);
|
||||
bottom_panel_raise->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
|
||||
bottom_panel_raise->connect("toggled", callable_mp(this, &EditorNode::_bottom_panel_raise_toggled));
|
||||
|
||||
log = memnew(EditorLog);
|
||||
|
|
|
@ -295,7 +295,6 @@ private:
|
|||
bool is_main_screen_editing = false;
|
||||
|
||||
PanelContainer *scene_root_parent = nullptr;
|
||||
Control *theme_base = nullptr;
|
||||
Control *gui_base = nullptr;
|
||||
VBoxContainer *main_vbox = nullptr;
|
||||
OptionButton *renderer = nullptr;
|
||||
|
@ -525,6 +524,7 @@ private:
|
|||
static void _resource_saved(Ref<Resource> p_resource, const String &p_path);
|
||||
static void _resource_loaded(Ref<Resource> p_resource, const String &p_path);
|
||||
|
||||
void _update_theme(bool p_skip_creation = false);
|
||||
void _build_icon_type_cache();
|
||||
void _enable_pending_addons();
|
||||
|
||||
|
@ -851,6 +851,8 @@ public:
|
|||
void stop_child_process(OS::ProcessID p_pid);
|
||||
|
||||
Ref<Theme> get_editor_theme() const { return theme; }
|
||||
void update_preview_themes(int p_mode);
|
||||
|
||||
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");
|
||||
|
@ -867,7 +869,6 @@ public:
|
|||
Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only);
|
||||
|
||||
Control *get_gui_base() { return gui_base; }
|
||||
Control *get_theme_base() { return gui_base->get_parent_control(); }
|
||||
|
||||
void save_scene_to_path(String p_file, bool p_with_preview = true) {
|
||||
if (p_with_preview) {
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "editor/editor_paths.h"
|
||||
#include "editor/editor_scale.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
|
||||
bool EditorResourcePreviewGenerator::handles(const String &p_type) const {
|
||||
|
@ -341,7 +342,8 @@ void EditorResourcePreview::_thread() {
|
|||
|
||||
void EditorResourcePreview::_update_thumbnail_sizes() {
|
||||
if (small_thumbnail_size == -1) {
|
||||
small_thumbnail_size = EditorNode::get_singleton()->get_theme_base()->get_editor_theme_icon(SNAME("Object"))->get_width(); // Kind of a workaround to retrieve the default icon size
|
||||
// Kind of a workaround to retrieve the default icon size.
|
||||
small_thumbnail_size = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Object"), EditorStringName(EditorIcons))->get_width();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1034,6 +1034,22 @@ void CanvasItemEditor::_on_grid_menu_id_pressed(int p_id) {
|
|||
viewport->queue_redraw();
|
||||
}
|
||||
|
||||
void CanvasItemEditor::_switch_theme_preview(int p_mode) {
|
||||
view_menu->get_popup()->hide();
|
||||
|
||||
if (theme_preview == p_mode) {
|
||||
return;
|
||||
}
|
||||
theme_preview = (ThemePreviewMode)p_mode;
|
||||
EditorSettings::get_singleton()->set_project_metadata("2d_editor", "theme_preview", theme_preview);
|
||||
|
||||
for (int i = 0; i < THEME_PREVIEW_MAX; i++) {
|
||||
theme_menu->set_item_checked(i, i == theme_preview);
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->update_preview_themes(theme_preview);
|
||||
}
|
||||
|
||||
bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_event) {
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
Ref<InputEventMouseButton> b = p_event;
|
||||
|
@ -5322,6 +5338,20 @@ CanvasItemEditor::CanvasItemEditor() {
|
|||
p->add_separator();
|
||||
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale")), PREVIEW_CANVAS_SCALE);
|
||||
|
||||
theme_menu = memnew(PopupMenu);
|
||||
theme_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_switch_theme_preview));
|
||||
theme_menu->set_name("ThemeMenu");
|
||||
theme_menu->add_radio_check_item(TTR("Project theme"), THEME_PREVIEW_PROJECT);
|
||||
theme_menu->add_radio_check_item(TTR("Editor theme"), THEME_PREVIEW_EDITOR);
|
||||
theme_menu->add_radio_check_item(TTR("Default theme"), THEME_PREVIEW_DEFAULT);
|
||||
p->add_child(theme_menu);
|
||||
p->add_submenu_item(TTR("Preview Theme"), "ThemeMenu");
|
||||
|
||||
theme_preview = (ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", THEME_PREVIEW_PROJECT);
|
||||
for (int i = 0; i < THEME_PREVIEW_MAX; i++) {
|
||||
theme_menu->set_item_checked(i, i == theme_preview);
|
||||
}
|
||||
|
||||
main_menu_hbox->add_child(memnew(VSeparator));
|
||||
|
||||
// Contextual toolbars.
|
||||
|
|
|
@ -325,6 +325,7 @@ private:
|
|||
Button *override_camera_button = nullptr;
|
||||
MenuButton *view_menu = nullptr;
|
||||
PopupMenu *grid_menu = nullptr;
|
||||
PopupMenu *theme_menu = nullptr;
|
||||
HBoxContainer *animation_hb = nullptr;
|
||||
MenuButton *animation_menu = nullptr;
|
||||
|
||||
|
@ -404,6 +405,19 @@ private:
|
|||
void _prepare_grid_menu();
|
||||
void _on_grid_menu_id_pressed(int p_id);
|
||||
|
||||
public:
|
||||
enum ThemePreviewMode {
|
||||
THEME_PREVIEW_PROJECT,
|
||||
THEME_PREVIEW_EDITOR,
|
||||
THEME_PREVIEW_DEFAULT,
|
||||
|
||||
THEME_PREVIEW_MAX // The number of options for enumerating.
|
||||
};
|
||||
|
||||
private:
|
||||
ThemePreviewMode theme_preview = THEME_PREVIEW_PROJECT;
|
||||
void _switch_theme_preview(int p_mode);
|
||||
|
||||
List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true) const;
|
||||
Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list);
|
||||
void _expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D(), bool include_locked_nodes = true);
|
||||
|
@ -558,6 +572,8 @@ public:
|
|||
|
||||
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
|
||||
|
||||
ThemePreviewMode get_theme_preview() const { return theme_preview; }
|
||||
|
||||
EditorSelection *editor_selection = nullptr;
|
||||
|
||||
CanvasItemEditor();
|
||||
|
|
|
@ -1612,6 +1612,8 @@ void ScriptEditor::_notification(int p_what) {
|
|||
case NOTIFICATION_TRANSLATION_CHANGED:
|
||||
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
tab_container->add_theme_style_override("panel", get_theme_stylebox(SNAME("ScriptEditor"), EditorStringName(EditorStyles)));
|
||||
|
||||
help_search->set_icon(get_editor_theme_icon(SNAME("HelpSearch")));
|
||||
site_search->set_icon(get_editor_theme_icon(SNAME("ExternalLink")));
|
||||
|
||||
|
@ -1628,7 +1630,7 @@ void ScriptEditor::_notification(int p_what) {
|
|||
filter_scripts->set_right_icon(get_editor_theme_icon(SNAME("Search")));
|
||||
filter_methods->set_right_icon(get_editor_theme_icon(SNAME("Search")));
|
||||
|
||||
filename->add_theme_style_override("normal", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")));
|
||||
filename->add_theme_style_override("normal", get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")));
|
||||
|
||||
recent_scripts->reset_size();
|
||||
|
||||
|
@ -1639,6 +1641,9 @@ void ScriptEditor::_notification(int p_what) {
|
|||
} break;
|
||||
|
||||
case NOTIFICATION_READY: {
|
||||
// Can't set own styles in NOTIFICATION_THEME_CHANGED, so for now this will do.
|
||||
add_theme_style_override("panel", get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles")));
|
||||
|
||||
get_tree()->connect("tree_changed", callable_mp(this, &ScriptEditor::_tree_changed));
|
||||
InspectorDock::get_singleton()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open));
|
||||
EditorNode::get_singleton()->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search));
|
||||
|
@ -4139,9 +4144,6 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
|
|||
|
||||
ScriptServer::edit_request_func = _open_script_request;
|
||||
|
||||
add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), EditorStringName(EditorStyles)));
|
||||
tab_container->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditor"), EditorStringName(EditorStyles)));
|
||||
|
||||
Ref<EditorJSONSyntaxHighlighter> json_syntax_highlighter;
|
||||
json_syntax_highlighter.instantiate();
|
||||
register_syntax_highlighter(json_syntax_highlighter);
|
||||
|
|
|
@ -1217,7 +1217,7 @@ void ThemeItemEditorDialog::_dialog_about_to_show() {
|
|||
import_default_theme_items->reset_item_tree();
|
||||
|
||||
import_editor_theme_items->set_edited_theme(edited_theme);
|
||||
import_editor_theme_items->set_base_theme(EditorNode::get_singleton()->get_theme_base()->get_theme());
|
||||
import_editor_theme_items->set_base_theme(EditorNode::get_singleton()->get_editor_theme());
|
||||
import_editor_theme_items->reset_item_tree();
|
||||
|
||||
import_other_theme_items->set_edited_theme(edited_theme);
|
||||
|
|
|
@ -202,8 +202,14 @@ void ThemeEditorPreview::_notification(int p_what) {
|
|||
}
|
||||
|
||||
connect("visibility_changed", callable_mp(this, &ThemeEditorPreview::_preview_visibility_changed));
|
||||
[[fallthrough]];
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_READY: {
|
||||
List<Ref<Theme>> preview_themes;
|
||||
preview_themes.push_back(ThemeDB::get_singleton()->get_default_theme());
|
||||
ThemeDB::get_singleton()->create_theme_context(preview_root, preview_themes);
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
picker_button->set_icon(get_editor_theme_icon(SNAME("ColorPick")));
|
||||
|
||||
|
@ -247,9 +253,8 @@ ThemeEditorPreview::ThemeEditorPreview() {
|
|||
preview_container = memnew(ScrollContainer);
|
||||
preview_body->add_child(preview_container);
|
||||
|
||||
MarginContainer *preview_root = memnew(MarginContainer);
|
||||
preview_root = memnew(MarginContainer);
|
||||
preview_container->add_child(preview_root);
|
||||
preview_root->set_theme(ThemeDB::get_singleton()->get_default_theme());
|
||||
preview_root->set_clip_contents(true);
|
||||
preview_root->set_custom_minimum_size(Size2(450, 0) * EDSCALE);
|
||||
preview_root->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
|
|
|
@ -44,6 +44,7 @@ class ThemeEditorPreview : public VBoxContainer {
|
|||
GDCLASS(ThemeEditorPreview, VBoxContainer);
|
||||
|
||||
ScrollContainer *preview_container = nullptr;
|
||||
MarginContainer *preview_root = nullptr;
|
||||
ColorRect *preview_bg = nullptr;
|
||||
MarginContainer *preview_overlay = nullptr;
|
||||
Control *picker_overlay = nullptr;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
|
||||
for (const Ref<Theme> &theme : global_context->get_themes()) {
|
||||
if (theme.is_null()) {
|
||||
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) {
|
||||
for (const StringName &E : theme_types) {
|
||||
if (!theme->has_font(theme_name, E)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context();
|
||||
for (const Ref<Theme> &theme : global_context->get_themes()) {
|
||||
if (theme.is_null()) {
|
||||
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) {
|
||||
for (const StringName &E : theme_types) {
|
||||
if (!theme->has_font(theme_name, E)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
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)) {
|
||||
return ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E);
|
||||
if (theme->has_font(theme_name, E)) {
|
||||
return theme->get_font(theme_name, E);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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) {
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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);
|
||||
} else {
|
||||
ERR_PRINT("Error loading custom font '" + font_path + "'");
|
||||
}
|
||||
}
|
||||
// Attempt to load custom project theme and font.
|
||||
|
||||
// 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 (!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 theme '" + theme_path + "'");
|
||||
ERR_PRINT("Error loading custom project theme '" + project_theme_path + "'");
|
||||
}
|
||||
}
|
||||
|
||||
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 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"));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue