Selaa lähdekoodia

Merge pull request #73475 from YuriSizov/theme-is-busy-plz-come-back-later

Add a warning when accessing theme prematurely and fix surfaced issues
Rémi Verschelde 2 vuotta sitten
vanhempi
commit
4f4b5a2785

+ 13 - 1
editor/debugger/editor_performance_profiler.cpp

@@ -234,7 +234,9 @@ TreeItem *EditorPerformanceProfiler::_get_monitor_base(const StringName &p_base_
 	base->set_editable(0, false);
 	base->set_selectable(0, false);
 	base->set_expand_right(0, true);
-	base->set_custom_font(0, get_theme_font(SNAME("bold"), SNAME("EditorFonts")));
+	if (is_inside_tree()) {
+		base->set_custom_font(0, get_theme_font(SNAME("bold"), SNAME("EditorFonts")));
+	}
 	base_map.insert(p_base_name, base);
 	return base;
 }
@@ -368,6 +370,16 @@ List<float> *EditorPerformanceProfiler::get_monitor_data(const StringName &p_nam
 	return nullptr;
 }
 
+void EditorPerformanceProfiler::_notification(int p_what) {
+	switch (p_what) {
+		case NOTIFICATION_THEME_CHANGED: {
+			for (KeyValue<StringName, TreeItem *> &E : base_map) {
+				E.value->set_custom_font(0, get_theme_font(SNAME("bold"), SNAME("EditorFonts")));
+			}
+		} break;
+	}
+}
+
 EditorPerformanceProfiler::EditorPerformanceProfiler() {
 	set_name(TTR("Monitors"));
 	set_split_offset(340 * EDSCALE);

+ 3 - 0
editor/debugger/editor_performance_profiler.h

@@ -79,6 +79,9 @@ private:
 	TreeItem *_create_monitor_item(const StringName &p_monitor_name, TreeItem *p_base);
 	void _marker_input(const Ref<InputEvent> &p_event);
 
+protected:
+	void _notification(int p_what);
+
 public:
 	void reset();
 	void update_monitors(const Vector<StringName> &p_names);

+ 14 - 15
editor/editor_audio_buses.cpp

@@ -1436,6 +1436,15 @@ Size2 EditorAudioMeterNotches::get_minimum_size() const {
 	return Size2(width, height);
 }
 
+void EditorAudioMeterNotches::_update_theme_item_cache() {
+	Control::_update_theme_item_cache();
+
+	theme_cache.notch_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
+
+	theme_cache.font = get_theme_font(SNAME("font"), SNAME("Label"));
+	theme_cache.font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
+}
+
 void EditorAudioMeterNotches::_bind_methods() {
 	ClassDB::bind_method("add_notch", &EditorAudioMeterNotches::add_notch);
 	ClassDB::bind_method("_draw_audio_notches", &EditorAudioMeterNotches::_draw_audio_notches);
@@ -1443,10 +1452,6 @@ void EditorAudioMeterNotches::_bind_methods() {
 
 void EditorAudioMeterNotches::_notification(int p_what) {
 	switch (p_what) {
-		case NOTIFICATION_THEME_CHANGED: {
-			notch_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
-		} break;
-
 		case NOTIFICATION_DRAW: {
 			_draw_audio_notches();
 		} break;
@@ -1454,28 +1459,22 @@ void EditorAudioMeterNotches::_notification(int p_what) {
 }
 
 void EditorAudioMeterNotches::_draw_audio_notches() {
-	Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
-	int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
-	float font_height = font->get_height(font_size);
+	float font_height = theme_cache.font->get_height(theme_cache.font_size);
 
 	for (int i = 0; i < notches.size(); i++) {
 		AudioNotch n = notches[i];
 		draw_line(Vector2(0, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
 				Vector2(line_length * EDSCALE, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
-				notch_color,
+				theme_cache.notch_color,
 				Math::round(EDSCALE));
 
 		if (n.render_db_value) {
-			draw_string(font,
+			draw_string(theme_cache.font,
 					Vector2((line_length + label_space) * EDSCALE,
 							(1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + (font_height / 4) + top_padding),
 					String::num(Math::abs(n.db_value)) + "dB",
-					HORIZONTAL_ALIGNMENT_LEFT, -1, font_size,
-					notch_color);
+					HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size,
+					theme_cache.notch_color);
 		}
 	}
 }
-
-EditorAudioMeterNotches::EditorAudioMeterNotches() {
-	notch_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
-}

+ 10 - 2
editor/editor_audio_buses.h

@@ -244,23 +244,31 @@ private:
 
 	List<AudioNotch> notches;
 
+	struct ThemeCache {
+		Color notch_color;
+
+		Ref<Font> font;
+		int font_size = 0;
+	} theme_cache;
+
 public:
 	const float line_length = 5.0f;
 	const float label_space = 2.0f;
 	const float btm_padding = 9.0f;
 	const float top_padding = 5.0f;
-	Color notch_color;
 
 	void add_notch(float p_normalized_offset, float p_db_value, bool p_render_value = false);
 	Size2 get_minimum_size() const override;
 
 private:
+	virtual void _update_theme_item_cache() override;
+
 	static void _bind_methods();
 	void _notification(int p_what);
 	void _draw_audio_notches();
 
 public:
-	EditorAudioMeterNotches();
+	EditorAudioMeterNotches() {}
 };
 
 class AudioBusesEditorPlugin : public EditorPlugin {

+ 1 - 1
editor/editor_run_native.cpp

@@ -40,6 +40,7 @@ void EditorRunNative::_notification(int p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 			remote_debug->set_icon(get_theme_icon(SNAME("PlayRemote"), SNAME("EditorIcons")));
 		} break;
+
 		case NOTIFICATION_PROCESS: {
 			bool changed = EditorExport::get_singleton()->poll_export_platforms() || first;
 
@@ -155,7 +156,6 @@ bool EditorRunNative::is_deploy_debug_remote_enabled() const {
 EditorRunNative::EditorRunNative() {
 	remote_debug = memnew(MenuButton);
 	remote_debug->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native));
-	remote_debug->set_icon(get_theme_icon(SNAME("PlayRemote"), SNAME("EditorIcons")));
 	remote_debug->set_tooltip_text(TTR("Remote Debug"));
 	remote_debug->set_disabled(true);
 

+ 1 - 5
editor/import/dynamic_font_import_settings.cpp

@@ -924,9 +924,9 @@ void DynamicFontImportSettings::_notification(int p_what) {
 			connect("confirmed", callable_mp(this, &DynamicFontImportSettings::_re_import));
 		} break;
 
-		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_THEME_CHANGED: {
 			add_var->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+			label_warn->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor")));
 		} break;
 
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
@@ -1284,8 +1284,6 @@ DynamicFontImportSettings::DynamicFontImportSettings() {
 	options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "variation_face_index"), 0));
 	options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::TRANSFORM2D, "variation_transform"), Transform2D()));
 
-	Color warn_color = (EditorNode::get_singleton()) ? EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")) : Color(1, 1, 0);
-
 	// Root layout
 
 	VBoxContainer *root_vb = memnew(VBoxContainer);
@@ -1302,7 +1300,6 @@ DynamicFontImportSettings::DynamicFontImportSettings() {
 	label_warn->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	label_warn->set_text("");
 	root_vb->add_child(label_warn);
-	label_warn->add_theme_color_override("font_color", warn_color);
 	label_warn->hide();
 
 	// Page 1 layout: Rendering Options
@@ -1379,7 +1376,6 @@ DynamicFontImportSettings::DynamicFontImportSettings() {
 	add_var = memnew(Button);
 	page2_hb_vars->add_child(add_var);
 	add_var->set_tooltip_text(TTR("Add configuration"));
-	add_var->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
 	add_var->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_variation_add));
 
 	vars_list = memnew(Tree);

+ 6 - 3
editor/import/scene_import_settings.cpp

@@ -1001,6 +1001,12 @@ void SceneImportSettings::_notification(int p_what) {
 			connect("confirmed", callable_mp(this, &SceneImportSettings::_re_import));
 		} break;
 
+		case NOTIFICATION_THEME_CHANGED: {
+			action_menu->add_theme_style_override("normal", get_theme_stylebox("normal", "Button"));
+			action_menu->add_theme_style_override("hover", get_theme_stylebox("hover", "Button"));
+			action_menu->add_theme_style_override("pressed", get_theme_stylebox("pressed", "Button"));
+		} break;
+
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 			inspector->set_property_name_style(EditorPropertyNameProcessor::get_settings_style());
 		} break;
@@ -1288,9 +1294,6 @@ SceneImportSettings::SceneImportSettings() {
 	action_menu->set_text(TTR("Actions..."));
 	// Style the MenuButton like a regular Button to make it more noticeable.
 	action_menu->set_flat(false);
-	action_menu->add_theme_style_override("normal", get_theme_stylebox("normal", "Button"));
-	action_menu->add_theme_style_override("hover", get_theme_stylebox("hover", "Button"));
-	action_menu->add_theme_style_override("pressed", get_theme_stylebox("pressed", "Button"));
 	action_menu->set_focus_mode(Control::FOCUS_ALL);
 	menu_hb->add_child(action_menu);
 

+ 4 - 7
editor/inspector_dock.cpp

@@ -420,12 +420,9 @@ Container *InspectorDock::get_addon_area() {
 
 void InspectorDock::_notification(int p_what) {
 	switch (p_what) {
-		case NOTIFICATION_ENTER_TREE:
+		case NOTIFICATION_THEME_CHANGED:
 		case NOTIFICATION_TRANSLATION_CHANGED:
-		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
-		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
-			set_theme(EditorNode::get_singleton()->get_gui_base()->get_theme());
-
+		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			resource_new_button->set_icon(get_theme_icon(SNAME("New"), SNAME("EditorIcons")));
 			resource_load_button->set_icon(get_theme_icon(SNAME("Load"), SNAME("EditorIcons")));
 			resource_save_button->set_icon(get_theme_icon(SNAME("Save"), SNAME("EditorIcons")));
@@ -659,8 +656,8 @@ InspectorDock::InspectorDock(EditorData &p_editor_data) {
 	resource_extra_button->set_tooltip_text(TTR("Extra resource options."));
 	general_options_hb->add_child(resource_extra_button);
 	resource_extra_button->connect("about_to_popup", callable_mp(this, &InspectorDock::_prepare_resource_extra_popup));
-	resource_extra_button->get_popup()->add_icon_shortcut(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), ED_SHORTCUT("property_editor/paste_resource", TTR("Edit Resource from Clipboard")), RESOURCE_EDIT_CLIPBOARD);
-	resource_extra_button->get_popup()->add_icon_shortcut(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_SHORTCUT("property_editor/copy_resource", TTR("Copy Resource")), RESOURCE_COPY);
+	resource_extra_button->get_popup()->add_shortcut(ED_SHORTCUT("property_editor/paste_resource", TTR("Edit Resource from Clipboard")), RESOURCE_EDIT_CLIPBOARD);
+	resource_extra_button->get_popup()->add_shortcut(ED_SHORTCUT("property_editor/copy_resource", TTR("Copy Resource")), RESOURCE_COPY);
 	resource_extra_button->get_popup()->set_item_disabled(1, true);
 	resource_extra_button->get_popup()->add_separator();
 	resource_extra_button->get_popup()->add_shortcut(ED_SHORTCUT("property_editor/show_in_filesystem", TTR("Show in FileSystem")), RESOURCE_SHOW_IN_FILESYSTEM);

+ 11 - 7
editor/plugins/tiles/tile_atlas_view.cpp

@@ -394,13 +394,11 @@ void TileAtlasView::_draw_alternatives() {
 }
 
 void TileAtlasView::_draw_background_left() {
-	Ref<Texture2D> texture = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"));
-	background_left->draw_texture_rect(texture, Rect2(Vector2(), background_left->get_size()), true);
+	background_left->draw_texture_rect(theme_cache.checkerboard, Rect2(Vector2(), background_left->get_size()), true);
 }
 
 void TileAtlasView::_draw_background_right() {
-	Ref<Texture2D> texture = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"));
-	background_right->draw_texture_rect(texture, Rect2(Vector2(), background_right->get_size()), true);
+	background_right->draw_texture_rect(theme_cache.checkerboard, Rect2(Vector2(), background_right->get_size()), true);
 }
 
 void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
@@ -539,6 +537,13 @@ void TileAtlasView::queue_redraw() {
 	background_right->queue_redraw();
 }
 
+void TileAtlasView::_update_theme_item_cache() {
+	Control::_update_theme_item_cache();
+
+	theme_cache.center_view_icon = get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"));
+	theme_cache.checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"));
+}
+
 void TileAtlasView::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE:
@@ -546,8 +551,8 @@ void TileAtlasView::_notification(int p_what) {
 			panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
 		} break;
 
-		case NOTIFICATION_READY: {
-			button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons")));
+		case NOTIFICATION_THEME_CHANGED: {
+			button_center_view->set_icon(theme_cache.center_view_icon);
 		} break;
 	}
 }
@@ -574,7 +579,6 @@ TileAtlasView::TileAtlasView() {
 	zoom_widget->set_shortcut_context(this);
 
 	button_center_view = memnew(Button);
-	button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons")));
 	button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5);
 	button_center_view->connect("pressed", callable_mp(this, &TileAtlasView::_center_view));
 	button_center_view->set_flat(true);

+ 7 - 0
editor/plugins/tiles/tile_atlas_view.h

@@ -110,7 +110,14 @@ private:
 
 	Size2i _compute_alternative_tiles_control_size();
 
+	struct ThemeCache {
+		Ref<Texture2D> center_view_icon;
+		Ref<Texture2D> checkerboard;
+	} theme_cache;
+
 protected:
+	virtual void _update_theme_item_cache() override;
+
 	void _notification(int p_what);
 	static void _bind_methods();
 

+ 33 - 16
modules/multiplayer/editor/editor_network_profiler.cpp

@@ -41,25 +41,42 @@ void EditorNetworkProfiler::_bind_methods() {
 
 void EditorNetworkProfiler::_notification(int p_what) {
 	switch (p_what) {
-		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_THEME_CHANGED: {
-			node_icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons"));
 			if (activate->is_pressed()) {
-				activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
+				activate->set_icon(theme_cache.stop_icon);
 			} else {
-				activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+				activate->set_icon(theme_cache.play_icon);
 			}
-			clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")));
-			incoming_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons")));
-			outgoing_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")));
+			clear_button->set_icon(theme_cache.clear_icon);
+
+			incoming_bandwidth_text->set_right_icon(theme_cache.incoming_bandwidth_icon);
+			outgoing_bandwidth_text->set_right_icon(theme_cache.outgoing_bandwidth_icon);
 
 			// This needs to be done here to set the faded color when the profiler is first opened
-			incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5));
-			outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5));
+			incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", theme_cache.incoming_bandwidth_color * Color(1, 1, 1, 0.5));
+			outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", theme_cache.outgoing_bandwidth_color * Color(1, 1, 1, 0.5));
 		} break;
 	}
 }
 
+void EditorNetworkProfiler::_update_theme_item_cache() {
+	VBoxContainer::_update_theme_item_cache();
+
+	theme_cache.node_icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons"));
+	theme_cache.stop_icon = get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"));
+	theme_cache.play_icon = get_theme_icon(SNAME("Play"), SNAME("EditorIcons"));
+	theme_cache.clear_icon = get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"));
+
+	theme_cache.multiplayer_synchronizer_icon = get_theme_icon("MultiplayerSynchronizer", SNAME("EditorIcons"));
+	theme_cache.instance_options_icon = get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons"));
+
+	theme_cache.incoming_bandwidth_icon = get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons"));
+	theme_cache.outgoing_bandwidth_icon = get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons"));
+
+	theme_cache.incoming_bandwidth_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
+	theme_cache.outgoing_bandwidth_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
+}
+
 void EditorNetworkProfiler::_refresh() {
 	if (!dirty) {
 		return;
@@ -111,11 +128,11 @@ void EditorNetworkProfiler::refresh_replication_data() {
 		const NodeInfo &cfg_info = node_data[E.value.config];
 
 		node->set_text(0, root_info.path.get_file());
-		node->set_icon(0, has_theme_icon(root_info.type, SNAME("EditorIcons")) ? get_theme_icon(root_info.type, SNAME("EditorIcons")) : node_icon);
+		node->set_icon(0, has_theme_icon(root_info.type, SNAME("EditorIcons")) ? get_theme_icon(root_info.type, SNAME("EditorIcons")) : theme_cache.node_icon);
 		node->set_tooltip_text(0, root_info.path);
 
 		node->set_text(1, sync_info.path.get_file());
-		node->set_icon(1, get_theme_icon("MultiplayerSynchronizer", SNAME("EditorIcons")));
+		node->set_icon(1, theme_cache.multiplayer_synchronizer_icon);
 		node->set_tooltip_text(1, sync_info.path);
 
 		int cfg_idx = cfg_info.path.find("::");
@@ -123,7 +140,7 @@ void EditorNetworkProfiler::refresh_replication_data() {
 			String res_idstr = cfg_info.path.substr(cfg_idx + 2).replace("SceneReplicationConfig_", "");
 			String scene_path = cfg_info.path.substr(0, cfg_idx);
 			node->set_text(2, vformat("%s (%s)", res_idstr, scene_path.get_file()));
-			node->add_button(2, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")));
+			node->add_button(2, theme_cache.instance_options_icon);
 			node->set_tooltip_text(2, cfg_info.path);
 			node->set_metadata(2, scene_path);
 		} else {
@@ -154,11 +171,11 @@ void EditorNetworkProfiler::add_node_data(const NodeInfo &p_info) {
 void EditorNetworkProfiler::_activate_pressed() {
 	if (activate->is_pressed()) {
 		refresh_timer->start();
-		activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
+		activate->set_icon(theme_cache.stop_icon);
 		activate->set_text(TTR("Stop"));
 	} else {
 		refresh_timer->stop();
-		activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+		activate->set_icon(theme_cache.play_icon);
 		activate->set_text(TTR("Start"));
 	}
 	emit_signal(SNAME("enable_profiling"), activate->is_pressed());
@@ -224,10 +241,10 @@ void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) {
 	// Make labels more prominent when the bandwidth is greater than 0 to attract user attention
 	incoming_bandwidth_text->add_theme_color_override(
 			"font_uneditable_color",
-			get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5));
+			theme_cache.incoming_bandwidth_color * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5));
 	outgoing_bandwidth_text->add_theme_color_override(
 			"font_uneditable_color",
-			get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5));
+			theme_cache.outgoing_bandwidth_color * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5));
 }
 
 bool EditorNetworkProfiler::is_profiling() {

+ 18 - 1
modules/multiplayer/editor/editor_network_profiler.h

@@ -73,7 +73,22 @@ private:
 	HashMap<ObjectID, SyncInfo> sync_data;
 	HashMap<ObjectID, NodeInfo> node_data;
 	HashSet<ObjectID> missing_node_data;
-	Ref<Texture2D> node_icon;
+
+	struct ThemeCache {
+		Ref<Texture2D> node_icon;
+		Ref<Texture2D> stop_icon;
+		Ref<Texture2D> play_icon;
+		Ref<Texture2D> clear_icon;
+
+		Ref<Texture2D> multiplayer_synchronizer_icon;
+		Ref<Texture2D> instance_options_icon;
+
+		Ref<Texture2D> incoming_bandwidth_icon;
+		Ref<Texture2D> outgoing_bandwidth_icon;
+
+		Color incoming_bandwidth_color;
+		Color outgoing_bandwidth_color;
+	} theme_cache;
 
 	void _activate_pressed();
 	void _clear_pressed();
@@ -81,6 +96,8 @@ private:
 	void _replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button);
 
 protected:
+	virtual void _update_theme_item_cache() override;
+
 	void _notification(int p_what);
 	static void _bind_methods();
 

+ 118 - 108
scene/gui/code_edit.cpp

@@ -36,44 +36,10 @@
 
 void CodeEdit::_notification(int p_what) {
 	switch (p_what) {
-		case NOTIFICATION_THEME_CHANGED:
-		case NOTIFICATION_ENTER_TREE: {
-			style_normal = get_theme_stylebox(SNAME("normal"));
-
-			font = get_theme_font(SNAME("font"));
-			font_size = get_theme_font_size(SNAME("font_size"));
-
-			line_spacing = get_theme_constant(SNAME("line_spacing"));
-
+		case NOTIFICATION_THEME_CHANGED: {
 			set_gutter_width(main_gutter, get_line_height());
-			set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', font_size).width);
+			set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width);
 			set_gutter_width(fold_gutter, get_line_height() / 1.2);
-
-			breakpoint_color = get_theme_color(SNAME("breakpoint_color"));
-			breakpoint_icon = get_theme_icon(SNAME("breakpoint"));
-
-			bookmark_color = get_theme_color(SNAME("bookmark_color"));
-			bookmark_icon = get_theme_icon(SNAME("bookmark"));
-
-			executing_line_color = get_theme_color(SNAME("executing_line_color"));
-			executing_line_icon = get_theme_icon(SNAME("executing_line"));
-
-			line_number_color = get_theme_color(SNAME("line_number_color"));
-
-			folding_color = get_theme_color(SNAME("code_folding_color"));
-			can_fold_icon = get_theme_icon(SNAME("can_fold"));
-			folded_icon = get_theme_icon(SNAME("folded"));
-
-			code_completion_max_width = get_theme_constant(SNAME("completion_max_width"));
-			code_completion_max_lines = get_theme_constant(SNAME("completion_lines"));
-			code_completion_scroll_width = get_theme_constant(SNAME("completion_scroll_width"));
-			code_completion_scroll_color = get_theme_color(SNAME("completion_scroll_color"));
-			code_completion_scroll_hovered_color = get_theme_color(SNAME("completion_scroll_hovered_color"));
-			code_completion_background_color = get_theme_color(SNAME("completion_background_color"));
-			code_completion_selected_color = get_theme_color(SNAME("completion_selected_color"));
-			code_completion_existing_color = get_theme_color(SNAME("completion_existing_color"));
-
-			line_length_guideline_color = get_theme_color(SNAME("line_length_guideline_color"));
 		} break;
 
 		case NOTIFICATION_DRAW: {
@@ -84,14 +50,14 @@ void CodeEdit::_notification(int p_what) {
 			const int row_height = get_line_height();
 
 			if (line_length_guideline_columns.size() > 0) {
-				const int xmargin_beg = style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width();
-				const int xmargin_end = size.width - style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0);
-				const float char_size = font->get_char_size('0', font_size).width;
+				const int xmargin_beg = theme_cache.style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width();
+				const int xmargin_end = size.width - theme_cache.style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0);
+				const float char_size = theme_cache.font->get_char_size('0', theme_cache.font_size).width;
 
 				for (int i = 0; i < line_length_guideline_columns.size(); i++) {
 					const int xoffset = xmargin_beg + char_size * (int)line_length_guideline_columns[i] - get_h_scroll();
 					if (xoffset > xmargin_beg && xoffset < xmargin_end) {
-						Color guideline_color = (i == 0) ? line_length_guideline_color : line_length_guideline_color * Color(1, 1, 1, 0.5);
+						Color guideline_color = (i == 0) ? theme_cache.line_length_guideline_color : theme_cache.line_length_guideline_color * Color(1, 1, 1, 0.5);
 						if (rtl) {
 							RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - xoffset, 0), Point2(size.width - xoffset, size.height), guideline_color);
 							continue;
@@ -103,45 +69,42 @@ void CodeEdit::_notification(int p_what) {
 
 			bool code_completion_below = false;
 			if (caret_visible && code_completion_active && code_completion_options.size() > 0) {
-				Ref<StyleBox> csb = get_theme_stylebox(SNAME("completion"));
-
 				const int code_completion_options_count = code_completion_options.size();
-				const int lines = MIN(code_completion_options_count, code_completion_max_lines);
-				const int icon_hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
+				const int lines = MIN(code_completion_options_count, theme_cache.code_completion_max_lines);
 				const Size2 icon_area_size(row_height, row_height);
 
-				code_completion_rect.size.width = code_completion_longest_line + icon_hsep + icon_area_size.width + 2;
+				code_completion_rect.size.width = code_completion_longest_line + theme_cache.code_completion_icon_separation + icon_area_size.width + 2;
 				code_completion_rect.size.height = lines * row_height;
 
 				const Point2 caret_pos = get_caret_draw_pos();
-				const int total_height = csb->get_minimum_size().y + code_completion_rect.size.height;
+				const int total_height = theme_cache.code_completion_style->get_minimum_size().y + code_completion_rect.size.height;
 				const bool can_fit_completion_above = (caret_pos.y - row_height > total_height);
 				const bool can_fit_completion_below = (caret_pos.y + row_height + total_height <= get_size().height);
 				if (!can_fit_completion_below && can_fit_completion_above) {
-					code_completion_rect.position.y = (caret_pos.y - total_height - row_height) + line_spacing;
+					code_completion_rect.position.y = (caret_pos.y - total_height - row_height) + theme_cache.line_spacing;
 				} else {
-					code_completion_rect.position.y = caret_pos.y + (line_spacing / 2.0f);
+					code_completion_rect.position.y = caret_pos.y + (theme_cache.line_spacing / 2.0f);
 					code_completion_below = true;
 				}
 
-				const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0;
-				const int code_completion_base_width = font->get_string_size(code_completion_base, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
+				const int scroll_width = code_completion_options_count > theme_cache.code_completion_max_lines ? theme_cache.code_completion_scroll_width : 0;
+				const int code_completion_base_width = theme_cache.font->get_string_size(code_completion_base, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
 				if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) {
 					code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width;
 				} else {
 					code_completion_rect.position.x = caret_pos.x - code_completion_base_width;
 				}
 
-				draw_style_box(csb, Rect2(code_completion_rect.position - csb->get_offset(), code_completion_rect.size + csb->get_minimum_size() + Size2(scroll_width, 0)));
-				if (code_completion_background_color.a > 0.01) {
-					RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(code_completion_rect.position, code_completion_rect.size + Size2(scroll_width, 0)), code_completion_background_color);
+				draw_style_box(theme_cache.code_completion_style, Rect2(code_completion_rect.position - theme_cache.code_completion_style->get_offset(), code_completion_rect.size + theme_cache.code_completion_style->get_minimum_size() + Size2(scroll_width, 0)));
+				if (theme_cache.code_completion_background_color.a > 0.01) {
+					RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(code_completion_rect.position, code_completion_rect.size + Size2(scroll_width, 0)), theme_cache.code_completion_background_color);
 				}
 
 				code_completion_scroll_rect.position = code_completion_rect.position + Vector2(code_completion_rect.size.width, 0);
 				code_completion_scroll_rect.size = Vector2(scroll_width, code_completion_rect.size.height);
 
 				code_completion_line_ofs = CLAMP((code_completion_force_item_center < 0 ? code_completion_current_selected : code_completion_force_item_center) - lines / 2, 0, code_completion_options_count - lines);
-				RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color);
+				RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), theme_cache.code_completion_selected_color);
 
 				for (int i = 0; i < lines; i++) {
 					int l = code_completion_line_ofs + i;
@@ -149,7 +112,7 @@ void CodeEdit::_notification(int p_what) {
 
 					Ref<TextLine> tl;
 					tl.instantiate();
-					tl->add_string(code_completion_options[l].display, font, font_size);
+					tl->add_string(code_completion_options[l].display, theme_cache.font, theme_cache.font_size);
 
 					int yofs = (row_height - tl->get_size().y) / 2;
 					Point2 title_pos(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height + yofs);
@@ -161,9 +124,9 @@ void CodeEdit::_notification(int p_what) {
 						Size2 icon_size = icon_area.size * 0.7;
 						icon->draw_rect(ci, Rect2(icon_area.position + (icon_area.size - icon_size) / 2, icon_size));
 					}
-					title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep;
+					title_pos.x = icon_area.position.x + icon_area.size.width + theme_cache.code_completion_icon_separation;
 
-					tl->set_width(code_completion_rect.size.width - (icon_area_size.x + icon_hsep));
+					tl->set_width(code_completion_rect.size.width - (icon_area_size.x + theme_cache.code_completion_icon_separation));
 					if (rtl) {
 						if (code_completion_options[l].default_value.get_type() == Variant::COLOR) {
 							draw_rect(Rect2(Point2(code_completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value);
@@ -176,14 +139,14 @@ void CodeEdit::_notification(int p_what) {
 						tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
 					}
 
-					Point2 match_pos = Point2(code_completion_rect.position.x + icon_area_size.x + icon_hsep, code_completion_rect.position.y + i * row_height);
+					Point2 match_pos = Point2(code_completion_rect.position.x + icon_area_size.x + theme_cache.code_completion_icon_separation, code_completion_rect.position.y + i * row_height);
 
 					for (int j = 0; j < code_completion_options[l].matches.size(); j++) {
 						Pair<int, int> match = code_completion_options[l].matches[j];
-						int match_offset = font->get_string_size(code_completion_options[l].display.substr(0, match.first), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
-						int match_len = font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
+						int match_offset = theme_cache.font->get_string_size(code_completion_options[l].display.substr(0, match.first), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
+						int match_len = theme_cache.font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
 
-						draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), code_completion_existing_color);
+						draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), theme_cache.code_completion_existing_color);
 					}
 
 					tl->draw(ci, title_pos, code_completion_options[l].font_color);
@@ -191,9 +154,9 @@ void CodeEdit::_notification(int p_what) {
 
 				/* Draw a small scroll rectangle to show a position in the options. */
 				if (scroll_width) {
-					Color scroll_color = is_code_completion_scroll_hovered || is_code_completion_scroll_pressed ? code_completion_scroll_hovered_color : code_completion_scroll_color;
+					Color scroll_color = is_code_completion_scroll_hovered || is_code_completion_scroll_pressed ? theme_cache.code_completion_scroll_hovered_color : theme_cache.code_completion_scroll_color;
 
-					float r = (float)code_completion_max_lines / code_completion_options_count;
+					float r = (float)theme_cache.code_completion_max_lines / code_completion_options_count;
 					float o = (float)code_completion_line_ofs / code_completion_options_count;
 					draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), scroll_color);
 				}
@@ -201,31 +164,29 @@ void CodeEdit::_notification(int p_what) {
 
 			/* Code hint */
 			if (caret_visible && !code_hint.is_empty() && (!code_completion_active || (code_completion_below != code_hint_draw_below))) {
-				const int font_height = font->get_height(font_size);
-				Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel"), SNAME("TooltipPanel"));
-				Color color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel"));
+				const int font_height = theme_cache.font->get_height(theme_cache.font_size);
 
 				Vector<String> code_hint_lines = code_hint.split("\n");
 				int line_count = code_hint_lines.size();
 
 				int max_width = 0;
 				for (int i = 0; i < line_count; i++) {
-					max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x);
+					max_width = MAX(max_width, theme_cache.font->get_string_size(code_hint_lines[i], HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x);
 				}
-				Size2 minsize = sb->get_minimum_size() + Size2(max_width, line_count * font_height + (line_spacing * line_count - 1));
+				Size2 minsize = theme_cache.code_hint_style->get_minimum_size() + Size2(max_width, line_count * font_height + (theme_cache.line_spacing * line_count - 1));
 
-				int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x;
+				int offset = theme_cache.font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x;
 				if (code_hint_xpos == -0xFFFF) {
 					code_hint_xpos = get_caret_draw_pos().x - offset;
 				}
 				Point2 hint_ofs = Vector2(code_hint_xpos, get_caret_draw_pos().y);
 				if (code_hint_draw_below) {
-					hint_ofs.y += line_spacing / 2.0f;
+					hint_ofs.y += theme_cache.line_spacing / 2.0f;
 				} else {
-					hint_ofs.y -= (minsize.y + row_height) - line_spacing;
+					hint_ofs.y -= (minsize.y + row_height) - theme_cache.line_spacing;
 				}
 
-				draw_style_box(sb, Rect2(hint_ofs, minsize));
+				draw_style_box(theme_cache.code_hint_style, Rect2(hint_ofs, minsize));
 
 				int yofs = 0;
 				for (int i = 0; i < line_count; i++) {
@@ -234,31 +195,80 @@ void CodeEdit::_notification(int p_what) {
 					int begin = 0;
 					int end = 0;
 					if (line.contains(String::chr(0xFFFF))) {
-						begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x;
-						end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x;
+						begin = theme_cache.font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x;
+						end = theme_cache.font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x;
 					}
 
-					Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs);
+					Point2 round_ofs = hint_ofs + theme_cache.code_hint_style->get_offset() + Vector2(0, theme_cache.font->get_ascent(theme_cache.font_size) + font_height * i + yofs);
 					round_ofs = round_ofs.round();
-					draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color);
+					draw_string(theme_cache.font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size, theme_cache.code_hint_color);
 					if (end > 0) {
 						// Draw an underline for the currently edited function parameter.
-						const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + yofs);
-						draw_line(b, b + Vector2(end - begin, 0), color, 2);
+						const Vector2 b = hint_ofs + theme_cache.code_hint_style->get_offset() + Vector2(begin, font_height + font_height * i + yofs);
+						draw_line(b, b + Vector2(end - begin, 0), theme_cache.code_hint_color, 2);
 
 						// Draw a translucent text highlight as well.
 						const Rect2 highlight_rect = Rect2(
 								b - Vector2(0, font_height),
 								Vector2(end - begin, font_height));
-						draw_rect(highlight_rect, color * Color(1, 1, 1, 0.2));
+						draw_rect(highlight_rect, theme_cache.code_hint_color * Color(1, 1, 1, 0.2));
 					}
-					yofs += line_spacing;
+					yofs += theme_cache.line_spacing;
 				}
 			}
 		} break;
 	}
 }
 
+void CodeEdit::_update_theme_item_cache() {
+	TextEdit::_update_theme_item_cache();
+
+	/* Gutters */
+	theme_cache.code_folding_color = get_theme_color(SNAME("code_folding_color"));
+	theme_cache.can_fold_icon = get_theme_icon(SNAME("can_fold"));
+	theme_cache.folded_icon = get_theme_icon(SNAME("folded"));
+	theme_cache.folded_eol_icon = get_theme_icon(SNAME("folded_eol_icon"));
+
+	theme_cache.breakpoint_color = get_theme_color(SNAME("breakpoint_color"));
+	theme_cache.breakpoint_icon = get_theme_icon(SNAME("breakpoint"));
+
+	theme_cache.bookmark_color = get_theme_color(SNAME("bookmark_color"));
+	theme_cache.bookmark_icon = get_theme_icon(SNAME("bookmark"));
+
+	theme_cache.executing_line_color = get_theme_color(SNAME("executing_line_color"));
+	theme_cache.executing_line_icon = get_theme_icon(SNAME("executing_line"));
+
+	theme_cache.line_number_color = get_theme_color(SNAME("line_number_color"));
+
+	/* Code Completion */
+	theme_cache.code_completion_style = get_theme_stylebox(SNAME("completion"));
+	theme_cache.code_completion_icon_separation = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
+
+	theme_cache.code_completion_max_width = get_theme_constant(SNAME("completion_max_width"));
+	theme_cache.code_completion_max_lines = get_theme_constant(SNAME("completion_lines"));
+	theme_cache.code_completion_scroll_width = get_theme_constant(SNAME("completion_scroll_width"));
+	theme_cache.code_completion_scroll_color = get_theme_color(SNAME("completion_scroll_color"));
+	theme_cache.code_completion_scroll_hovered_color = get_theme_color(SNAME("completion_scroll_hovered_color"));
+	theme_cache.code_completion_background_color = get_theme_color(SNAME("completion_background_color"));
+	theme_cache.code_completion_selected_color = get_theme_color(SNAME("completion_selected_color"));
+	theme_cache.code_completion_existing_color = get_theme_color(SNAME("completion_existing_color"));
+
+	/* Code hint */
+	theme_cache.code_hint_style = get_theme_stylebox(SNAME("panel"), SNAME("TooltipPanel"));
+	theme_cache.code_hint_color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel"));
+
+	/* Line length guideline */
+	theme_cache.line_length_guideline_color = get_theme_color(SNAME("line_length_guideline_color"));
+
+	/* Other visuals */
+	theme_cache.style_normal = get_theme_stylebox(SNAME("normal"));
+
+	theme_cache.font = get_theme_font(SNAME("font"));
+	theme_cache.font_size = get_theme_font_size(SNAME("font_size"));
+
+	theme_cache.line_spacing = get_theme_constant(SNAME("line_spacing"));
+}
+
 void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 	Ref<InputEventMouseButton> mb = p_gui_input;
 	if (mb.is_valid()) {
@@ -355,7 +365,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 				if (is_line_folded(line)) {
 					int wrap_index = get_line_wrap_index_at_column(line, col);
 					if (wrap_index == get_line_wrap_count(line)) {
-						int eol_icon_width = folded_eol_icon->get_width();
+						int eol_icon_width = theme_cache.folded_eol_icon->get_width();
 						int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
 						if (mpos.x > left_margin && mpos.x <= left_margin + eol_icon_width + 3) {
 							unfold_line(line);
@@ -499,14 +509,14 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 			return;
 		}
 		if (k->is_action("ui_page_up", true)) {
-			code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines);
+			code_completion_current_selected = MAX(0, code_completion_current_selected - theme_cache.code_completion_max_lines);
 			code_completion_force_item_center = -1;
 			queue_redraw();
 			accept_event();
 			return;
 		}
 		if (k->is_action("ui_page_down", true)) {
-			code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines);
+			code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + theme_cache.code_completion_max_lines);
 			code_completion_force_item_center = -1;
 			queue_redraw();
 			accept_event();
@@ -635,7 +645,7 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
 	if (line != -1 && is_line_folded(line)) {
 		int wrap_index = get_line_wrap_index_at_column(line, col);
 		if (wrap_index == get_line_wrap_count(line)) {
-			int eol_icon_width = folded_eol_icon->get_width();
+			int eol_icon_width = theme_cache.folded_eol_icon->get_width();
 			int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
 			if (p_pos.x > left_margin && p_pos.x <= left_margin + eol_icon_width + 3) {
 				return CURSOR_POINTING_HAND;
@@ -1246,7 +1256,7 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const {
 }
 
 void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
-	if (draw_breakpoints && breakpoint_icon.is_valid()) {
+	if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) {
 		bool breakpointed = is_line_breakpointed(p_line);
 		bool hovering = p_region.has_point(get_local_mouse_pos());
 		bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
@@ -1254,18 +1264,18 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2
 		if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) {
 			int padding = p_region.size.x / 6;
 
-			Color use_color = breakpoint_color;
+			Color use_color = theme_cache.breakpoint_color;
 			if (hovering && !shift_pressed) {
 				use_color = breakpointed ? use_color.lightened(0.3) : use_color.darkened(0.5);
 			}
 			Rect2 icon_region = p_region;
 			icon_region.position += Point2(padding, padding);
 			icon_region.size -= Point2(padding, padding) * 2;
-			breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
+			theme_cache.breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
 		}
 	}
 
-	if (draw_bookmarks && bookmark_icon.is_valid()) {
+	if (draw_bookmarks && theme_cache.bookmark_icon.is_valid()) {
 		bool bookmarked = is_line_bookmarked(p_line);
 		bool hovering = p_region.has_point(get_local_mouse_pos());
 		bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
@@ -1274,25 +1284,25 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2
 			int horizontal_padding = p_region.size.x / 2;
 			int vertical_padding = p_region.size.y / 4;
 
-			Color use_color = bookmark_color;
+			Color use_color = theme_cache.bookmark_color;
 			if (hovering && shift_pressed) {
 				use_color = bookmarked ? use_color.lightened(0.3) : use_color.darkened(0.5);
 			}
 			Rect2 icon_region = p_region;
 			icon_region.position += Point2(horizontal_padding, 0);
 			icon_region.size -= Point2(horizontal_padding * 1.1, vertical_padding);
-			bookmark_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
+			theme_cache.bookmark_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
 		}
 	}
 
-	if (draw_executing_lines && is_line_executing(p_line) && executing_line_icon.is_valid()) {
+	if (draw_executing_lines && is_line_executing(p_line) && theme_cache.executing_line_icon.is_valid()) {
 		int horizontal_padding = p_region.size.x / 10;
 		int vertical_padding = p_region.size.y / 4;
 
 		Rect2 icon_region = p_region;
 		icon_region.position += Point2(horizontal_padding, vertical_padding);
 		icon_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
-		executing_line_icon->draw_rect(get_canvas_item(), icon_region, false, executing_line_color);
+		theme_cache.executing_line_icon->draw_rect(get_canvas_item(), icon_region, false, theme_cache.executing_line_color);
 	}
 }
 
@@ -1416,11 +1426,11 @@ void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2
 	}
 	Ref<TextLine> tl;
 	tl.instantiate();
-	tl->add_string(fc, font, font_size);
+	tl->add_string(fc, theme_cache.font, theme_cache.font_size);
 	int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2;
 	Color number_color = get_line_gutter_item_color(p_line, line_number_gutter);
 	if (number_color == Color(1, 1, 1)) {
-		number_color = line_number_color;
+		number_color = theme_cache.line_number_color;
 	}
 	tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color);
 }
@@ -1448,10 +1458,10 @@ void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_regi
 	p_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
 
 	if (can_fold_line(p_line)) {
-		can_fold_icon->draw_rect(get_canvas_item(), p_region, false, folding_color);
+		theme_cache.can_fold_icon->draw_rect(get_canvas_item(), p_region, false, theme_cache.code_folding_color);
 		return;
 	}
-	folded_icon->draw_rect(get_canvas_item(), p_region, false, folding_color);
+	theme_cache.folded_icon->draw_rect(get_canvas_item(), p_region, false, theme_cache.code_folding_color);
 }
 
 /* Line Folding */
@@ -2866,13 +2876,13 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 				offset = line_height;
 			}
 
-			if (font.is_valid()) {
-				max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset);
+			if (theme_cache.font.is_valid()) {
+				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
 			}
 			code_completion_options.push_back(option);
 		}
 
-		code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size);
+		code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
 		code_completion_current_selected = 0;
 		code_completion_force_item_center = -1;
 		code_completion_active = true;
@@ -2987,8 +2997,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 
 		if (string_to_complete.length() == 0) {
 			code_completion_options.push_back(option);
-			if (font.is_valid()) {
-				max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset);
+			if (theme_cache.font.is_valid()) {
+				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
 			}
 			continue;
 		}
@@ -3095,8 +3105,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 				option.matches.append_array(ssq_matches);
 				completion_options_subseq.push_back(option);
 			}
-			if (font.is_valid()) {
-				max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset);
+			if (theme_cache.font.is_valid()) {
+				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
 			}
 		} else if (!*ssq_lower) { // Matched the whole subsequence in s_lower.
 			option.matches.clear();
@@ -3114,8 +3124,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 				option.matches.append_array(ssq_lower_matches);
 				completion_options_subseq_casei.push_back(option);
 			}
-			if (font.is_valid()) {
-				max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset);
+			if (theme_cache.font.is_valid()) {
+				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
 			}
 		}
 	}
@@ -3138,7 +3148,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 		return;
 	}
 
-	code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size);
+	code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
 	code_completion_current_selected = 0;
 	code_completion_force_item_center = -1;
 	code_completion_active = true;
@@ -3174,8 +3184,8 @@ void CodeEdit::_text_changed() {
 		line_number_digits++;
 	}
 
-	if (font.is_valid()) {
-		set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', font_size).width);
+	if (theme_cache.font.is_valid()) {
+		set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width);
 	}
 
 	lc = get_line_count();

+ 45 - 25
scene/gui/code_edit.h

@@ -93,32 +93,22 @@ private:
 	// breakpoints
 	HashMap<int, bool> breakpointed_lines;
 	bool draw_breakpoints = false;
-	Color breakpoint_color = Color(1, 1, 1);
-	Ref<Texture2D> breakpoint_icon = Ref<Texture2D>();
 
 	// bookmarks
 	bool draw_bookmarks = false;
-	Color bookmark_color = Color(1, 1, 1);
-	Ref<Texture2D> bookmark_icon = Ref<Texture2D>();
 
 	// executing lines
 	bool draw_executing_lines = false;
-	Color executing_line_color = Color(1, 1, 1);
-	Ref<Texture2D> executing_line_icon = Ref<Texture2D>();
 
 	/* Line numbers */
 	int line_number_gutter = -1;
 	int line_number_digits = 1;
 	String line_number_padding = " ";
-	Color line_number_color = Color(1, 1, 1);
 	void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region);
 
 	/* Fold Gutter */
 	int fold_gutter = -1;
 	bool draw_fold_gutter = false;
-	Color folding_color = Color(1, 1, 1);
-	Ref<Texture2D> can_fold_icon = Ref<Texture2D>();
-	Ref<Texture2D> folded_icon = Ref<Texture2D>();
 	void _fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region);
 
 	void _gutter_clicked(int p_line, int p_gutter);
@@ -199,15 +189,6 @@ private:
 	bool code_completion_enabled = false;
 	bool code_completion_forced = false;
 
-	int code_completion_max_width = 0;
-	int code_completion_max_lines = 7;
-	int code_completion_scroll_width = 0;
-	Color code_completion_scroll_color = Color(0, 0, 0, 0);
-	Color code_completion_scroll_hovered_color = Color(0, 0, 0, 0);
-	Color code_completion_background_color = Color(0, 0, 0, 0);
-	Color code_completion_selected_color = Color(0, 0, 0, 0);
-	Color code_completion_existing_color = Color(0, 0, 0, 0);
-
 	bool code_completion_active = false;
 	bool is_code_completion_scroll_hovered = false;
 	bool is_code_completion_scroll_pressed = false;
@@ -230,7 +211,6 @@ private:
 
 	/* Line length guidelines */
 	TypedArray<int> line_length_guideline_columns;
-	Color line_length_guideline_color;
 
 	/* Symbol lookup */
 	bool symbol_lookup_on_click_enabled = false;
@@ -240,12 +220,51 @@ private:
 	Point2i symbol_lookup_pos;
 
 	/* Visual */
-	Ref<StyleBox> style_normal;
+	struct ThemeCache {
+		/* Gutters */
+		Color code_folding_color = Color(1, 1, 1);
+		Ref<Texture2D> can_fold_icon;
+		Ref<Texture2D> folded_icon;
+		Ref<Texture2D> folded_eol_icon;
+
+		Color breakpoint_color = Color(1, 1, 1);
+		Ref<Texture2D> breakpoint_icon = Ref<Texture2D>();
+
+		Color bookmark_color = Color(1, 1, 1);
+		Ref<Texture2D> bookmark_icon = Ref<Texture2D>();
+
+		Color executing_line_color = Color(1, 1, 1);
+		Ref<Texture2D> executing_line_icon = Ref<Texture2D>();
+
+		Color line_number_color = Color(1, 1, 1);
+
+		/* Code Completion */
+		Ref<StyleBox> code_completion_style;
+		int code_completion_icon_separation = 0;
 
-	Ref<Font> font;
-	int font_size = 16;
+		int code_completion_max_width = 0;
+		int code_completion_max_lines = 7;
+		int code_completion_scroll_width = 0;
+		Color code_completion_scroll_color = Color(0, 0, 0, 0);
+		Color code_completion_scroll_hovered_color = Color(0, 0, 0, 0);
+		Color code_completion_background_color = Color(0, 0, 0, 0);
+		Color code_completion_selected_color = Color(0, 0, 0, 0);
+		Color code_completion_existing_color = Color(0, 0, 0, 0);
 
-	int line_spacing = 1;
+		/* Code hint */
+		Ref<StyleBox> code_hint_style;
+		Color code_hint_color;
+
+		/* Line length guideline */
+		Color line_length_guideline_color;
+
+		/* Other visuals */
+		Ref<StyleBox> style_normal;
+
+		Ref<Font> font;
+		int font_size = 16;
+		int line_spacing = 1;
+	} theme_cache;
 
 	/* Callbacks */
 	int lines_edited_changed = 0;
@@ -258,9 +277,10 @@ private:
 
 protected:
 	void _notification(int p_what);
-
 	static void _bind_methods();
 
+	virtual void _update_theme_item_cache() override;
+
 	/* Text manipulation */
 
 	// Overridable actions

+ 113 - 57
scene/gui/color_picker.cpp

@@ -47,36 +47,47 @@ void ColorPicker::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 			_update_color();
-			[[fallthrough]];
-		}
+		} break;
+
 		case NOTIFICATION_THEME_CHANGED: {
-			btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker")));
+			btn_pick->set_icon(theme_cache.screen_picker);
 			_update_drop_down_arrow(btn_preset->is_pressed(), btn_preset);
 			_update_drop_down_arrow(btn_recent_preset->is_pressed(), btn_recent_preset);
-			btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset")));
+			btn_add_preset->set_icon(theme_cache.add_preset);
 
-			btn_pick->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0));
-			btn_shape->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0));
-			btn_mode->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0));
+			btn_pick->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0));
+			btn_shape->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0));
+			btn_mode->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0));
 
-			uv_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height"))));
-			w_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("h_width")), 0));
+			uv_edit->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height));
+			w_edit->set_custom_minimum_size(Size2(theme_cache.h_width, 0));
 
-			wheel_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height"))));
-			wheel_margin->add_theme_constant_override("margin_bottom", 8 * get_theme_default_base_scale());
+			wheel_edit->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height));
+			wheel_margin->add_theme_constant_override("margin_bottom", 8 * theme_cache.base_scale);
 
 			for (int i = 0; i < SLIDER_COUNT; i++) {
-				labels[i]->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0));
-				set_offset((Side)i, get_offset((Side)i) + get_theme_constant(SNAME("margin")));
+				labels[i]->set_custom_minimum_size(Size2(theme_cache.label_width, 0));
+				set_offset((Side)i, get_offset((Side)i) + theme_cache.content_margin);
 			}
-			alpha_label->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0));
-			set_offset((Side)0, get_offset((Side)0) + get_theme_constant(SNAME("margin")));
+			alpha_label->set_custom_minimum_size(Size2(theme_cache.label_width, 0));
+			set_offset((Side)0, get_offset((Side)0) + theme_cache.content_margin);
+
+			for (int i = 0; i < MODE_BUTTON_COUNT; i++) {
+				mode_btns[i]->add_theme_style_override("pressed", theme_cache.mode_button_pressed);
+				mode_btns[i]->add_theme_style_override("normal", theme_cache.mode_button_normal);
+				mode_btns[i]->add_theme_style_override("hover", theme_cache.mode_button_hover);
+			}
+
+			shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_HSV_RECTANGLE), theme_cache.shape_rect);
+			shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_HSV_WHEEL), theme_cache.shape_rect_wheel);
+			shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_VHS_CIRCLE), theme_cache.shape_circle);
+			shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_OKHSL_CIRCLE), theme_cache.shape_circle);
 
-			_reset_theme();
+			_reset_sliders_theme();
 
 			if (Engine::get_singleton()->is_editor_hint()) {
 				// Adjust for the width of the "Script" icon.
-				text_type->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0));
+				text_type->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0));
 			}
 
 			_update_presets();
@@ -87,7 +98,7 @@ void ColorPicker::_notification(int p_what) {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			Popup *p = Object::cast_to<Popup>(get_parent());
 			if (p && is_visible_in_tree()) {
-				p->set_size(Size2(get_combined_minimum_size().width + get_theme_constant(SNAME("margin")) * 2, get_combined_minimum_size().height + get_theme_constant(SNAME("margin")) * 2));
+				p->set_size(Size2(get_combined_minimum_size().width + theme_cache.content_margin * 2, get_combined_minimum_size().height + theme_cache.content_margin * 2));
 			}
 		} break;
 
@@ -99,6 +110,38 @@ void ColorPicker::_notification(int p_what) {
 	}
 }
 
+void ColorPicker::_update_theme_item_cache() {
+	VBoxContainer::_update_theme_item_cache();
+
+	theme_cache.base_scale = get_theme_default_base_scale();
+
+	theme_cache.content_margin = get_theme_constant(SNAME("margin"));
+	theme_cache.label_width = get_theme_constant(SNAME("label_width"));
+
+	theme_cache.sv_width = get_theme_constant(SNAME("sv_width"));
+	theme_cache.sv_height = get_theme_constant(SNAME("sv_height"));
+	theme_cache.h_width = get_theme_constant(SNAME("h_width"));
+
+	theme_cache.screen_picker = get_theme_icon(SNAME("screen_picker"));
+	theme_cache.expanded_arrow = get_theme_icon(SNAME("expanded_arrow"));
+	theme_cache.folded_arrow = get_theme_icon(SNAME("folded_arrow"));
+	theme_cache.add_preset = get_theme_icon(SNAME("add_preset"));
+
+	theme_cache.shape_rect = get_theme_icon(SNAME("shape_rect"));
+	theme_cache.shape_rect_wheel = get_theme_icon(SNAME("shape_rect_wheel"));
+	theme_cache.shape_circle = get_theme_icon(SNAME("shape_circle"));
+
+	theme_cache.bar_arrow = get_theme_icon(SNAME("bar_arrow"));
+	theme_cache.sample_background_icon = get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker"));
+	theme_cache.overbright_indicator = get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker"));
+	theme_cache.picker_cursor = get_theme_icon(SNAME("picker_cursor"));
+	theme_cache.color_hue_icon = get_theme_icon(SNAME("color_hue"));
+
+	theme_cache.mode_button_normal = get_theme_stylebox("tab_unselected", "TabContainer");
+	theme_cache.mode_button_pressed = get_theme_stylebox("tab_selected", "TabContainer");
+	theme_cache.mode_button_hover = get_theme_stylebox("tab_selected", "TabContainer");
+}
+
 Ref<Shader> ColorPicker::wheel_shader;
 Ref<Shader> ColorPicker::circle_shader;
 Ref<Shader> ColorPicker::circle_ok_color_shader;
@@ -475,21 +518,21 @@ ColorPicker::PickerShapeType ColorPicker::_get_actual_shape() const {
 	return modes[current_mode]->get_shape_override() != SHAPE_MAX ? modes[current_mode]->get_shape_override() : current_shape;
 }
 
-void ColorPicker::_reset_theme() {
+void ColorPicker::_reset_sliders_theme() {
 	Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat));
-	style_box_flat->set_content_margin(SIDE_TOP, 16 * get_theme_default_base_scale());
+	style_box_flat->set_content_margin(SIDE_TOP, 16 * theme_cache.base_scale);
 	style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp());
 	for (int i = 0; i < SLIDER_COUNT; i++) {
-		sliders[i]->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker")));
-		sliders[i]->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker")));
-		sliders[i]->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale());
+		sliders[i]->add_theme_icon_override("grabber", theme_cache.bar_arrow);
+		sliders[i]->add_theme_icon_override("grabber_highlight", theme_cache.bar_arrow);
+		sliders[i]->add_theme_constant_override("grabber_offset", 8 * theme_cache.base_scale);
 		if (!colorize_sliders) {
 			sliders[i]->add_theme_style_override("slider", style_box_flat);
 		}
 	}
-	alpha_slider->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker")));
-	alpha_slider->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker")));
-	alpha_slider->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale());
+	alpha_slider->add_theme_icon_override("grabber", theme_cache.bar_arrow);
+	alpha_slider->add_theme_icon_override("grabber_highlight", theme_cache.bar_arrow);
+	alpha_slider->add_theme_constant_override("grabber_offset", 8 * theme_cache.base_scale);
 	if (!colorize_sliders) {
 		alpha_slider->add_theme_style_override("slider", style_box_flat);
 	}
@@ -676,9 +719,9 @@ void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_
 
 void ColorPicker::_update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset) {
 	if (p_is_btn_pressed) {
-		p_btn_preset->set_icon(get_theme_icon(SNAME("expanded_arrow"), SNAME("ColorPicker")));
+		p_btn_preset->set_icon(theme_cache.expanded_arrow);
 	} else {
-		p_btn_preset->set_icon(get_theme_icon(SNAME("folded_arrow"), SNAME("ColorPicker")));
+		p_btn_preset->set_icon(theme_cache.folded_arrow);
 	}
 }
 
@@ -851,7 +894,7 @@ void ColorPicker::set_color_mode(ColorModeType p_mode) {
 	}
 
 	if (slider_theme_modified) {
-		_reset_theme();
+		_reset_sliders_theme();
 	}
 
 	mode_popup->set_item_checked(current_mode, false);
@@ -896,7 +939,7 @@ void ColorPicker::set_colorize_sliders(bool p_colorize_sliders) {
 		alpha_slider->add_theme_style_override("slider", style_box_empty);
 	} else {
 		Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat));
-		style_box_flat->set_content_margin(SIDE_TOP, 16 * get_theme_default_base_scale());
+		style_box_flat->set_content_margin(SIDE_TOP, 16 * theme_cache.base_scale);
 		style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp());
 
 		if (!slider_theme_modified) {
@@ -966,28 +1009,28 @@ void ColorPicker::_sample_draw() {
 		const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95));
 
 		if (old_color.a < 1.0) {
-			sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_old, true);
+			sample->draw_texture_rect(theme_cache.sample_background_icon, rect_old, true);
 		}
 
 		sample->draw_rect(rect_old, old_color);
 
 		if (old_color.r > 1 || old_color.g > 1 || old_color.b > 1) {
 			// Draw an indicator to denote that the old color is "overbright" and can't be displayed accurately in the preview.
-			sample->draw_texture(get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), Point2());
+			sample->draw_texture(theme_cache.overbright_indicator, Point2());
 		}
 	} else {
 		rect_new = Rect2(Point2(), Size2(sample->get_size().width, sample->get_size().height * 0.95));
 	}
 
 	if (color.a < 1.0) {
-		sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_new, true);
+		sample->draw_texture_rect(theme_cache.sample_background_icon, rect_new, true);
 	}
 
 	sample->draw_rect(rect_new, color);
 
 	if (color.r > 1 || color.g > 1 || color.b > 1) {
 		// Draw an indicator to denote that the new color is "overbright" and can't be displayed accurately in the preview.
-		sample->draw_texture(get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), Point2(uv_edit->get_size().width * 0.5, 0));
+		sample->draw_texture(theme_cache.overbright_indicator, Point2(uv_edit->get_size().width * 0.5, 0));
 	}
 }
 
@@ -1063,21 +1106,21 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) {
 			default: {
 			}
 		}
-		Ref<Texture2D> cursor = get_theme_icon(SNAME("picker_cursor"), SNAME("ColorPicker"));
+
 		int x;
 		int y;
 		if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) {
-			x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (cursor->get_width() / 2);
-			y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2);
+			x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (theme_cache.picker_cursor->get_width() / 2);
+			y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (theme_cache.picker_cursor->get_height() / 2);
 		} else {
 			real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0;
 			real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0;
 
 			Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2);
-			x = CLAMP(real_size.x * s, 0, real_size.x) + corner_x - (cursor->get_width() / 2);
-			y = CLAMP(real_size.y - real_size.y * v, 0, real_size.y) + corner_y - (cursor->get_height() / 2);
+			x = CLAMP(real_size.x * s, 0, real_size.x) + corner_x - (theme_cache.picker_cursor->get_width() / 2);
+			y = CLAMP(real_size.y - real_size.y * v, 0, real_size.y) + corner_y - (theme_cache.picker_cursor->get_height() / 2);
 		}
-		c->draw_texture(cursor, Point2(x, y));
+		c->draw_texture(theme_cache.picker_cursor, Point2(x, y));
 
 		col.set_hsv(h, 1, 1);
 		if (actual_shape == SHAPE_HSV_WHEEL) {
@@ -1093,9 +1136,8 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) {
 
 	} else if (p_which == 1) {
 		if (actual_shape == SHAPE_HSV_RECTANGLE) {
-			Ref<Texture2D> hue = get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker"));
 			c->draw_set_transform(Point2(), -Math_PI / 2, Size2(c->get_size().x, -c->get_size().y));
-			c->draw_texture_rect(hue, Rect2(Point2(), Size2(1, 1)));
+			c->draw_texture_rect(theme_cache.color_hue_icon, Rect2(Point2(), Size2(1, 1)));
 			c->draw_set_transform(Point2(), 0, Size2(1, 1));
 			int y = c->get_size().y - c->get_size().y * (1.0 - h);
 			Color col;
@@ -1663,10 +1705,10 @@ ColorPicker::ColorPicker() {
 	current_shape = SHAPE_HSV_RECTANGLE;
 
 	shape_popup = btn_shape->get_popup();
-	shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect"), SNAME("ColorPicker")), "HSV Rectangle", SHAPE_HSV_RECTANGLE);
-	shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect_wheel"), SNAME("ColorPicker")), "HSV Wheel", SHAPE_HSV_WHEEL);
-	shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "VHS Circle", SHAPE_VHS_CIRCLE);
-	shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "OKHSL Circle", SHAPE_OKHSL_CIRCLE);
+	shape_popup->add_radio_check_item("HSV Rectangle", SHAPE_HSV_RECTANGLE);
+	shape_popup->add_radio_check_item("HSV Wheel", SHAPE_HSV_WHEEL);
+	shape_popup->add_radio_check_item("VHS Circle", SHAPE_VHS_CIRCLE);
+	shape_popup->add_radio_check_item("OKHSL Circle", SHAPE_OKHSL_CIRCLE);
 	shape_popup->set_item_checked(current_shape, true);
 	shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape));
 
@@ -1687,9 +1729,6 @@ ColorPicker::ColorPicker() {
 		mode_hbc->add_child(mode_btns[i]);
 		mode_btns[i]->set_focus_mode(FOCUS_NONE);
 		mode_btns[i]->set_h_size_flags(SIZE_EXPAND_FILL);
-		mode_btns[i]->add_theme_style_override("pressed", get_theme_stylebox("tab_selected", "TabContainer"));
-		mode_btns[i]->add_theme_style_override("normal", get_theme_stylebox("tab_unselected", "TabContainer"));
-		mode_btns[i]->add_theme_style_override("hover", get_theme_stylebox("tab_selected", "TabContainer"));
 		mode_btns[i]->set_toggle_mode(true);
 		mode_btns[i]->set_text(modes[i]->get_name());
 		mode_btns[i]->set_button_group(mode_group);
@@ -1901,14 +1940,13 @@ void ColorPickerButton::pressed() {
 void ColorPickerButton::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_DRAW: {
-			const Ref<StyleBox> normal = get_theme_stylebox(SNAME("normal"));
-			const Rect2 r = Rect2(normal->get_offset(), get_size() - normal->get_minimum_size());
-			draw_texture_rect(Control::get_theme_icon(SNAME("bg"), SNAME("ColorPickerButton")), r, true);
+			const Rect2 r = Rect2(theme_cache.normal_style->get_offset(), get_size() - theme_cache.normal_style->get_minimum_size());
+			draw_texture_rect(theme_cache.background_icon, r, true);
 			draw_rect(r, color);
 
 			if (color.r > 1 || color.g > 1 || color.b > 1) {
 				// Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview
-				draw_texture(Control::get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), normal->get_offset());
+				draw_texture(theme_cache.overbright_indicator, theme_cache.normal_style->get_offset());
 			}
 		} break;
 
@@ -1926,6 +1964,15 @@ void ColorPickerButton::_notification(int p_what) {
 	}
 }
 
+void ColorPickerButton::_update_theme_item_cache() {
+	Button::_update_theme_item_cache();
+
+	theme_cache.normal_style = get_theme_stylebox(SNAME("normal"));
+	theme_cache.background_icon = get_theme_icon(SNAME("bg"));
+
+	theme_cache.overbright_indicator = get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker"));
+}
+
 void ColorPickerButton::set_pick_color(const Color &p_color) {
 	if (color == p_color) {
 		return;
@@ -2012,7 +2059,7 @@ void ColorPresetButton::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_DRAW: {
 			const Rect2 r = Rect2(Point2(0, 0), get_size());
-			Ref<StyleBox> sb_raw = get_theme_stylebox(SNAME("preset_fg"), SNAME("ColorPresetButton"))->duplicate();
+			Ref<StyleBox> sb_raw = theme_cache.foreground_style->duplicate();
 			Ref<StyleBoxFlat> sb_flat = sb_raw;
 			Ref<StyleBoxTexture> sb_texture = sb_raw;
 
@@ -2034,7 +2081,7 @@ void ColorPresetButton::_notification(int p_what) {
 					bg_texture_rect = bg_texture_rect.grow_side(SIDE_TOP, -sb_flat->get_margin(SIDE_TOP));
 					bg_texture_rect = bg_texture_rect.grow_side(SIDE_BOTTOM, -sb_flat->get_margin(SIDE_BOTTOM));
 
-					draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), bg_texture_rect, true);
+					draw_texture_rect(theme_cache.background_icon, bg_texture_rect, true);
 					sb_flat->set_bg_color(preset_color);
 				}
 				sb_flat->set_bg_color(preset_color);
@@ -2043,7 +2090,7 @@ void ColorPresetButton::_notification(int p_what) {
 				if (preset_color.a < 1) {
 					// Draw a background pattern when the color is transparent.
 					bool use_tile_texture = (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE) || (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE_FIT);
-					draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), r, use_tile_texture);
+					draw_texture_rect(theme_cache.background_icon, r, use_tile_texture);
 				}
 				sb_texture->set_modulate(preset_color);
 				sb_texture->draw(get_canvas_item(), r);
@@ -2052,13 +2099,22 @@ void ColorPresetButton::_notification(int p_what) {
 			}
 			if (preset_color.r > 1 || preset_color.g > 1 || preset_color.b > 1) {
 				// Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview
-				draw_texture(Control::get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPresetButton")), Vector2(0, 0));
+				draw_texture(theme_cache.overbright_indicator, Vector2(0, 0));
 			}
 
 		} break;
 	}
 }
 
+void ColorPresetButton::_update_theme_item_cache() {
+	BaseButton::_update_theme_item_cache();
+
+	theme_cache.foreground_style = get_theme_stylebox(SNAME("preset_fg"));
+
+	theme_cache.background_icon = get_theme_icon(SNAME("preset_bg"));
+	theme_cache.overbright_indicator = get_theme_icon(SNAME("overbright_indicator"));
+}
+
 void ColorPresetButton::set_preset_color(const Color &p_color) {
 	preset_color = p_color;
 }

+ 52 - 1
scene/gui/color_picker.h

@@ -58,7 +58,16 @@ class ColorPresetButton : public BaseButton {
 
 	Color preset_color;
 
+	struct ThemeCache {
+		Ref<StyleBox> foreground_style;
+
+		Ref<Texture2D> background_icon;
+		Ref<Texture2D> overbright_indicator;
+	} theme_cache;
+
 protected:
+	virtual void _update_theme_item_cache() override;
+
 	void _notification(int);
 
 public:
@@ -194,12 +203,43 @@ private:
 	float v = 0.0;
 	Color last_color;
 
+	struct ThemeCache {
+		float base_scale = 1.0;
+
+		int content_margin = 0;
+		int label_width = 0;
+
+		int sv_height = 0;
+		int sv_width = 0;
+		int h_width = 0;
+
+		Ref<Texture2D> screen_picker;
+		Ref<Texture2D> expanded_arrow;
+		Ref<Texture2D> folded_arrow;
+		Ref<Texture2D> add_preset;
+
+		Ref<Texture2D> shape_rect;
+		Ref<Texture2D> shape_rect_wheel;
+		Ref<Texture2D> shape_circle;
+
+		Ref<Texture2D> bar_arrow;
+		Ref<Texture2D> sample_background_icon;
+		Ref<Texture2D> overbright_indicator;
+		Ref<Texture2D> picker_cursor;
+		Ref<Texture2D> color_hue_icon;
+
+		/* Mode buttons */
+		Ref<StyleBox> mode_button_normal;
+		Ref<StyleBox> mode_button_pressed;
+		Ref<StyleBox> mode_button_hover;
+	} theme_cache;
+
 	void _copy_color_to_hsv();
 	void _copy_hsv_to_color();
 
 	PickerShapeType _get_actual_shape() const;
 	void create_slider(GridContainer *gc, int idx);
-	void _reset_theme();
+	void _reset_sliders_theme();
 	void _html_submitted(const String &p_html);
 	void _value_changed(double);
 	void _update_controls();
@@ -237,6 +277,8 @@ private:
 	void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control);
 
 protected:
+	virtual void _update_theme_item_cache() override;
+
 	void _notification(int);
 	static void _bind_methods();
 
@@ -326,6 +368,13 @@ class ColorPickerButton : public Button {
 	Color color;
 	bool edit_alpha = true;
 
+	struct ThemeCache {
+		Ref<StyleBox> normal_style;
+		Ref<Texture2D> background_icon;
+
+		Ref<Texture2D> overbright_indicator;
+	} theme_cache;
+
 	void _about_to_popup();
 	void _color_changed(const Color &p_color);
 	void _modal_closed();
@@ -335,6 +384,8 @@ class ColorPickerButton : public Button {
 	void _update_picker();
 
 protected:
+	virtual void _update_theme_item_cache() override;
+
 	void _notification(int);
 	static void _bind_methods();
 

+ 50 - 0
scene/gui/control.cpp

@@ -2424,6 +2424,10 @@ StringName Control::get_theme_type_variation() const {
 /// Theme property lookup.
 
 Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		const Ref<Texture2D> *tex = data.theme_icon_override.getptr(p_name);
 		if (tex) {
@@ -2443,6 +2447,10 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam
 }
 
 Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		const Ref<StyleBox> *style = data.theme_style_override.getptr(p_name);
 		if (style) {
@@ -2462,6 +2470,10 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String
 }
 
 Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		const Ref<Font> *font = data.theme_font_override.getptr(p_name);
 		if (font) {
@@ -2481,6 +2493,10 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_
 }
 
 int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		const int *font_size = data.theme_font_size_override.getptr(p_name);
 		if (font_size && (*font_size) > 0) {
@@ -2500,6 +2516,10 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t
 }
 
 Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		const Color *color = data.theme_color_override.getptr(p_name);
 		if (color) {
@@ -2519,6 +2539,10 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the
 }
 
 int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		const int *constant = data.theme_constant_override.getptr(p_name);
 		if (constant) {
@@ -2538,6 +2562,10 @@ int Control::get_theme_constant(const StringName &p_name, const StringName &p_th
 }
 
 bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		if (has_theme_icon_override(p_name)) {
 			return true;
@@ -2550,6 +2578,10 @@ bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme
 }
 
 bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		if (has_theme_stylebox_override(p_name)) {
 			return true;
@@ -2562,6 +2594,10 @@ bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_t
 }
 
 bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		if (has_theme_font_override(p_name)) {
 			return true;
@@ -2574,6 +2610,10 @@ bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme
 }
 
 bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		if (has_theme_font_size_override(p_name)) {
 			return true;
@@ -2586,6 +2626,10 @@ bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_
 }
 
 bool Control::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		if (has_theme_color_override(p_name)) {
 			return true;
@@ -2598,6 +2642,10 @@ bool Control::has_theme_color(const StringName &p_name, const StringName &p_them
 }
 
 bool Control::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!data.initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
 		if (has_theme_constant_override(p_name)) {
 			return true;
@@ -2894,6 +2942,8 @@ Control *Control::make_custom_tooltip(const String &p_text) const {
 void Control::_notification(int p_notification) {
 	switch (p_notification) {
 		case NOTIFICATION_POSTINITIALIZE: {
+			data.initialized = true;
+
 			_invalidate_theme_cache();
 			_update_theme_item_cache();
 		} break;

+ 2 - 0
scene/gui/control.h

@@ -161,6 +161,8 @@ private:
 
 	// This Data struct is to avoid namespace pollution in derived classes.
 	struct Data {
+		bool initialized = false;
+
 		// Global relations.
 
 		List<Control *>::Element *RI = nullptr;

+ 144 - 134
scene/gui/text_edit.cpp

@@ -417,6 +417,10 @@ void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) {
 
 void TextEdit::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_POSTINITIALIZE: {
+			_update_caches();
+		} break;
+
 		case NOTIFICATION_ENTER_TREE: {
 			_update_caches();
 			if (caret_pos_dirty) {
@@ -512,28 +516,28 @@ void TextEdit::_notification(int p_what) {
 
 			RID ci = get_canvas_item();
 			RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true);
-			int xmargin_beg = style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding;
+			int xmargin_beg = theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding;
 
-			int xmargin_end = size.width - style_normal->get_margin(SIDE_RIGHT);
+			int xmargin_end = size.width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
 			if (draw_minimap) {
 				xmargin_end -= minimap_width;
 			}
 			// Let's do it easy for now.
-			style_normal->draw(ci, Rect2(Point2(), size));
+			theme_cache.style_normal->draw(ci, Rect2(Point2(), size));
 			if (!editable) {
-				style_readonly->draw(ci, Rect2(Point2(), size));
+				theme_cache.style_readonly->draw(ci, Rect2(Point2(), size));
 				draw_caret = false;
 			}
 			if (has_focus()) {
-				style_focus->draw(ci, Rect2(Point2(), size));
+				theme_cache.style_focus->draw(ci, Rect2(Point2(), size));
 			}
 
 			int visible_rows = get_visible_line_count() + 1;
 
-			Color color = !editable ? font_readonly_color : font_color;
+			Color color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color;
 
-			if (background_color.a > 0.01) {
-				RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), background_color);
+			if (theme_cache.background_color.a > 0.01) {
+				RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), theme_cache.background_color);
 			}
 
 			Vector<BraceMatchingData> brace_matching;
@@ -734,7 +738,7 @@ void TextEdit::_notification(int p_what) {
 					viewport_alpha = 0.1;
 				}
 
-				const Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha);
+				const Color viewport_color = (theme_cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha);
 				if (rtl) {
 					RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color);
 				} else {
@@ -763,9 +767,9 @@ void TextEdit::_notification(int p_what) {
 
 					Color line_background_color = text.get_line_background_color(minimap_line);
 					line_background_color.a *= 0.6;
-					Color current_color = font_color;
+					Color current_color = theme_cache.font_color;
 					if (!editable) {
-						current_color = font_readonly_color;
+						current_color = theme_cache.font_readonly_color;
 					}
 
 					Vector<String> wrap_rows = get_line_wrapped_text(minimap_line);
@@ -793,9 +797,9 @@ void TextEdit::_notification(int p_what) {
 
 						if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) {
 							if (rtl) {
-								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), current_line_color);
+								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), theme_cache.current_line_color);
 							} else {
-								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), current_line_color);
+								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), theme_cache.current_line_color);
 							}
 						} else if (line_background_color != Color(0, 0, 0, 0)) {
 							if (rtl) {
@@ -811,9 +815,9 @@ void TextEdit::_notification(int p_what) {
 						for (int j = 0; j < str.length(); j++) {
 							const Variant *color_data = color_map.getptr(last_wrap_column + j);
 							if (color_data != nullptr) {
-								current_color = (color_data->operator Dictionary()).get("color", font_color);
+								current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
 								if (!editable) {
-									current_color.a = font_readonly_color.a;
+									current_color.a = theme_cache.font_readonly_color.a;
 								}
 							}
 							color = current_color;
@@ -875,16 +879,16 @@ void TextEdit::_notification(int p_what) {
 			int top_limit_y = 0;
 			int bottom_limit_y = get_size().height;
 			if (!editable) {
-				top_limit_y += style_readonly->get_margin(SIDE_TOP);
-				bottom_limit_y -= style_readonly->get_margin(SIDE_BOTTOM);
+				top_limit_y += theme_cache.style_readonly->get_margin(SIDE_TOP);
+				bottom_limit_y -= theme_cache.style_readonly->get_margin(SIDE_BOTTOM);
 			} else {
-				top_limit_y += style_normal->get_margin(SIDE_TOP);
-				bottom_limit_y -= style_normal->get_margin(SIDE_BOTTOM);
+				top_limit_y += theme_cache.style_normal->get_margin(SIDE_TOP);
+				bottom_limit_y -= theme_cache.style_normal->get_margin(SIDE_BOTTOM);
 			}
 
 			// Draw main text.
 			line_drawing_cache.clear();
-			int row_height = draw_placeholder ? placeholder_line_height + line_spacing : get_line_height();
+			int row_height = draw_placeholder ? placeholder_line_height + theme_cache.line_spacing : get_line_height();
 			int line = first_vis_line;
 			for (int i = 0; i < draw_amount; i++) {
 				line++;
@@ -909,9 +913,9 @@ void TextEdit::_notification(int p_what) {
 				Dictionary color_map = _get_line_syntax_highlighting(line);
 
 				// Ensure we at least use the font color.
-				Color current_color = !editable ? font_readonly_color : font_color;
+				Color current_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color;
 				if (draw_placeholder) {
-					current_color = font_placeholder_color;
+					current_color = theme_cache.font_placeholder_color;
 				}
 
 				const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line);
@@ -933,14 +937,14 @@ void TextEdit::_notification(int p_what) {
 					int ofs_x = 0;
 					int ofs_y = 0;
 					if (!editable) {
-						ofs_x = style_readonly->get_offset().x / 2;
-						ofs_x -= style_normal->get_offset().x / 2;
-						ofs_y = style_readonly->get_offset().y / 2;
+						ofs_x = theme_cache.style_readonly->get_offset().x / 2;
+						ofs_x -= theme_cache.style_normal->get_offset().x / 2;
+						ofs_y = theme_cache.style_readonly->get_offset().y / 2;
 					} else {
-						ofs_y = style_normal->get_offset().y / 2;
+						ofs_y = theme_cache.style_normal->get_offset().y / 2;
 					}
 
-					ofs_y += i * row_height + line_spacing / 2;
+					ofs_y += i * row_height + theme_cache.line_spacing / 2;
 					ofs_y -= first_visible_line_wrap_ofs * row_height;
 					ofs_y -= _get_v_scroll_offset() * row_height;
 
@@ -969,20 +973,20 @@ void TextEdit::_notification(int p_what) {
 						// Draw line background if empty as we won't loop at all.
 						if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
 							if (rtl) {
-								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color);
+								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
 							} else {
-								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), current_line_color);
+								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
 							}
 						}
 
 						// Give visual indication of empty selected line.
 						for (int c = 0; c < carets.size(); c++) {
 							if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c) && char_margin >= xmargin_beg) {
-								float char_w = font->get_char_size(' ', font_size).width;
+								float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
 								if (rtl) {
-									RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color);
+									RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), theme_cache.selection_color);
 								} else {
-									RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color);
+									RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), theme_cache.selection_color);
 								}
 							}
 						}
@@ -990,9 +994,9 @@ void TextEdit::_notification(int p_what) {
 						// If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later.
 						if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
 							if (rtl) {
-								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color);
+								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
 							} else {
-								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), current_line_color);
+								RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
 							}
 						}
 					}
@@ -1002,7 +1006,7 @@ void TextEdit::_notification(int p_what) {
 
 						cache_entry.y_offset = ofs_y;
 
-						int gutter_offset = style_normal->get_margin(SIDE_LEFT);
+						int gutter_offset = theme_cache.style_normal->get_margin(SIDE_LEFT);
 						for (int g = 0; g < gutters.size(); g++) {
 							const GutterInfo gutter = gutters[g];
 
@@ -1019,11 +1023,11 @@ void TextEdit::_notification(int p_what) {
 
 									Ref<TextLine> tl;
 									tl.instantiate();
-									tl->add_string(txt, font, font_size);
+									tl->add_string(txt, theme_cache.font, theme_cache.font_size);
 
 									int yofs = ofs_y + (row_height - tl->get_size().y) / 2;
-									if (outline_size > 0 && outline_color.a > 0) {
-										tl->draw_outline(ci, Point2(gutter_offset + ofs_x, yofs), outline_size, outline_color);
+									if (theme_cache.outline_size > 0 && theme_cache.outline_color.a > 0) {
+										tl->draw_outline(ci, Point2(gutter_offset + ofs_x, yofs), theme_cache.outline_size, theme_cache.outline_color);
 									}
 									tl->draw(ci, Point2(gutter_offset + ofs_x, yofs), get_line_gutter_item_color(line, g));
 								} break;
@@ -1100,7 +1104,7 @@ void TextEdit::_notification(int p_what) {
 								if (rect.position.x + rect.size.x > xmargin_end) {
 									rect.size.x = xmargin_end - rect.position.x;
 								}
-								draw_rect(rect, selection_color, true);
+								draw_rect(rect, theme_cache.selection_color, true);
 							}
 						}
 					}
@@ -1122,8 +1126,8 @@ void TextEdit::_notification(int p_what) {
 								} else if (rect.position.x + rect.size.x > xmargin_end) {
 									rect.size.x = xmargin_end - rect.position.x;
 								}
-								draw_rect(rect, search_result_color, true);
-								draw_rect(rect, search_result_border_color, false);
+								draw_rect(rect, theme_cache.search_result_color, true);
+								draw_rect(rect, theme_cache.search_result_border_color, false);
 							}
 
 							search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + search_text_len);
@@ -1146,7 +1150,7 @@ void TextEdit::_notification(int p_what) {
 								} else if (rect.position.x + rect.size.x > xmargin_end) {
 									rect.size.x = xmargin_end - rect.position.x;
 								}
-								draw_rect(rect, word_highlighted_color);
+								draw_rect(rect, theme_cache.word_highlighted_color);
 							}
 
 							highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len);
@@ -1160,7 +1164,7 @@ void TextEdit::_notification(int p_what) {
 							while (lookup_symbol_word_col != -1) {
 								Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start);
 								for (int j = 0; j < sel.size(); j++) {
-									Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (line_spacing / 2), sel[j].y - sel[j].x, row_height);
+									Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (theme_cache.line_spacing / 2), sel[j].y - sel[j].x, row_height);
 									if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
 										continue;
 									}
@@ -1170,8 +1174,8 @@ void TextEdit::_notification(int p_what) {
 									} else if (rect.position.x + rect.size.x > xmargin_end) {
 										rect.size.x = xmargin_end - rect.position.x;
 									}
-									rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(font->get_underline_position(font_size));
-									rect.size.y = MAX(1, font->get_underline_thickness(font_size));
+									rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size));
+									rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size));
 									draw_rect(rect, color);
 								}
 
@@ -1191,12 +1195,12 @@ void TextEdit::_notification(int p_what) {
 					int last_visible_char = TS->shaped_text_get_range(rid).x;
 
 					float char_ofs = 0;
-					if (outline_size > 0 && outline_color.a > 0) {
+					if (theme_cache.outline_size > 0 && theme_cache.outline_color.a > 0) {
 						for (int j = 0; j < gl_size; j++) {
 							for (int k = 0; k < glyphs[j].repeat; k++) {
 								if ((char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) {
 									if (glyphs[j].font_rid != RID()) {
-										TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, outline_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, outline_color);
+										TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, theme_cache.outline_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, theme_cache.outline_color);
 									}
 								}
 								char_ofs += glyphs[j].advance;
@@ -1218,9 +1222,9 @@ void TextEdit::_notification(int p_what) {
 						}
 						const Variant *color_data = (color_start >= 0) ? color_map.getptr(color_start) : nullptr;
 						if (color_data != nullptr) {
-							current_color = (color_data->operator Dictionary()).get("color", font_color);
-							if (!editable && current_color.a > font_readonly_color.a) {
-								current_color.a = font_readonly_color.a;
+							current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
+							if (!editable && current_color.a > theme_cache.font_readonly_color.a) {
+								current_color.a = theme_cache.font_readonly_color.a;
 							}
 						}
 						Color gl_color = current_color;
@@ -1231,7 +1235,7 @@ void TextEdit::_notification(int p_what) {
 								int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c);
 
 								if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && use_selected_font_color) {
-									gl_color = font_selected_color;
+									gl_color = theme_cache.font_selected_color;
 								}
 							}
 						}
@@ -1243,30 +1247,30 @@ void TextEdit::_notification(int p_what) {
 									if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) ||
 											(get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) {
 										if (brace_matching[c].open_mismatch) {
-											gl_color = brace_mismatch_color;
+											gl_color = theme_cache.brace_mismatch_color;
 										}
-										Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1));
+										Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1));
 										draw_rect(rect, gl_color);
 									}
 
 									if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) ||
 											(get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) {
 										if (brace_matching[c].close_mismatch) {
-											gl_color = brace_mismatch_color;
+											gl_color = theme_cache.brace_mismatch_color;
 										}
-										Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1));
+										Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1));
 										draw_rect(rect, gl_color);
 									}
 								}
 							}
 
 							if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) {
-								int yofs = (text_height - tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
-								tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), gl_color);
+								int yofs = (text_height - theme_cache.tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
+								theme_cache.tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), gl_color);
 							} else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL)) {
-								int yofs = (text_height - space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
-								int xofs = (glyphs[j].advance * glyphs[j].repeat - space_icon->get_width()) / 2;
-								space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), gl_color);
+								int yofs = (text_height - theme_cache.space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
+								int xofs = (glyphs[j].advance * glyphs[j].repeat - theme_cache.space_icon->get_width()) / 2;
+								theme_cache.space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), gl_color);
 							}
 						}
 
@@ -1302,18 +1306,18 @@ void TextEdit::_notification(int p_what) {
 
 					// is_line_folded
 					if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && _is_line_hidden(line + 1)) {
-						int xofs = char_ofs + char_margin + ofs_x + (folded_eol_icon->get_width() / 2);
+						int xofs = char_ofs + char_margin + ofs_x + (theme_cache.folded_eol_icon->get_width() / 2);
 						if (xofs >= xmargin_beg && xofs < xmargin_end) {
-							int yofs = (text_height - folded_eol_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
-							Color eol_color = code_folding_color;
+							int yofs = (text_height - theme_cache.folded_eol_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
+							Color eol_color = theme_cache.code_folding_color;
 							eol_color.a = 1;
-							folded_eol_icon->draw(ci, Point2(xofs, ofs_y + yofs), eol_color);
+							theme_cache.folded_eol_icon->draw(ci, Point2(xofs, ofs_y + yofs), eol_color);
 						}
 					}
 
 					// Carets.
 					// Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1).
-					const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale());
+					const int caret_width = theme_cache.caret_width * MAX(1, theme_cache.base_scale);
 
 					for (int c = 0; c < carets.size(); c++) {
 						if (!clipped && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index) {
@@ -1327,7 +1331,7 @@ void TextEdit::_notification(int p_what) {
 									ts_caret = TS->shaped_text_get_carets(rid, ime_text.is_empty() ? get_caret_column(c) : get_caret_column(c) + ime_selection.x);
 								} else {
 									// No carets, add one at the start.
-									int h = font->get_height(font_size);
+									int h = theme_cache.font->get_height(theme_cache.font_size);
 									if (rtl) {
 										ts_caret.l_dir = TextServer::DIRECTION_RTL;
 										ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h));
@@ -1348,7 +1352,7 @@ void TextEdit::_notification(int p_what) {
 									if (draw_caret || drag_caret_force_displayed) {
 										if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) {
 											//Block or underline caret, draw trailing carets at full height.
-											int h = font->get_height(font_size);
+											int h = theme_cache.font->get_height(theme_cache.font_size);
 
 											if (ts_caret.t_caret != Rect2()) {
 												if (overtype_mode) {
@@ -1359,17 +1363,17 @@ void TextEdit::_notification(int p_what) {
 													ts_caret.t_caret.size.y = h;
 												}
 												ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
-												draw_rect(ts_caret.t_caret, caret_color, overtype_mode);
+												draw_rect(ts_caret.t_caret, theme_cache.caret_color, overtype_mode);
 
 												if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
 													// Draw split caret (leading part).
 													ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
 													ts_caret.l_caret.size.x = caret_width;
-													draw_rect(ts_caret.l_caret, caret_color);
+													draw_rect(ts_caret.l_caret, theme_cache.caret_color);
 													// Draw extra direction marker on top of split caret.
 													float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
 													Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
-													RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+													RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
 												}
 											} else { // End of the line.
 												if (gl_size > 0) {
@@ -1386,7 +1390,7 @@ void TextEdit::_notification(int p_what) {
 													ts_caret.l_caret.size.y = caret_width;
 												}
 												if (Math::ceil(ts_caret.l_caret.position.x) >= TS->shaped_text_get_size(rid).x) {
-													ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x;
+													ts_caret.l_caret.size.x = theme_cache.font->get_char_size('m', theme_cache.font_size).x;
 												} else {
 													ts_caret.l_caret.size.x = 3 * caret_width;
 												}
@@ -1394,7 +1398,7 @@ void TextEdit::_notification(int p_what) {
 												if (ts_caret.l_dir == TextServer::DIRECTION_RTL) {
 													ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x;
 												}
-												draw_rect(ts_caret.l_caret, caret_color, overtype_mode);
+												draw_rect(ts_caret.l_caret, theme_cache.caret_color, overtype_mode);
 											}
 										} else {
 											// Normal caret.
@@ -1402,28 +1406,28 @@ void TextEdit::_notification(int p_what) {
 												// Draw extra marker on top of mid caret.
 												Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width);
 												trect.position += Vector2(char_margin + ofs_x, ofs_y);
-												RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+												RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
 											} else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
 												// Draw extra direction marker on top of split caret.
 												float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
 												Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
 												trect.position += Vector2(char_margin + ofs_x, ofs_y);
-												RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+												RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
 
 												d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
 												trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width);
 												trect.position += Vector2(char_margin + ofs_x, ofs_y);
-												RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
+												RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
 											}
 											ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
 											ts_caret.l_caret.size.x = caret_width;
 
-											draw_rect(ts_caret.l_caret, caret_color);
+											draw_rect(ts_caret.l_caret, theme_cache.caret_color);
 
 											ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
 											ts_caret.t_caret.size.x = caret_width;
 
-											draw_rect(ts_caret.t_caret, caret_color);
+											draw_rect(ts_caret.t_caret, theme_cache.caret_color);
 										}
 									}
 								}
@@ -1444,7 +1448,7 @@ void TextEdit::_notification(int p_what) {
 											rect.size.x = xmargin_end - rect.position.x;
 										}
 										rect.size.y = caret_width;
-										draw_rect(rect, caret_color);
+										draw_rect(rect, theme_cache.caret_color);
 										carets.write[c].draw_pos.x = rect.position.x;
 									}
 								}
@@ -1463,7 +1467,7 @@ void TextEdit::_notification(int p_what) {
 											rect.size.x = xmargin_end - rect.position.x;
 										}
 										rect.size.y = caret_width * 3;
-										draw_rect(rect, caret_color);
+										draw_rect(rect, theme_cache.caret_color);
 										carets.write[c].draw_pos.x = rect.position.x;
 									}
 								}
@@ -1711,7 +1715,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 				int row = pos.y;
 				int col = pos.x;
 
-				int left_margin = style_normal->get_margin(SIDE_LEFT);
+				int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
 				for (int i = 0; i < gutters.size(); i++) {
 					if (!gutters[i].draw || gutters[i].width <= 0) {
 						continue;
@@ -1956,7 +1960,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 		// Check if user is hovering a different gutter, and update if yes.
 		Vector2i current_hovered_gutter = Vector2i(-1, -1);
 
-		int left_margin = style_normal->get_margin(SIDE_LEFT);
+		int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
 		if (mpos.x <= left_margin + gutters_width + gutter_padding) {
 			int hovered_row = get_line_column_at_pos(mpos).y;
 			for (int i = 0; i < gutters.size(); i++) {
@@ -2919,7 +2923,7 @@ void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap
 }
 
 void TextEdit::_update_placeholder() {
-	if (font.is_null() || font_size <= 0) {
+	if (theme_cache.font.is_null() || theme_cache.font_size <= 0) {
 		return; // Not in tree?
 	}
 
@@ -2928,7 +2932,7 @@ void TextEdit::_update_placeholder() {
 	placeholder_data_buf->set_width(text.get_width());
 	placeholder_data_buf->set_direction((TextServer::Direction)text_direction);
 	placeholder_data_buf->set_preserve_control(draw_control_chars);
-	placeholder_data_buf->add_string(placeholder_text, font, font_size, language);
+	placeholder_data_buf->add_string(placeholder_text, theme_cache.font, theme_cache.font_size, language);
 
 	placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text);
 	if (placeholder_bidi_override.is_empty()) {
@@ -2937,13 +2941,13 @@ void TextEdit::_update_placeholder() {
 
 	if (get_tab_size() > 0) {
 		Vector<float> tabs;
-		tabs.push_back(font->get_char_size(' ', font_size).width * get_tab_size());
+		tabs.push_back(theme_cache.font->get_char_size(' ', theme_cache.font_size).width * get_tab_size());
 		placeholder_data_buf->tab_align(tabs);
 	}
 
 	// Update height.
 	const int wrap_amount = placeholder_data_buf->get_line_count() - 1;
-	placeholder_line_height = font->get_height(font_size);
+	placeholder_line_height = theme_cache.font->get_height(theme_cache.font_size);
 	for (int i = 0; i <= wrap_amount; i++) {
 		placeholder_line_height = MAX(placeholder_line_height, placeholder_data_buf->get_line_size(i).y);
 	}
@@ -2959,48 +2963,55 @@ void TextEdit::_update_placeholder() {
 	}
 }
 
-void TextEdit::_update_caches() {
-	/* Internal API for CodeEdit. */
-	brace_mismatch_color = get_theme_color(SNAME("brace_mismatch_color"), SNAME("CodeEdit"));
-	code_folding_color = get_theme_color(SNAME("code_folding_color"), SNAME("CodeEdit"));
-	folded_eol_icon = get_theme_icon(SNAME("folded_eol_icon"), SNAME("CodeEdit"));
+void TextEdit::_update_theme_item_cache() {
+	Control::_update_theme_item_cache();
+
+	theme_cache.base_scale = get_theme_default_base_scale();
+
+	/* Internal API for CodeEdit */
+	theme_cache.brace_mismatch_color = get_theme_color(SNAME("brace_mismatch_color"), SNAME("CodeEdit"));
+	theme_cache.code_folding_color = get_theme_color(SNAME("code_folding_color"), SNAME("CodeEdit"));
+	theme_cache.folded_eol_icon = get_theme_icon(SNAME("folded_eol_icon"), SNAME("CodeEdit"));
 
 	/* Search */
-	search_result_color = get_theme_color(SNAME("search_result_color"));
-	search_result_border_color = get_theme_color(SNAME("search_result_border_color"));
+	theme_cache.search_result_color = get_theme_color(SNAME("search_result_color"));
+	theme_cache.search_result_border_color = get_theme_color(SNAME("search_result_border_color"));
 
 	/* Caret */
-	caret_color = get_theme_color(SNAME("caret_color"));
-	caret_background_color = get_theme_color(SNAME("caret_background_color"));
+	theme_cache.caret_width = get_theme_constant(SNAME("caret_width"));
+	theme_cache.caret_color = get_theme_color(SNAME("caret_color"));
+	theme_cache.caret_background_color = get_theme_color(SNAME("caret_background_color"));
 
 	/* Selection */
-	font_selected_color = get_theme_color(SNAME("font_selected_color"));
-	selection_color = get_theme_color(SNAME("selection_color"));
-	use_selected_font_color = font_selected_color != Color(0, 0, 0, 0);
+	theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color"));
+	theme_cache.selection_color = get_theme_color(SNAME("selection_color"));
+	use_selected_font_color = theme_cache.font_selected_color != Color(0, 0, 0, 0);
 
-	/* Visual. */
-	style_normal = get_theme_stylebox(SNAME("normal"));
-	style_focus = get_theme_stylebox(SNAME("focus"));
-	style_readonly = get_theme_stylebox(SNAME("read_only"));
+	/* Other visuals */
+	theme_cache.style_normal = get_theme_stylebox(SNAME("normal"));
+	theme_cache.style_focus = get_theme_stylebox(SNAME("focus"));
+	theme_cache.style_readonly = get_theme_stylebox(SNAME("read_only"));
 
-	tab_icon = get_theme_icon(SNAME("tab"));
-	space_icon = get_theme_icon(SNAME("space"));
+	theme_cache.tab_icon = get_theme_icon(SNAME("tab"));
+	theme_cache.space_icon = get_theme_icon(SNAME("space"));
 
-	font = get_theme_font(SNAME("font"));
-	font_size = get_theme_font_size(SNAME("font_size"));
-	font_color = get_theme_color(SNAME("font_color"));
-	font_readonly_color = get_theme_color(SNAME("font_readonly_color"));
-	font_placeholder_color = get_theme_color(SNAME("font_placeholder_color"));
+	theme_cache.font = get_theme_font(SNAME("font"));
+	theme_cache.font_size = get_theme_font_size(SNAME("font_size"));
+	theme_cache.font_color = get_theme_color(SNAME("font_color"));
+	theme_cache.font_readonly_color = get_theme_color(SNAME("font_readonly_color"));
+	theme_cache.font_placeholder_color = get_theme_color(SNAME("font_placeholder_color"));
 
-	outline_size = get_theme_constant(SNAME("outline_size"));
-	outline_color = get_theme_color(SNAME("font_outline_color"));
+	theme_cache.outline_size = get_theme_constant(SNAME("outline_size"));
+	theme_cache.outline_color = get_theme_color(SNAME("font_outline_color"));
 
-	line_spacing = get_theme_constant(SNAME("line_spacing"));
+	theme_cache.line_spacing = get_theme_constant(SNAME("line_spacing"));
 
-	background_color = get_theme_color(SNAME("background_color"));
-	current_line_color = get_theme_color(SNAME("current_line_color"));
-	word_highlighted_color = get_theme_color(SNAME("word_highlighted_color"));
+	theme_cache.background_color = get_theme_color(SNAME("background_color"));
+	theme_cache.current_line_color = get_theme_color(SNAME("current_line_color"));
+	theme_cache.word_highlighted_color = get_theme_color(SNAME("word_highlighted_color"));
+}
 
+void TextEdit::_update_caches() {
 	/* Text properties. */
 	TextServer::Direction dir;
 	if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
@@ -3010,8 +3021,8 @@ void TextEdit::_update_caches() {
 	}
 	text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
 	text.set_draw_control_chars(draw_control_chars);
-	text.set_font(font);
-	text.set_font_size(font_size);
+	text.set_font(theme_cache.font);
+	text.set_font_size(theme_cache.font_size);
 	text.invalidate_font();
 	_update_placeholder();
 
@@ -3023,7 +3034,7 @@ void TextEdit::_update_caches() {
 
 /* General overrides. */
 Size2 TextEdit::get_minimum_size() const {
-	Size2 size = style_normal->get_minimum_size();
+	Size2 size = theme_cache.style_normal->get_minimum_size();
 	if (fit_content_height) {
 		size.y += content_height_cache;
 	}
@@ -3119,7 +3130,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
 	Point2i pos = get_line_column_at_pos(p_pos);
 	int row = pos.y;
 
-	int left_margin = style_normal->get_margin(SIDE_LEFT);
+	int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
 	int gutter = left_margin + gutters_width;
 	if (p_pos.x < gutter) {
 		for (int i = 0; i < gutters.size(); i++) {
@@ -3137,7 +3148,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
 		return CURSOR_ARROW;
 	}
 
-	int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT);
+	int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
 	if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) {
 		return CURSOR_ARROW;
 	}
@@ -3465,7 +3476,7 @@ int TextEdit::get_line_width(int p_line, int p_wrap_index) const {
 }
 
 int TextEdit::get_line_height() const {
-	return text.get_line_height() + line_spacing;
+	return text.get_line_height() + theme_cache.line_spacing;
 }
 
 int TextEdit::get_indent_level(int p_line) const {
@@ -4249,7 +4260,7 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const {
 
 Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds) const {
 	float rows = p_pos.y;
-	rows -= style_normal->get_margin(SIDE_TOP);
+	rows -= theme_cache.style_normal->get_margin(SIDE_TOP);
 	rows /= get_line_height();
 	rows += _get_v_scroll_offset();
 	int first_vis_line = get_first_visible_line();
@@ -4284,7 +4295,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
 	}
 
 	int col = 0;
-	int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding);
+	int colx = p_pos.x - (theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding);
 	colx += first_visible_col;
 	col = _get_char_pos_for_line(colx, row, wrap_index);
 	if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) {
@@ -4339,7 +4350,7 @@ Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const {
 
 	Point2i pos, size;
 	pos.y = cache_entry.y_offset + get_line_height() * wrap_index;
-	pos.x = get_total_gutter_width() + style_normal->get_margin(SIDE_LEFT) - get_h_scroll();
+	pos.x = get_total_gutter_width() + theme_cache.style_normal->get_margin(SIDE_LEFT) - get_h_scroll();
 
 	RID text_rid = text.get_line_data(p_line)->get_line_rid(wrap_index);
 	Vector2 col_bounds = TS->shaped_text_get_grapheme_bounds(text_rid, p_column);
@@ -4353,7 +4364,7 @@ Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const {
 
 int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const {
 	float rows = p_pos.y;
-	rows -= style_normal->get_margin(SIDE_TOP);
+	rows -= theme_cache.style_normal->get_margin(SIDE_TOP);
 	rows /= (minimap_char_size.y + minimap_line_spacing);
 	rows += _get_v_scroll_offset();
 
@@ -5519,7 +5530,7 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) {
 		set_line_as_last_visible(cur_line, cur_wrap);
 	}
 
-	int visible_width = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding;
+	int visible_width = get_size().width - theme_cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding;
 	if (draw_minimap) {
 		visible_width -= minimap_width;
 	}
@@ -5573,7 +5584,7 @@ void TextEdit::center_viewport_to_caret(int p_caret) {
 	minimap_clicked = false;
 
 	set_line_as_center_visible(get_caret_line(p_caret), get_caret_wrap_index(p_caret));
-	int visible_width = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding;
+	int visible_width = get_size().width - theme_cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding;
 	if (draw_minimap) {
 		visible_width -= minimap_width;
 	}
@@ -7191,7 +7202,7 @@ void TextEdit::_post_shift_selection(int p_caret) {
 
 /* Line Wrapping */
 void TextEdit::_update_wrap_at_column(bool p_force) {
-	int new_wrap_at = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding;
+	int new_wrap_at = get_size().width - theme_cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding;
 	if (draw_minimap) {
 		new_wrap_at -= minimap_width;
 	}
@@ -7228,8 +7239,8 @@ void TextEdit::_update_scrollbars() {
 	Size2 hmin = h_scroll->get_combined_minimum_size();
 	Size2 vmin = v_scroll->get_combined_minimum_size();
 
-	v_scroll->set_begin(Point2(size.width - vmin.width, style_normal->get_margin(SIDE_TOP)));
-	v_scroll->set_end(Point2(size.width, size.height - style_normal->get_margin(SIDE_TOP) - style_normal->get_margin(SIDE_BOTTOM)));
+	v_scroll->set_begin(Point2(size.width - vmin.width, theme_cache.style_normal->get_margin(SIDE_TOP)));
+	v_scroll->set_end(Point2(size.width, size.height - theme_cache.style_normal->get_margin(SIDE_TOP) - theme_cache.style_normal->get_margin(SIDE_BOTTOM)));
 
 	h_scroll->set_begin(Point2(0, size.height - hmin.height));
 	h_scroll->set_end(Point2(size.width - vmin.width, size.height));
@@ -7242,7 +7253,7 @@ void TextEdit::_update_scrollbars() {
 		total_rows += visible_rows - 1;
 	}
 
-	int visible_width = size.width - style_normal->get_minimum_size().width;
+	int visible_width = size.width - theme_cache.style_normal->get_minimum_size().width;
 	int total_width = (draw_placeholder ? placeholder_max_width : text.get_max_width()) + vmin.x + gutters_width + gutter_padding;
 
 	if (draw_minimap) {
@@ -7298,7 +7309,7 @@ void TextEdit::_update_scrollbars() {
 
 int TextEdit::_get_control_height() const {
 	int control_height = get_size().height;
-	control_height -= style_normal->get_minimum_size().height;
+	control_height -= theme_cache.style_normal->get_minimum_size().height;
 	if (h_scroll->is_visible_in_tree()) {
 		control_height -= h_scroll->get_size().height;
 	}
@@ -7460,7 +7471,7 @@ void TextEdit::_scroll_lines_down() {
 
 void TextEdit::_update_minimap_hover() {
 	const Point2 mp = get_local_mouse_pos();
-	const int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT);
+	const int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
 
 	const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end;
 	if (!hovering_sidebar) {
@@ -7487,7 +7498,7 @@ void TextEdit::_update_minimap_hover() {
 void TextEdit::_update_minimap_click() {
 	Point2 mp = get_local_mouse_pos();
 
-	int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT);
+	int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
 	if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) {
 		minimap_clicked = false;
 		return;
@@ -7777,7 +7788,6 @@ TextEdit::TextEdit(const String &p_placeholder) {
 
 	clear();
 	set_focus_mode(FOCUS_ALL);
-	_update_caches();
 	set_default_cursor_shape(CURSOR_IBEAM);
 	set_process_unhandled_key_input(true);
 

+ 41 - 29
scene/gui/text_edit.h

@@ -356,9 +356,6 @@ private:
 	void _clear_redo();
 
 	/* Search */
-	Color search_result_color = Color(1, 1, 1);
-	Color search_result_border_color = Color(1, 1, 1);
-
 	String search_text = "";
 	uint32_t search_flags = 0;
 
@@ -413,9 +410,6 @@ private:
 	bool caret_pos_dirty = false;
 	bool caret_index_edit_dirty = true;
 
-	Color caret_color = Color(1, 1, 1);
-	Color caret_background_color = Color(0, 0, 0);
-
 	CaretType caret_type = CaretType::CARET_TYPE_LINE;
 
 	bool draw_caret = true;
@@ -446,8 +440,6 @@ private:
 	bool deselect_on_focus_loss_enabled = true;
 	bool drag_and_drop_selection_enabled = true;
 
-	Color font_selected_color = Color(0, 0, 0, 0);
-	Color selection_color = Color(1, 1, 1);
 	bool use_selected_font_color = false;
 
 	bool selection_drag_attempt = false;
@@ -543,27 +535,50 @@ private:
 	Dictionary _get_line_syntax_highlighting(int p_line);
 
 	/* Visual. */
-	Ref<StyleBox> style_normal;
-	Ref<StyleBox> style_focus;
-	Ref<StyleBox> style_readonly;
+	struct ThemeCache {
+		float base_scale = 1.0;
+
+		/* Internal API for CodeEdit */
+		Color brace_mismatch_color;
+		Color code_folding_color = Color(1, 1, 1);
+		Ref<Texture2D> folded_eol_icon;
 
-	Ref<Texture2D> tab_icon;
-	Ref<Texture2D> space_icon;
+		/* Search */
+		Color search_result_color = Color(1, 1, 1);
+		Color search_result_border_color = Color(1, 1, 1);
 
-	Ref<Font> font;
-	int font_size = 16;
-	Color font_color = Color(1, 1, 1);
-	Color font_readonly_color = Color(1, 1, 1);
-	Color font_placeholder_color = Color(1, 1, 1, 0.6);
+		/* Caret */
+		int caret_width = 1;
+		Color caret_color = Color(1, 1, 1);
+		Color caret_background_color = Color(0, 0, 0);
 
-	int outline_size = 0;
-	Color outline_color = Color(1, 1, 1);
+		/* Selection */
+		Color font_selected_color = Color(0, 0, 0, 0);
+		Color selection_color = Color(1, 1, 1);
 
-	int line_spacing = 1;
+		/* Other visuals */
+		Ref<StyleBox> style_normal;
+		Ref<StyleBox> style_focus;
+		Ref<StyleBox> style_readonly;
+
+		Ref<Texture2D> tab_icon;
+		Ref<Texture2D> space_icon;
+
+		Ref<Font> font;
+		int font_size = 16;
+		Color font_color = Color(1, 1, 1);
+		Color font_readonly_color = Color(1, 1, 1);
+		Color font_placeholder_color = Color(1, 1, 1, 0.6);
 
-	Color background_color = Color(1, 1, 1);
-	Color current_line_color = Color(1, 1, 1);
-	Color word_highlighted_color = Color(1, 1, 1);
+		int outline_size = 0;
+		Color outline_color = Color(1, 1, 1);
+
+		int line_spacing = 1;
+
+		Color background_color = Color(1, 1, 1);
+		Color current_line_color = Color(1, 1, 1);
+		Color word_highlighted_color = Color(1, 1, 1);
+	} theme_cache;
 
 	bool window_has_focus = true;
 	bool first_draw = true;
@@ -607,9 +622,10 @@ private:
 
 protected:
 	void _notification(int p_what);
-
 	static void _bind_methods();
 
+	virtual void _update_theme_item_cache() override;
+
 	/* Internal API for CodeEdit, pending public API. */
 	// brace matching
 	struct BraceMatchingData {
@@ -624,12 +640,8 @@ protected:
 	};
 
 	bool highlight_matching_braces_enabled = false;
-	Color brace_mismatch_color;
 
 	// Line hiding.
-	Color code_folding_color = Color(1, 1, 1);
-	Ref<Texture2D> folded_eol_icon;
-
 	bool hiding_enabled = false;
 
 	void _set_hiding_enabled(bool p_enabled);

+ 50 - 0
scene/main/window.cpp

@@ -1073,6 +1073,8 @@ Viewport *Window::get_embedder() const {
 void Window::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_POSTINITIALIZE: {
+			initialized = true;
+
 			_invalidate_theme_cache();
 			_update_theme_item_cache();
 		} break;
@@ -1754,6 +1756,10 @@ StringName Window::get_theme_type_variation() const {
 /// Theme property lookup.
 
 Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		const Ref<Texture2D> *tex = theme_icon_override.getptr(p_name);
 		if (tex) {
@@ -1773,6 +1779,10 @@ Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName
 }
 
 Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		const Ref<StyleBox> *style = theme_style_override.getptr(p_name);
 		if (style) {
@@ -1792,6 +1802,10 @@ Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringN
 }
 
 Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		const Ref<Font> *font = theme_font_override.getptr(p_name);
 		if (font) {
@@ -1811,6 +1825,10 @@ Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_t
 }
 
 int Window::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		const int *font_size = theme_font_size_override.getptr(p_name);
 		if (font_size && (*font_size) > 0) {
@@ -1830,6 +1848,10 @@ int Window::get_theme_font_size(const StringName &p_name, const StringName &p_th
 }
 
 Color Window::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		const Color *color = theme_color_override.getptr(p_name);
 		if (color) {
@@ -1849,6 +1871,10 @@ Color Window::get_theme_color(const StringName &p_name, const StringName &p_them
 }
 
 int Window::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		const int *constant = theme_constant_override.getptr(p_name);
 		if (constant) {
@@ -1868,6 +1894,10 @@ int Window::get_theme_constant(const StringName &p_name, const StringName &p_the
 }
 
 bool Window::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		if (has_theme_icon_override(p_name)) {
 			return true;
@@ -1880,6 +1910,10 @@ bool Window::has_theme_icon(const StringName &p_name, const StringName &p_theme_
 }
 
 bool Window::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		if (has_theme_stylebox_override(p_name)) {
 			return true;
@@ -1892,6 +1926,10 @@ bool Window::has_theme_stylebox(const StringName &p_name, const StringName &p_th
 }
 
 bool Window::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		if (has_theme_font_override(p_name)) {
 			return true;
@@ -1904,6 +1942,10 @@ bool Window::has_theme_font(const StringName &p_name, const StringName &p_theme_
 }
 
 bool Window::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		if (has_theme_font_size_override(p_name)) {
 			return true;
@@ -1916,6 +1958,10 @@ bool Window::has_theme_font_size(const StringName &p_name, const StringName &p_t
 }
 
 bool Window::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		if (has_theme_color_override(p_name)) {
 			return true;
@@ -1928,6 +1974,10 @@ bool Window::has_theme_color(const StringName &p_name, const StringName &p_theme
 }
 
 bool Window::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+	if (!initialized) {
+		WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+	}
+
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
 		if (has_theme_constant_override(p_name)) {
 			return true;

+ 1 - 0
scene/main/window.h

@@ -97,6 +97,7 @@ public:
 
 private:
 	DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID;
+	bool initialized = false;
 
 	String title;
 	mutable int current_screen = 0;