Browse Source

Add dumb and manual theme caching systems to Window

Yuri Sizov 3 years ago
parent
commit
15fd025f90

+ 6 - 0
doc/classes/AcceptDialog.xml

@@ -99,6 +99,12 @@
 		</signal>
 	</signals>
 	<theme_items>
+		<theme_item name="button_margin" data_type="constant" type="int" default="32">
+			Offset that is applied to the content of the window on the bottom, effectively moving the button row.
+		</theme_item>
+		<theme_item name="margin" data_type="constant" type="int" default="8">
+			Offset that is applied to the content of the window on top, left, and right.
+		</theme_item>
 		<theme_item name="panel" data_type="style" type="StyleBox">
 			Panel that fills up the background of the window.
 		</theme_item>

+ 18 - 13
scene/gui/dialogs.cpp

@@ -50,6 +50,14 @@ void AcceptDialog::_parent_focused() {
 	}
 }
 
+void AcceptDialog::_update_theme_item_cache() {
+	Window::_update_theme_item_cache();
+
+	theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
+	theme_cache.margin = get_theme_constant(SNAME("margin"));
+	theme_cache.button_margin = get_theme_constant(SNAME("button_margin"));
+}
+
 void AcceptDialog::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -69,7 +77,10 @@ void AcceptDialog::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_THEME_CHANGED: {
-			bg->add_theme_style_override("panel", bg->get_theme_stylebox(SNAME("panel"), SNAME("AcceptDialog")));
+			bg->add_theme_style_override("panel", theme_cache.panel_style);
+
+			label->set_begin(Point2(theme_cache.margin, theme_cache.margin));
+			label->set_end(Point2(-theme_cache.margin, -theme_cache.button_margin - 10));
 		} break;
 
 		case NOTIFICATION_EXIT_TREE: {
@@ -185,12 +196,12 @@ void AcceptDialog::_update_child_rects() {
 	if (label->get_text().is_empty()) {
 		label_size.height = 0;
 	}
-	int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs"));
+
 	Size2 size = get_size();
 	Size2 hminsize = hbc->get_combined_minimum_size();
 
-	Vector2 cpos(margin, margin + label_size.height);
-	Vector2 csize(size.x - margin * 2, size.y - margin * 3 - hminsize.y - label_size.height);
+	Vector2 cpos(theme_cache.margin, theme_cache.margin + label_size.height);
+	Vector2 csize(size.x - theme_cache.margin * 2, size.y - theme_cache.margin * 3 - hminsize.y - label_size.height);
 
 	for (int i = 0; i < get_child_count(); i++) {
 		Control *c = Object::cast_to<Control>(get_child(i));
@@ -206,7 +217,7 @@ void AcceptDialog::_update_child_rects() {
 		c->set_size(csize);
 	}
 
-	cpos.y += csize.y + margin;
+	cpos.y += csize.y + theme_cache.margin;
 	csize.y = hminsize.y;
 
 	hbc->set_position(cpos);
@@ -217,7 +228,6 @@ void AcceptDialog::_update_child_rects() {
 }
 
 Size2 AcceptDialog::_get_contents_minimum_size() const {
-	int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs"));
 	Size2 minsize = label->get_combined_minimum_size();
 
 	for (int i = 0; i < get_child_count(); i++) {
@@ -238,8 +248,8 @@ Size2 AcceptDialog::_get_contents_minimum_size() const {
 	Size2 hminsize = hbc->get_combined_minimum_size();
 	minsize.x = MAX(hminsize.x, minsize.x);
 	minsize.y += hminsize.y;
-	minsize.x += margin * 2;
-	minsize.y += margin * 3; //one as separation between hbc and child
+	minsize.x += theme_cache.margin * 2;
+	minsize.y += theme_cache.margin * 3; //one as separation between hbc and child
 
 	Size2 wmsize = get_min_size();
 	minsize.x = MAX(wmsize.x, minsize.x);
@@ -350,14 +360,9 @@ AcceptDialog::AcceptDialog() {
 
 	hbc = memnew(HBoxContainer);
 
-	int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs"));
-	int button_margin = hbc->get_theme_constant(SNAME("button_margin"), SNAME("Dialogs"));
-
 	label = memnew(Label);
 	label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END);
 	label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
-	label->set_begin(Point2(margin, margin));
-	label->set_end(Point2(-margin, -button_margin - 10));
 	add_child(label, false, INTERNAL_MODE_FRONT);
 
 	add_child(hbc, false, INTERNAL_MODE_FRONT);

+ 7 - 0
scene/gui/dialogs.h

@@ -52,6 +52,12 @@ class AcceptDialog : public Window {
 	bool hide_on_ok = true;
 	bool close_on_escape = true;
 
+	struct ThemeCache {
+		Ref<StyleBox> panel_style;
+		int margin = 0;
+		int button_margin = 0;
+	} theme_cache;
+
 	void _custom_action(const String &p_action);
 	void _update_child_rects();
 
@@ -62,6 +68,7 @@ class AcceptDialog : public Window {
 
 protected:
 	virtual Size2 _get_contents_minimum_size() const override;
+	virtual void _update_theme_item_cache() override;
 
 	void _notification(int p_what);
 	static void _bind_methods();

+ 58 - 49
scene/gui/file_dialog.cpp

@@ -59,36 +59,26 @@ VBoxContainer *FileDialog::get_vbox() {
 	return vbox;
 }
 
-void FileDialog::_theme_changed() {
-	Color font_color = vbox->get_theme_color(SNAME("font_color"), SNAME("Button"));
-	Color font_hover_color = vbox->get_theme_color(SNAME("font_hover_color"), SNAME("Button"));
-	Color font_focus_color = vbox->get_theme_color(SNAME("font_focus_color"), SNAME("Button"));
-	Color font_pressed_color = vbox->get_theme_color(SNAME("font_pressed_color"), SNAME("Button"));
-
-	dir_up->add_theme_color_override("icon_normal_color", font_color);
-	dir_up->add_theme_color_override("icon_hover_color", font_hover_color);
-	dir_up->add_theme_color_override("icon_focus_color", font_focus_color);
-	dir_up->add_theme_color_override("icon_pressed_color", font_pressed_color);
-
-	dir_prev->add_theme_color_override("icon_color_normal", font_color);
-	dir_prev->add_theme_color_override("icon_color_hover", font_hover_color);
-	dir_prev->add_theme_color_override("icon_focus_color", font_focus_color);
-	dir_prev->add_theme_color_override("icon_color_pressed", font_pressed_color);
-
-	dir_next->add_theme_color_override("icon_color_normal", font_color);
-	dir_next->add_theme_color_override("icon_color_hover", font_hover_color);
-	dir_next->add_theme_color_override("icon_focus_color", font_focus_color);
-	dir_next->add_theme_color_override("icon_color_pressed", font_pressed_color);
-
-	refresh->add_theme_color_override("icon_normal_color", font_color);
-	refresh->add_theme_color_override("icon_hover_color", font_hover_color);
-	refresh->add_theme_color_override("icon_focus_color", font_focus_color);
-	refresh->add_theme_color_override("icon_pressed_color", font_pressed_color);
-
-	show_hidden->add_theme_color_override("icon_normal_color", font_color);
-	show_hidden->add_theme_color_override("icon_hover_color", font_hover_color);
-	show_hidden->add_theme_color_override("icon_focus_color", font_focus_color);
-	show_hidden->add_theme_color_override("icon_pressed_color", font_pressed_color);
+void FileDialog::_update_theme_item_cache() {
+	ConfirmationDialog::_update_theme_item_cache();
+
+	theme_cache.parent_folder = get_theme_icon(SNAME("parent_folder"));
+	theme_cache.forward_folder = get_theme_icon(SNAME("forward_folder"));
+	theme_cache.back_folder = get_theme_icon(SNAME("back_folder"));
+	theme_cache.reload = get_theme_icon(SNAME("reload"));
+	theme_cache.toggle_hidden = get_theme_icon(SNAME("toggle_hidden"));
+	theme_cache.folder = get_theme_icon(SNAME("folder"));
+	theme_cache.file = get_theme_icon(SNAME("file"));
+
+	theme_cache.folder_icon_modulate = get_theme_color(SNAME("folder_icon_modulate"));
+	theme_cache.file_icon_modulate = get_theme_color(SNAME("file_icon_modulate"));
+	theme_cache.files_disabled = get_theme_color(SNAME("files_disabled"));
+
+	// TODO: Define own colors?
+	theme_cache.icon_normal_color = get_theme_color(SNAME("font_color"), SNAME("Button"));
+	theme_cache.icon_hover_color = get_theme_color(SNAME("font_hover_color"), SNAME("Button"));
+	theme_cache.icon_focus_color = get_theme_color(SNAME("font_focus_color"), SNAME("Button"));
+	theme_cache.icon_pressed_color = get_theme_color(SNAME("font_pressed_color"), SNAME("Button"));
 }
 
 void FileDialog::_notification(int p_what) {
@@ -99,18 +89,42 @@ void FileDialog::_notification(int p_what) {
 			}
 		} break;
 
-		case NOTIFICATION_ENTER_TREE: {
-			dir_up->set_icon(vbox->get_theme_icon(SNAME("parent_folder"), SNAME("FileDialog")));
+		case NOTIFICATION_THEME_CHANGED: {
+			dir_up->set_icon(theme_cache.parent_folder);
 			if (vbox->is_layout_rtl()) {
-				dir_prev->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog")));
-				dir_next->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog")));
+				dir_prev->set_icon(theme_cache.forward_folder);
+				dir_next->set_icon(theme_cache.back_folder);
 			} else {
-				dir_prev->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog")));
-				dir_next->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog")));
+				dir_prev->set_icon(theme_cache.back_folder);
+				dir_next->set_icon(theme_cache.forward_folder);
 			}
-			refresh->set_icon(vbox->get_theme_icon(SNAME("reload"), SNAME("FileDialog")));
-			show_hidden->set_icon(vbox->get_theme_icon(SNAME("toggle_hidden"), SNAME("FileDialog")));
-			_theme_changed();
+			refresh->set_icon(theme_cache.reload);
+			show_hidden->set_icon(theme_cache.toggle_hidden);
+
+			dir_up->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
+			dir_up->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color);
+			dir_up->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
+			dir_up->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
+
+			dir_prev->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color);
+			dir_prev->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color);
+			dir_prev->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
+			dir_prev->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color);
+
+			dir_next->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color);
+			dir_next->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color);
+			dir_next->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
+			dir_next->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color);
+
+			refresh->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
+			refresh->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color);
+			refresh->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
+			refresh->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
+
+			show_hidden->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
+			show_hidden->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color);
+			show_hidden->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
+			show_hidden->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
 		} break;
 
 		case NOTIFICATION_TRANSLATION_CHANGED: {
@@ -506,10 +520,6 @@ void FileDialog::update_file_list() {
 	}
 
 	TreeItem *root = tree->create_item();
-	Ref<Texture2D> folder = vbox->get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
-	Ref<Texture2D> file_icon = vbox->get_theme_icon(SNAME("file"), SNAME("FileDialog"));
-	const Color folder_color = vbox->get_theme_color(SNAME("folder_icon_modulate"), SNAME("FileDialog"));
-	const Color file_color = vbox->get_theme_color(SNAME("file_icon_modulate"), SNAME("FileDialog"));
 	List<String> files;
 	List<String> dirs;
 
@@ -541,8 +551,8 @@ void FileDialog::update_file_list() {
 		String &dir_name = dirs.front()->get();
 		TreeItem *ti = tree->create_item(root);
 		ti->set_text(0, dir_name);
-		ti->set_icon(0, folder);
-		ti->set_icon_modulate(0, folder_color);
+		ti->set_icon(0, theme_cache.folder);
+		ti->set_icon_modulate(0, theme_cache.folder_icon_modulate);
 
 		Dictionary d;
 		d["name"] = dir_name;
@@ -601,12 +611,12 @@ void FileDialog::update_file_list() {
 				Ref<Texture2D> icon = get_icon_func(base_dir.path_join(files.front()->get()));
 				ti->set_icon(0, icon);
 			} else {
-				ti->set_icon(0, file_icon);
+				ti->set_icon(0, theme_cache.file);
 			}
-			ti->set_icon_modulate(0, file_color);
+			ti->set_icon_modulate(0, theme_cache.file_icon_modulate);
 
 			if (mode == FILE_MODE_OPEN_DIR) {
-				ti->set_custom_color(0, vbox->get_theme_color(SNAME("files_disabled"), SNAME("FileDialog")));
+				ti->set_custom_color(0, theme_cache.files_disabled);
 				ti->set_selectable(0, false);
 			}
 			Dictionary d;
@@ -1006,7 +1016,6 @@ FileDialog::FileDialog() {
 
 	vbox = memnew(VBoxContainer);
 	add_child(vbox, false, INTERNAL_MODE_FRONT);
-	vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed));
 
 	mode = FILE_MODE_SAVE_FILE;
 	set_title(TTRC("Save a File"));

+ 20 - 1
scene/gui/file_dialog.h

@@ -109,6 +109,25 @@ private:
 
 	bool invalidated = true;
 
+	struct ThemeCache {
+		Ref<Texture2D> parent_folder;
+		Ref<Texture2D> forward_folder;
+		Ref<Texture2D> back_folder;
+		Ref<Texture2D> reload;
+		Ref<Texture2D> toggle_hidden;
+		Ref<Texture2D> folder;
+		Ref<Texture2D> file;
+
+		Color folder_icon_modulate;
+		Color file_icon_modulate;
+		Color files_disabled;
+
+		Color icon_normal_color;
+		Color icon_hover_color;
+		Color icon_focus_color;
+		Color icon_pressed_color;
+	} theme_cache;
+
 	void update_dir();
 	void update_file_name();
 	void update_file_list();
@@ -143,7 +162,7 @@ private:
 	virtual void _post_popup() override;
 
 protected:
-	void _theme_changed();
+	virtual void _update_theme_item_cache() override;
 
 	void _notification(int p_what);
 	static void _bind_methods();

+ 17 - 13
scene/gui/popup.cpp

@@ -68,6 +68,12 @@ void Popup::_deinitialize_visible_parents() {
 	}
 }
 
+void Popup::_update_theme_item_cache() {
+	Window::_update_theme_item_cache();
+
+	theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
+}
+
 void Popup::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -186,8 +192,6 @@ Popup::~Popup() {
 }
 
 Size2 PopupPanel::_get_contents_minimum_size() const {
-	Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name());
-
 	Size2 ms;
 
 	for (int i = 0; i < get_child_count(); i++) {
@@ -205,14 +209,12 @@ Size2 PopupPanel::_get_contents_minimum_size() const {
 		ms.y = MAX(cms.y, ms.y);
 	}
 
-	return ms + p->get_minimum_size();
+	return ms + theme_cache.panel_style->get_minimum_size();
 }
 
 void PopupPanel::_update_child_rects() {
-	Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name());
-
-	Vector2 cpos(p->get_offset());
-	Vector2 csize(get_size() - p->get_minimum_size());
+	Vector2 cpos(theme_cache.panel_style->get_offset());
+	Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size());
 
 	for (int i = 0; i < get_child_count(); i++) {
 		Control *c = Object::cast_to<Control>(get_child(i));
@@ -234,15 +236,17 @@ void PopupPanel::_update_child_rects() {
 	}
 }
 
+void PopupPanel::_update_theme_item_cache() {
+	Popup::_update_theme_item_cache();
+
+	theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
+}
+
 void PopupPanel::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_READY:
 		case NOTIFICATION_THEME_CHANGED: {
-			panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name()));
-		} break;
-
-		case NOTIFICATION_ENTER_TREE:
-		case NOTIFICATION_READY: {
-			panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name()));
+			panel->add_theme_style_override("panel", theme_cache.panel_style);
 			_update_child_rects();
 		} break;
 

+ 11 - 0
scene/gui/popup.h

@@ -43,6 +43,10 @@ class Popup : public Window {
 	LocalVector<Window *> visible_parents;
 	bool popped_up = false;
 
+	struct ThemeCache {
+		Ref<StyleBox> panel_style;
+	} theme_cache;
+
 	void _input_from_window(const Ref<InputEvent> &p_event);
 
 	void _initialize_visible_parents();
@@ -52,6 +56,7 @@ protected:
 	void _close_pressed();
 	virtual Rect2i _popup_adjust_rect() const override;
 
+	virtual void _update_theme_item_cache() override;
 	void _notification(int p_what);
 	static void _bind_methods();
 
@@ -69,8 +74,14 @@ class PopupPanel : public Popup {
 
 	Panel *panel = nullptr;
 
+	struct ThemeCache {
+		Ref<StyleBox> panel_style;
+	} theme_cache;
+
 protected:
 	void _update_child_rects();
+
+	virtual void _update_theme_item_cache() override;
 	void _notification(int p_what);
 
 	virtual Size2 _get_contents_minimum_size() const override;

+ 107 - 98
scene/gui/popup_menu.cpp

@@ -48,15 +48,12 @@ String PopupMenu::_get_accel_text(const Item &p_item) const {
 }
 
 Size2 PopupMenu::_get_contents_minimum_size() const {
-	int vseparation = get_theme_constant(SNAME("v_separation"));
-	int hseparation = get_theme_constant(SNAME("h_separation"));
-
-	Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container
+	Size2 minsize = theme_cache.panel_style->get_minimum_size(); // Accounts for margin in the margin container
 	minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content
 
 	float max_w = 0.0;
 	float icon_w = 0.0;
-	int check_w = MAX(get_theme_icon(SNAME("checked"))->get_width(), get_theme_icon(SNAME("radio_checked"))->get_width()) + hseparation;
+	int check_w = MAX(theme_cache.checked->get_width(), theme_cache.radio_checked->get_width()) + theme_cache.h_separation;
 	int accel_max_w = 0;
 	bool has_check = false;
 
@@ -67,23 +64,23 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
 		size.height = _get_item_height(i);
 		icon_w = MAX(icon_size.width, icon_w);
 
-		size.width += items[i].indent * get_theme_constant(SNAME("indent"));
+		size.width += items[i].indent * theme_cache.indent;
 
 		if (items[i].checkable_type && !items[i].separator) {
 			has_check = true;
 		}
 
 		size.width += items[i].text_buf->get_size().x;
-		size.height += vseparation;
+		size.height += theme_cache.v_separation;
 
 		if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
-			int accel_w = hseparation * 2;
+			int accel_w = theme_cache.h_separation * 2;
 			accel_w += items[i].accel_text_buf->get_size().x;
 			accel_max_w = MAX(accel_w, accel_max_w);
 		}
 
 		if (!items[i].submenu.is_empty()) {
-			size.width += get_theme_icon(SNAME("submenu"))->get_width();
+			size.width += theme_cache.submenu->get_width();
 		}
 
 		max_w = MAX(max_w, size.width);
@@ -91,7 +88,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
 		minsize.height += size.height;
 	}
 
-	int item_side_padding = get_theme_constant(SNAME("item_start_padding")) + get_theme_constant(SNAME("item_end_padding"));
+	int item_side_padding = theme_cache.item_start_padding + theme_cache.item_end_padding;
 	minsize.width += max_w + icon_w + accel_max_w + item_side_padding;
 
 	if (has_check) {
@@ -113,33 +110,31 @@ int PopupMenu::_get_item_height(int p_item) const {
 
 	int icon_height = items[p_item].get_icon_size().height;
 	if (items[p_item].checkable_type && !items[p_item].separator) {
-		icon_height = MAX(icon_height, MAX(get_theme_icon(SNAME("checked"))->get_height(), get_theme_icon(SNAME("radio_checked"))->get_height()));
+		icon_height = MAX(icon_height, MAX(theme_cache.checked->get_height(), theme_cache.radio_checked->get_height()));
 	}
 
 	int text_height = items[p_item].text_buf->get_size().height;
 	if (text_height == 0 && !items[p_item].separator) {
-		text_height = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size")));
+		text_height = theme_cache.font->get_height(theme_cache.font_size);
 	}
 
 	int separator_height = 0;
 	if (items[p_item].separator) {
-		separator_height = MAX(get_theme_stylebox(SNAME("separator"))->get_minimum_size().height, MAX(get_theme_stylebox(SNAME("labeled_separator_left"))->get_minimum_size().height, get_theme_stylebox(SNAME("labeled_separator_right"))->get_minimum_size().height));
+		separator_height = MAX(theme_cache.separator_style->get_minimum_size().height, MAX(theme_cache.labeled_separator_left->get_minimum_size().height, theme_cache.labeled_separator_right->get_minimum_size().height));
 	}
 
 	return MAX(separator_height, MAX(text_height, icon_height));
 }
 
 int PopupMenu::_get_items_total_height() const {
-	int vsep = get_theme_constant(SNAME("v_separation"));
-
 	// Get total height of all items by taking max of icon height and font height
 	int items_total_height = 0;
 	for (int i = 0; i < items.size(); i++) {
-		items_total_height += _get_item_height(i) + vsep;
+		items_total_height += _get_item_height(i) + theme_cache.v_separation;
 	}
 
 	// Subtract a separator which is not needed for the last item.
-	return items_total_height - vsep;
+	return items_total_height - theme_cache.v_separation;
 }
 
 int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
@@ -147,18 +142,15 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
 		return -1;
 	}
 
-	Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container
-
-	int vseparation = get_theme_constant(SNAME("v_separation"));
-
-	Point2 ofs = style->get_offset() + Point2(0, vseparation / 2);
+	// Accounts for margin in the margin container
+	Point2 ofs = theme_cache.panel_style->get_offset() + Point2(0, theme_cache.v_separation / 2);
 
 	if (ofs.y > p_over.y) {
 		return -1;
 	}
 
 	for (int i = 0; i < items.size(); i++) {
-		ofs.y += i > 0 ? vseparation : (float)vseparation / 2;
+		ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2;
 
 		ofs.y += _get_item_height(i);
 
@@ -179,9 +171,6 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
 		return; // Already visible.
 	}
 
-	Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
-	int vsep = get_theme_constant(SNAME("v_separation"));
-
 	Point2 this_pos = get_position();
 	Rect2 this_rect(this_pos, get_size());
 
@@ -231,7 +220,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
 	// Set autohide areas.
 
 	Rect2 safe_area = this_rect;
-	safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2;
+	safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2;
 	safe_area.size.y = items[p_over]._height_cache;
 	DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area);
 
@@ -240,11 +229,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
 
 	// Autohide area above the submenu item.
 	submenu_pum->clear_autohide_areas();
-	submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
+	submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2));
 
 	// If there is an area below the submenu item, add an autohide area there.
 	if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
-		int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
+		int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height;
 		submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
 	}
 }
@@ -528,34 +517,17 @@ void PopupMenu::_draw_items() {
 	margin_size.height = margin_container->get_theme_constant(SNAME("margin_top")) + margin_container->get_theme_constant(SNAME("margin_bottom"));
 
 	// Space between the item content and the sides of popup menu.
-	int item_start_padding = get_theme_constant(SNAME("item_start_padding"));
-	int item_end_padding = get_theme_constant(SNAME("item_end_padding"));
-
 	bool rtl = control->is_layout_rtl();
-	Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
-	Ref<StyleBox> hover = get_theme_stylebox(SNAME("hover"));
 	// In Item::checkable_type enum order (less the non-checkable member), with disabled repeated at the end.
-	Ref<Texture2D> check[] = { get_theme_icon(SNAME("checked")), get_theme_icon(SNAME("radio_checked")), get_theme_icon(SNAME("checked_disabled")), get_theme_icon(SNAME("radio_checked_disabled")) };
-	Ref<Texture2D> uncheck[] = { get_theme_icon(SNAME("unchecked")), get_theme_icon(SNAME("radio_unchecked")), get_theme_icon(SNAME("unchecked_disabled")), get_theme_icon(SNAME("radio_unchecked_disabled")) };
+	Ref<Texture2D> check[] = { theme_cache.checked, theme_cache.radio_checked, theme_cache.checked_disabled, theme_cache.radio_checked_disabled };
+	Ref<Texture2D> uncheck[] = { theme_cache.unchecked, theme_cache.radio_unchecked, theme_cache.unchecked_disabled, theme_cache.radio_unchecked_disabled };
 	Ref<Texture2D> submenu;
 	if (rtl) {
-		submenu = get_theme_icon(SNAME("submenu_mirrored"));
+		submenu = theme_cache.submenu_mirrored;
 	} else {
-		submenu = get_theme_icon(SNAME("submenu"));
+		submenu = theme_cache.submenu;
 	}
 
-	Ref<StyleBox> separator = get_theme_stylebox(SNAME("separator"));
-	Ref<StyleBox> labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left"));
-	Ref<StyleBox> labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right"));
-
-	int vseparation = get_theme_constant(SNAME("v_separation"));
-	int hseparation = get_theme_constant(SNAME("h_separation"));
-	Color font_color = get_theme_color(SNAME("font_color"));
-	Color font_disabled_color = get_theme_color(SNAME("font_disabled_color"));
-	Color font_accelerator_color = get_theme_color(SNAME("font_accelerator_color"));
-	Color font_hover_color = get_theme_color(SNAME("font_hover_color"));
-	Color font_separator_color = get_theme_color(SNAME("font_separator_color"));
-
 	float scroll_width = scroll_container->get_v_scroll_bar()->is_visible_in_tree() ? scroll_container->get_v_scroll_bar()->get_size().width : 0;
 	float display_width = control->get_size().width - scroll_width;
 
@@ -574,7 +546,7 @@ void PopupMenu::_draw_items() {
 		}
 	}
 	if (icon_ofs > 0.0) {
-		icon_ofs += hseparation;
+		icon_ofs += theme_cache.h_separation;
 	}
 
 	float check_ofs = 0.0;
@@ -583,7 +555,7 @@ void PopupMenu::_draw_items() {
 			check_ofs = MAX(check_ofs, check[i]->get_width());
 			check_ofs = MAX(check_ofs, uncheck[i]->get_width());
 		}
-		check_ofs += hseparation;
+		check_ofs += theme_cache.h_separation;
 	}
 
 	Point2 ofs = Point2();
@@ -591,7 +563,7 @@ void PopupMenu::_draw_items() {
 	// Loop through all items and draw each.
 	for (int i = 0; i < items.size(); i++) {
 		// For the first item only add half a separation. For all other items, add a whole separation to the offset.
-		ofs.y += i > 0 ? vseparation : (float)vseparation / 2;
+		ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2;
 
 		_shape_item(i);
 
@@ -601,47 +573,47 @@ void PopupMenu::_draw_items() {
 
 		if (i == mouse_over) {
 			if (rtl) {
-				hover->draw(ci, Rect2(item_ofs + Point2(scroll_width, -vseparation / 2), Size2(display_width, h + vseparation)));
+				theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(scroll_width, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation)));
 			} else {
-				hover->draw(ci, Rect2(item_ofs + Point2(0, -vseparation / 2), Size2(display_width, h + vseparation)));
+				theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(0, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation)));
 			}
 		}
 
 		String text = items[i].xl_text;
 
 		// Separator
-		item_ofs.x += items[i].indent * get_theme_constant(SNAME("indent"));
+		item_ofs.x += items[i].indent * theme_cache.indent;
 		if (items[i].separator) {
 			if (!text.is_empty() || !items[i].icon.is_null()) {
-				int content_size = items[i].text_buf->get_size().width + hseparation * 2;
+				int content_size = items[i].text_buf->get_size().width + theme_cache.h_separation * 2;
 				if (!items[i].icon.is_null()) {
-					content_size += icon_size.width + hseparation;
+					content_size += icon_size.width + theme_cache.h_separation;
 				}
 
 				int content_center = display_width / 2;
 				int content_left = content_center - content_size / 2;
 				int content_right = content_center + content_size / 2;
 				if (content_left > item_ofs.x) {
-					int sep_h = labeled_separator_left->get_center_size().height + labeled_separator_left->get_minimum_size().height;
+					int sep_h = theme_cache.labeled_separator_left->get_center_size().height + theme_cache.labeled_separator_left->get_minimum_size().height;
 					int sep_ofs = Math::floor((h - sep_h) / 2.0);
-					labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h)));
+					theme_cache.labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h)));
 				}
 				if (content_right < display_width) {
-					int sep_h = labeled_separator_right->get_center_size().height + labeled_separator_right->get_minimum_size().height;
+					int sep_h = theme_cache.labeled_separator_right->get_center_size().height + theme_cache.labeled_separator_right->get_minimum_size().height;
 					int sep_ofs = Math::floor((h - sep_h) / 2.0);
-					labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h)));
+					theme_cache.labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h)));
 				}
 			} else {
-				int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
+				int sep_h = theme_cache.separator_style->get_center_size().height + theme_cache.separator_style->get_minimum_size().height;
 				int sep_ofs = Math::floor((h - sep_h) / 2.0);
-				separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h)));
+				theme_cache.separator_style->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h)));
 			}
 		}
 
 		Color icon_color(1, 1, 1, items[i].disabled && !items[i].separator ? 0.5 : 1);
 
 		// For non-separator items, add some padding for the content.
-		item_ofs.x += item_start_padding;
+		item_ofs.x += theme_cache.item_start_padding;
 
 		// Checkboxes
 		if (items[i].checkable_type && !items[i].separator) {
@@ -659,7 +631,7 @@ void PopupMenu::_draw_items() {
 		// Icon
 		if (!items[i].icon.is_null()) {
 			if (items[i].separator) {
-				separator_ofs -= (icon_size.width + hseparation) / 2;
+				separator_ofs -= (icon_size.width + theme_cache.h_separation) / 2;
 
 				if (rtl) {
 					items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
@@ -678,61 +650,55 @@ void PopupMenu::_draw_items() {
 		// Submenu arrow on right hand side.
 		if (!items[i].submenu.is_empty()) {
 			if (rtl) {
-				submenu->draw(ci, Point2(scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
+				submenu->draw(ci, Point2(scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
 			} else {
-				submenu->draw(ci, Point2(display_width - style->get_margin(SIDE_RIGHT) - submenu->get_width() - item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
+				submenu->draw(ci, Point2(display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - submenu->get_width() - theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
 			}
 		}
 
-		Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
-		int outline_size = get_theme_constant(SNAME("outline_size"));
-
 		// Text
 		if (items[i].separator) {
-			Color font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color"));
-			int separator_outline_size = get_theme_constant(SNAME("separator_outline_size"));
-
 			if (!text.is_empty()) {
 				Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
 				if (!rtl && !items[i].icon.is_null()) {
-					text_pos.x += icon_size.width + hseparation;
+					text_pos.x += icon_size.width + theme_cache.h_separation;
 				}
 
-				if (separator_outline_size > 0 && font_separator_outline_color.a > 0) {
-					items[i].text_buf->draw_outline(ci, text_pos, separator_outline_size, font_separator_outline_color);
+				if (theme_cache.font_separator_outline_size > 0 && theme_cache.font_separator_outline_color.a > 0) {
+					items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_separator_outline_size, theme_cache.font_separator_outline_color);
 				}
-				items[i].text_buf->draw(ci, text_pos, font_separator_color);
+				items[i].text_buf->draw(ci, text_pos, theme_cache.font_separator_color);
 			}
 		} else {
 			item_ofs.x += icon_ofs + check_ofs;
 
 			if (rtl) {
 				Vector2 text_pos = Size2(control->get_size().width - items[i].text_buf->get_size().width - item_ofs.x, item_ofs.y) + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
-				if (outline_size > 0 && font_outline_color.a > 0) {
-					items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
+				if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
+					items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
 				}
-				items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color));
+				items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color));
 			} else {
 				Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
-				if (outline_size > 0 && font_outline_color.a > 0) {
-					items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
+				if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
+					items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
 				}
-				items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color));
+				items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color));
 			}
 		}
 
 		// Accelerator / Shortcut
 		if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
 			if (rtl) {
-				item_ofs.x = scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding;
+				item_ofs.x = scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding;
 			} else {
-				item_ofs.x = display_width - style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - item_end_padding;
+				item_ofs.x = display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - theme_cache.item_end_padding;
 			}
 			Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
-			if (outline_size > 0 && font_outline_color.a > 0) {
-				items[i].accel_text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
+			if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
+				items[i].accel_text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
 			}
-			items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? font_hover_color : font_accelerator_color);
+			items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_accelerator_color);
 		}
 
 		// Cache the item vertical offset from the first item and the height.
@@ -744,9 +710,8 @@ void PopupMenu::_draw_items() {
 }
 
 void PopupMenu::_draw_background() {
-	Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
 	RID ci2 = margin_container->get_canvas_item();
-	style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
+	theme_cache.panel_style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
 }
 
 void PopupMenu::_minimum_lifetime_timeout() {
@@ -778,8 +743,8 @@ void PopupMenu::_shape_item(int p_item) {
 	if (items.write[p_item].dirty) {
 		items.write[p_item].text_buf->clear();
 
-		Ref<Font> font = get_theme_font(items[p_item].separator ? SNAME("font_separator") : SNAME("font"));
-		int font_size = get_theme_font_size(items[p_item].separator ? SNAME("font_separator_size") : SNAME("font_size"));
+		Ref<Font> font = items[p_item].separator ? theme_cache.font_separator : theme_cache.font;
+		int font_size = items[p_item].separator ? theme_cache.font_separator_size : theme_cache.font_size;
 
 		if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) {
 			items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
@@ -821,6 +786,51 @@ void PopupMenu::remove_child_notify(Node *p_child) {
 	_menu_changed();
 }
 
+void PopupMenu::_update_theme_item_cache() {
+	Popup::_update_theme_item_cache();
+
+	theme_cache.panel_style = get_theme_stylebox(SNAME("panel"));
+	theme_cache.hover_style = get_theme_stylebox(SNAME("hover"));
+
+	theme_cache.separator_style = get_theme_stylebox(SNAME("separator"));
+	theme_cache.labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left"));
+	theme_cache.labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right"));
+
+	theme_cache.v_separation = get_theme_constant(SNAME("v_separation"));
+	theme_cache.h_separation = get_theme_constant(SNAME("h_separation"));
+	theme_cache.indent = get_theme_constant(SNAME("indent"));
+	theme_cache.item_start_padding = get_theme_constant(SNAME("item_start_padding"));
+	theme_cache.item_end_padding = get_theme_constant(SNAME("item_end_padding"));
+
+	theme_cache.checked = get_theme_icon(SNAME("checked"));
+	theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled"));
+	theme_cache.unchecked = get_theme_icon(SNAME("unchecked"));
+	theme_cache.unchecked_disabled = get_theme_icon(SNAME("unchecked_disabled"));
+	theme_cache.radio_checked = get_theme_icon(SNAME("radio_checked"));
+	theme_cache.radio_checked_disabled = get_theme_icon(SNAME("radio_checked_disabled"));
+	theme_cache.radio_unchecked = get_theme_icon(SNAME("radio_unchecked"));
+	theme_cache.radio_unchecked_disabled = get_theme_icon(SNAME("radio_unchecked_disabled"));
+
+	theme_cache.submenu = get_theme_icon(SNAME("submenu"));
+	theme_cache.submenu_mirrored = get_theme_icon(SNAME("submenu_mirrored"));
+
+	theme_cache.font = get_theme_font(SNAME("font"));
+	theme_cache.font_size = get_theme_font_size(SNAME("font_size"));
+	theme_cache.font_separator = get_theme_font(SNAME("font_separator"));
+	theme_cache.font_separator_size = get_theme_font_size(SNAME("font_separator_size"));
+
+	theme_cache.font_color = get_theme_color(SNAME("font_color"));
+	theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color"));
+	theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color"));
+	theme_cache.font_accelerator_color = get_theme_color(SNAME("font_accelerator_color"));
+	theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size"));
+	theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color"));
+
+	theme_cache.font_separator_color = get_theme_color(SNAME("font_separator_color"));
+	theme_cache.font_separator_outline_size = get_theme_constant(SNAME("separator_outline_size"));
+	theme_cache.font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color"));
+}
+
 void PopupMenu::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
@@ -909,11 +919,10 @@ void PopupMenu::_notification(int p_what) {
 				}
 
 				// Set margin on the margin container
-				Ref<StyleBox> panel_style = get_theme_stylebox(SNAME("panel"));
-				margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Side::SIDE_LEFT));
-				margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP));
-				margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Side::SIDE_RIGHT));
-				margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM));
+				margin_container->add_theme_constant_override("margin_left", theme_cache.panel_style->get_margin(Side::SIDE_LEFT));
+				margin_container->add_theme_constant_override("margin_top", theme_cache.panel_style->get_margin(Side::SIDE_TOP));
+				margin_container->add_theme_constant_override("margin_right", theme_cache.panel_style->get_margin(Side::SIDE_RIGHT));
+				margin_container->add_theme_constant_override("margin_bottom", theme_cache.panel_style->get_margin(Side::SIDE_BOTTOM));
 			}
 		} break;
 	}

+ 45 - 0
scene/gui/popup_menu.h

@@ -129,6 +129,49 @@ class PopupMenu : public Popup {
 	ScrollContainer *scroll_container = nullptr;
 	Control *control = nullptr;
 
+	struct ThemeCache {
+		Ref<StyleBox> panel_style;
+		Ref<StyleBox> hover_style;
+
+		Ref<StyleBox> separator_style;
+		Ref<StyleBox> labeled_separator_left;
+		Ref<StyleBox> labeled_separator_right;
+
+		int v_separation = 0;
+		int h_separation = 0;
+		int indent = 0;
+		int item_start_padding = 0;
+		int item_end_padding = 0;
+
+		Ref<Texture2D> checked;
+		Ref<Texture2D> checked_disabled;
+		Ref<Texture2D> unchecked;
+		Ref<Texture2D> unchecked_disabled;
+		Ref<Texture2D> radio_checked;
+		Ref<Texture2D> radio_checked_disabled;
+		Ref<Texture2D> radio_unchecked;
+		Ref<Texture2D> radio_unchecked_disabled;
+
+		Ref<Texture2D> submenu;
+		Ref<Texture2D> submenu_mirrored;
+
+		Ref<Font> font;
+		int font_size = 0;
+		Ref<Font> font_separator;
+		int font_separator_size = 0;
+
+		Color font_color;
+		Color font_hover_color;
+		Color font_disabled_color;
+		Color font_accelerator_color;
+		int font_outline_size = 0;
+		Color font_outline_color;
+
+		Color font_separator_color;
+		int font_separator_outline_size = 0;
+		Color font_separator_outline_color;
+	} theme_cache;
+
 	void _draw_items();
 	void _draw_background();
 
@@ -137,6 +180,8 @@ class PopupMenu : public Popup {
 	void _menu_changed();
 
 protected:
+	virtual void _update_theme_item_cache() override;
+
 	virtual void add_child_notify(Node *p_child) override;
 	virtual void remove_child_notify(Node *p_child) override;
 	void _notification(int p_what);

+ 64 - 6
scene/main/window.cpp

@@ -799,6 +799,11 @@ Viewport *Window::_get_embedder() const {
 
 void Window::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_POSTINITIALIZE: {
+			_invalidate_theme_cache();
+			_update_theme_item_cache();
+		} break;
+
 		case NOTIFICATION_ENTER_TREE: {
 			bool embedded = false;
 			{
@@ -858,6 +863,8 @@ void Window::_notification(int p_what) {
 
 		case NOTIFICATION_THEME_CHANGED: {
 			emit_signal(SceneStringNames::get_singleton()->theme_changed);
+			_invalidate_theme_cache();
+			_update_theme_item_cache();
 		} break;
 
 		case NOTIFICATION_READY: {
@@ -867,6 +874,9 @@ void Window::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_TRANSLATION_CHANGED: {
+			_invalidate_theme_cache();
+			_update_theme_item_cache();
+
 			if (embedder) {
 				embedder->_sub_window_update(this);
 			} else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
@@ -1342,6 +1352,18 @@ void Window::_theme_changed() {
 	}
 }
 
+void Window::_invalidate_theme_cache() {
+	theme_icon_cache.clear();
+	theme_style_cache.clear();
+	theme_font_cache.clear();
+	theme_font_size_cache.clear();
+	theme_color_cache.clear();
+	theme_constant_cache.clear();
+}
+
+void Window::_update_theme_item_cache() {
+}
+
 void Window::set_theme_type_variation(const StringName &p_theme_type) {
 	theme_type_variation = p_theme_type;
 	if (is_inside_tree()) {
@@ -1366,39 +1388,75 @@ void Window::_get_theme_type_dependencies(const StringName &p_theme_type, List<S
 }
 
 Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+	if (theme_icon_cache.has(p_theme_type) && theme_icon_cache[p_theme_type].has(p_name)) {
+		return theme_icon_cache[p_theme_type][p_name];
+	}
+
 	List<StringName> theme_types;
 	_get_theme_type_dependencies(p_theme_type, &theme_types);
-	return Control::get_theme_item_in_types<Ref<Texture2D>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types);
+	Ref<Texture2D> icon = Control::get_theme_item_in_types<Ref<Texture2D>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types);
+	theme_icon_cache[p_theme_type][p_name] = icon;
+	return icon;
 }
 
 Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+	if (theme_style_cache.has(p_theme_type) && theme_style_cache[p_theme_type].has(p_name)) {
+		return theme_style_cache[p_theme_type][p_name];
+	}
+
 	List<StringName> theme_types;
 	_get_theme_type_dependencies(p_theme_type, &theme_types);
-	return Control::get_theme_item_in_types<Ref<StyleBox>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types);
+	Ref<StyleBox> style = Control::get_theme_item_in_types<Ref<StyleBox>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types);
+	theme_style_cache[p_theme_type][p_name] = style;
+	return style;
 }
 
 Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+	if (theme_font_cache.has(p_theme_type) && theme_font_cache[p_theme_type].has(p_name)) {
+		return theme_font_cache[p_theme_type][p_name];
+	}
+
 	List<StringName> theme_types;
 	_get_theme_type_dependencies(p_theme_type, &theme_types);
-	return Control::get_theme_item_in_types<Ref<Font>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types);
+	Ref<Font> font = Control::get_theme_item_in_types<Ref<Font>>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types);
+	theme_font_cache[p_theme_type][p_name] = font;
+	return font;
 }
 
 int Window::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+	if (theme_font_size_cache.has(p_theme_type) && theme_font_size_cache[p_theme_type].has(p_name)) {
+		return theme_font_size_cache[p_theme_type][p_name];
+	}
+
 	List<StringName> theme_types;
 	_get_theme_type_dependencies(p_theme_type, &theme_types);
-	return Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types);
+	int font_size = Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types);
+	theme_font_size_cache[p_theme_type][p_name] = font_size;
+	return font_size;
 }
 
 Color Window::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+	if (theme_color_cache.has(p_theme_type) && theme_color_cache[p_theme_type].has(p_name)) {
+		return theme_color_cache[p_theme_type][p_name];
+	}
+
 	List<StringName> theme_types;
 	_get_theme_type_dependencies(p_theme_type, &theme_types);
-	return Control::get_theme_item_in_types<Color>(theme_owner, theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types);
+	Color color = Control::get_theme_item_in_types<Color>(theme_owner, theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types);
+	theme_color_cache[p_theme_type][p_name] = color;
+	return color;
 }
 
 int Window::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+	if (theme_constant_cache.has(p_theme_type) && theme_constant_cache[p_theme_type].has(p_name)) {
+		return theme_constant_cache[p_theme_type][p_name];
+	}
+
 	List<StringName> theme_types;
 	_get_theme_type_dependencies(p_theme_type, &theme_types);
-	return Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types);
+	int constant = Control::get_theme_item_in_types<int>(theme_owner, theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types);
+	theme_constant_cache[p_theme_type][p_name] = constant;
+	return constant;
 }
 
 bool Window::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {

+ 15 - 3
scene/main/window.h

@@ -32,12 +32,12 @@
 #define WINDOW_H
 
 #include "scene/main/viewport.h"
+#include "scene/resources/theme.h"
 
 class Control;
 class Font;
 class Shortcut;
 class StyleBox;
-class Theme;
 
 class Window : public Viewport {
 	GDCLASS(Window, Viewport)
@@ -141,6 +141,18 @@ private:
 	Window *theme_owner_window = nullptr;
 	StringName theme_type_variation;
 
+	mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache;
+	mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache;
+	mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache;
+	mutable HashMap<StringName, Theme::ThemeFontSizeMap> theme_font_size_cache;
+	mutable HashMap<StringName, Theme::ThemeColorMap> theme_color_cache;
+	mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache;
+
+	_FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const;
+
+	void _theme_changed();
+	void _invalidate_theme_cache();
+
 	Viewport *embedder = nullptr;
 
 	friend class Viewport; //friend back, can call the methods below
@@ -158,6 +170,8 @@ protected:
 	Viewport *_get_embedder() const;
 	virtual Rect2i _popup_adjust_rect() const { return Rect2i(); }
 
+	virtual void _update_theme_item_cache();
+
 	virtual void _post_popup() {}
 	virtual Size2 _get_contents_minimum_size() const;
 	static void _bind_methods();
@@ -259,11 +273,9 @@ public:
 
 	void set_theme(const Ref<Theme> &p_theme);
 	Ref<Theme> get_theme() const;
-	void _theme_changed();
 
 	void set_theme_type_variation(const StringName &p_theme_type);
 	StringName get_theme_type_variation() const;
-	_FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const;
 
 	Size2 get_contents_minimum_size() const;
 

+ 3 - 5
scene/resources/default_theme/default_theme.cpp

@@ -609,11 +609,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 
 	// Dialogs
 
-	theme->set_constant("margin", "Dialogs", 8 * scale);
-	theme->set_constant("button_margin", "Dialogs", 32 * scale);
-
-	// AcceptDialog
-
+	// AcceptDialog is currently the base dialog, so this defines styles for all extending nodes.
+	theme->set_constant("margin", "AcceptDialog", 8 * scale);
+	theme->set_constant("button_margin", "AcceptDialog", 32 * scale);
 	theme->set_stylebox("panel", "AcceptDialog", make_flat_stylebox(style_popup_color, 0, 0, 0, 0));
 
 	// File Dialog