소스 검색

Refactor Theme item management in the theme editor

Backport of #46593, #46808, #49227
Yuri Sizov 4 년 전
부모
커밋
014cb0c33a

+ 1 - 0
editor/icons/icon_collapse_tree.svg

@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-rule="nonzero"><path d="m8 9.669-3.536 2.583h2.536v2.537h2v-2.537h2.536z"/><path d="m8 6.355-3.536-2.583h2.536v-2.537h2v2.537h2.536z"/><path d="m.704 7.085h14.591v1.831h-14.591z"/></g></svg>

+ 1 - 0
editor/icons/icon_expand_tree.svg

@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-rule="nonzero"><path d="m8 16-3.536-2.597h2.536v-2.523h2v2.523h2.536z"/><path d="m8 0-3.536 2.583h2.536v2.537h2v-2.537h2.536z"/><path d="m.704 7.085h14.591v1.831h-14.591z"/></g></svg>

+ 1 - 0
editor/icons/icon_node_disabled.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 2a6 6 0 0 0 -6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0 -6-6zm0 2a4 4 0 0 1 4 4 4 4 0 0 1 -4 4 4 4 0 0 1 -4-4 4 4 0 0 1 4-4z" fill="#999"/></svg>

+ 1 - 0
editor/icons/icon_theme_deselect_all.svg

@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="m5.952 6.976h8.063v2.005h-8.063z" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.01h-4.011zm.968.968h2.075v2.074h-2.075z"/><path d="m5.952 1.956h8.063v2.005h-8.063z" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z"/><path d="m5.952 11.996h8.063v2.005h-8.063z" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z"/></g></svg>

+ 1 - 0
editor/icons/icon_theme_remove_all_items.svg

@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1.745c-.595 0-1.084.489-1.084 1.084v3.699l-3.851-1.927c-.163-.08-.343-.119-.525-.112-.395.015-.752.244-.929.597-.076.151-.115.317-.115.485 0 .41.233.786.599.97l3.481 1.74-3.481 1.74c-.366.184-.599.56-.599.97 0 .168.039.334.115.485.183.367.559.599.969.599.168 0 .334-.039.485-.114l3.851-1.927v3.111c0 .594.489 1.084 1.084 1.084s1.084-.49 1.084-1.084v-3.111l3.851 1.927c.151.075.317.114.485.114.41 0 .786-.232.969-.599.076-.151.115-.317.115-.485 0-.41-.233-.786-.599-.97l-3.481-1.74 3.481-1.74c.366-.184.599-.56.599-.97 0-.168-.039-.334-.115-.485-.182-.364-.554-.596-.961-.599-.171-.001-.34.038-.493.114l-3.851 1.927v-3.699c0-.595-.489-1.084-1.084-1.084z" fill="#8eef97"/><g fill-rule="nonzero"><path d="m8 1.745v1.783h-1.084v-.699c0-.595.489-1.084 1.084-1.084z" fill="#ff4545"/><path d="m1.528 5.312h2.957l-1.42-.711c-.163-.08-.343-.119-.525-.112-.395.015-.752.244-.929.597-.036.072-.064.148-.083.226zm5.388-1.784h1.084v1.784h-1.084z" fill="#ffe345"/><path d="m6.916 5.312h1.084v1.783h-4.796l-1.109-.554c-.366-.184-.599-.56-.599-.97 0-.088.011-.175.032-.259h2.957l2.431 1.216z" fill="#80ff45"/><path d="m3.204 7.095h4.796v1.783h-3.619l1.195-.597z" fill="#45ffa2"/><path d="m4.381 8.878h3.619v1.784h-1.084v-.628l-1.255.628h-4.114c.088-.274.283-.508.548-.641z" fill="#45d7ff"/><path d="m6.916 12.445h1.084v1.784c-.595-.001-1.084-.49-1.084-1.084z" fill="#ff4596"/><path d="m6.916 10.662h1.084v1.783h-1.084zm-1.255 0h-4.114c-.033.105-.051.216-.051.329 0 .168.039.334.115.485.183.367.559.599.969.599.168 0 .334-.039.485-.114z" fill="#8045ff"/></g></svg>

+ 1 - 0
editor/icons/icon_theme_remove_custom_items.svg

@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero"><path d="m11.299 3c.772.513 1.42 1.199 1.888 2h-2.553c-.706-.621-1.629-1-2.634-1s-1.928.379-2.634 1h-2.553c.468-.801 1.116-1.487 1.888-2z" fill="#ffe345"/><path d="m5.366 5c-.593.522-1.033 1.216-1.238 2h-2.043c.122-.717.373-1.392.728-2zm7.821 0c.355.608.606 1.283.728 2h-2.043c-.205-.784-.645-1.478-1.238-2z" fill="#80ff45"/><path d="m13.915 7c.056.326.085.66.085 1s-.029.674-.085 1h-2.043c.083-.32.128-.655.128-1s-.045-.68-.128-1zm-9.787 0c-.083.32-.128.655-.128 1s.045.68.128 1h-2.043c-.056-.326-.085-.66-.085-1s.029-.674.085-1z" fill="#45ffa2"/><path d="m4.128 9c.205.784.645 1.478 1.238 2h-2.553c-.355-.608-.606-1.283-.728-2zm9.787 0c-.122.717-.373 1.392-.728 2h-2.553c.593-.522 1.033-1.216 1.238-2z" fill="#45d7ff"/><path d="m11.299 13h-6.598c.949.631 2.084 1 3.299 1s2.35-.369 3.299-1z" fill="#ff4596"/><path d="m13.187 11c-.468.801-1.116 1.487-1.888 2h-6.598c-.772-.513-1.42-1.199-1.888-2h2.553c.706.621 1.629 1 2.634 1s1.928-.379 2.634-1z" fill="#8045ff"/><path d="m4.701 3h6.598c-.949-.631-2.084-1-3.299-1s-2.35.369-3.299 1z" fill="#ff4545"/></g></svg>

+ 1 - 0
editor/icons/icon_theme_select_all.svg

@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m5.952 6.976h8.049v2.005h-8.049z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.01h-4.011zm.968.968h2.075v2.074h-2.075z" fill="#e0e0e0"/><path d="m5.952 1.956h8.049v2.005h-8.049z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/><path d="m5.952 11.996h8.049v2.005h-8.049z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/></svg>

+ 1 - 0
editor/icons/icon_theme_select_full.svg

@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m11 6.976h3.015v2.005h-3.015z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 5.995h4.011v4.01h-4.011zm.968.968h2.075v2.074h-2.075z" fill="#e0e0e0"/><path d="m11 1.956h3.015v2.005h-3.015z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989.974h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/><path d="m11 11.996h3.015v2.005h-3.015z" fill="#e0e0e0" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m.989 11.015h4.011v4.011h-4.011zm.968.968h2.075v2.075h-2.075z" fill="#e0e0e0"/><path d="m5.995 5.995h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m5.995 5.995h4.01v4.01h-4.01zm.968.968h2.074v2.074h-2.074z" fill="#e0e0e0"/><path d="m5.995.974h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m5.995.974h4.01v4.011h-4.01zm.968.968h2.074v2.075h-2.074z" fill="#e0e0e0"/><path d="m5.995 11.015h4.011v4.011h-4.011z" fill="#fff" fill-rule="nonzero"/><path d="m5.995 11.015h4.01v4.011h-4.01zm.968.968h2.074v2.075h-2.074z" fill="#e0e0e0"/></svg>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1773 - 467
editor/plugins/theme_editor_plugin.cpp


+ 219 - 37
editor/plugins/theme_editor_plugin.h

@@ -31,8 +31,6 @@
 #ifndef THEME_EDITOR_PLUGIN_H
 #define THEME_EDITOR_PLUGIN_H
 
-#include "scene/gui/check_box.h"
-#include "scene/gui/file_dialog.h"
 #include "scene/gui/margin_container.h"
 #include "scene/gui/option_button.h"
 #include "scene/gui/scroll_container.h"
@@ -41,49 +39,233 @@
 
 #include "editor/editor_node.h"
 
+class ThemeItemImportTree : public VBoxContainer {
+	GDCLASS(ThemeItemImportTree, VBoxContainer);
+
+	Ref<Theme> edited_theme;
+	Ref<Theme> base_theme;
+
+	struct ThemeItem {
+		String type_name;
+		Theme::DataType data_type;
+		String item_name;
+
+		bool operator<(const ThemeItem &p_item) const {
+			if (type_name == p_item.type_name && data_type == p_item.data_type) {
+				return item_name < p_item.item_name;
+			}
+			if (type_name == p_item.type_name) {
+				return data_type < p_item.data_type;
+			}
+			return type_name < p_item.type_name;
+		}
+	};
+
+	enum ItemCheckedState {
+		SELECT_IMPORT_DEFINITION,
+		SELECT_IMPORT_FULL,
+	};
+
+	Map<ThemeItem, ItemCheckedState> selected_items;
+
+	LineEdit *import_items_filter;
+
+	Tree *import_items_tree;
+	List<TreeItem *> tree_color_items;
+	List<TreeItem *> tree_constant_items;
+	List<TreeItem *> tree_font_items;
+	List<TreeItem *> tree_icon_items;
+	List<TreeItem *> tree_stylebox_items;
+
+	bool updating_tree = false;
+
+	enum ItemActionFlag {
+		IMPORT_ITEM = 1,
+		IMPORT_ITEM_DATA = 2,
+	};
+
+	TextureRect *select_colors_icon;
+	Label *select_colors_label;
+	Button *select_all_colors_button;
+	Button *select_full_colors_button;
+	Button *deselect_all_colors_button;
+	Label *total_selected_colors_label;
+
+	TextureRect *select_constants_icon;
+	Label *select_constants_label;
+	Button *select_all_constants_button;
+	Button *select_full_constants_button;
+	Button *deselect_all_constants_button;
+	Label *total_selected_constants_label;
+
+	TextureRect *select_fonts_icon;
+	Label *select_fonts_label;
+	Button *select_all_fonts_button;
+	Button *select_full_fonts_button;
+	Button *deselect_all_fonts_button;
+	Label *total_selected_fonts_label;
+
+	TextureRect *select_icons_icon;
+	Label *select_icons_label;
+	Button *select_all_icons_button;
+	Button *select_full_icons_button;
+	Button *deselect_all_icons_button;
+	Label *total_selected_icons_label;
+
+	TextureRect *select_styleboxes_icon;
+	Label *select_styleboxes_label;
+	Button *select_all_styleboxes_button;
+	Button *select_full_styleboxes_button;
+	Button *deselect_all_styleboxes_button;
+	Label *total_selected_styleboxes_label;
+
+	HBoxContainer *select_icons_warning_hb;
+	TextureRect *select_icons_warning_icon;
+	Label *select_icons_warning;
+
+	Button *import_collapse_types_button;
+	Button *import_expand_types_button;
+	Button *import_select_all_button;
+	Button *import_select_full_button;
+	Button *import_deselect_all_button;
+
+	void _update_items_tree();
+	void _toggle_type_items(bool p_collapse);
+	void _filter_text_changed(const String &p_value);
+
+	void _store_selected_item(TreeItem *p_tree_item);
+	void _restore_selected_item(TreeItem *p_tree_item);
+	void _update_total_selected(Theme::DataType p_data_type);
+
+	void _tree_item_edited();
+	void _select_all_subitems(TreeItem *p_root_item, bool p_select_with_data);
+	void _deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely);
+	void _update_parent_items(TreeItem *p_root_item);
+
+	void _select_all_items_pressed();
+	void _select_full_items_pressed();
+	void _deselect_all_items_pressed();
+
+	void _select_all_data_type_pressed(int p_data_type);
+	void _select_full_data_type_pressed(int p_data_type);
+	void _deselect_all_data_type_pressed(int p_data_type);
+
+	void _import_selected();
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+public:
+	void set_edited_theme(const Ref<Theme> &p_theme);
+	void set_base_theme(const Ref<Theme> &p_theme);
+	void reset_item_tree();
+
+	bool has_selected_items() const;
+
+	ThemeItemImportTree();
+};
+
+class ThemeItemEditorDialog : public AcceptDialog {
+	GDCLASS(ThemeItemEditorDialog, AcceptDialog);
+
+	Ref<Theme> edited_theme;
+
+	TabContainer *tc;
+
+	ItemList *edit_type_list;
+	LineEdit *edit_add_type_value;
+	String edited_item_type;
+
+	Button *edit_items_add_color;
+	Button *edit_items_add_constant;
+	Button *edit_items_add_font;
+	Button *edit_items_add_icon;
+	Button *edit_items_add_stylebox;
+	Button *edit_items_remove_class;
+	Button *edit_items_remove_custom;
+	Button *edit_items_remove_all;
+	Tree *edit_items_tree;
+
+	enum ItemsTreeAction {
+		ITEMS_TREE_RENAME_ITEM,
+		ITEMS_TREE_REMOVE_ITEM,
+		ITEMS_TREE_REMOVE_DATA_TYPE,
+	};
+
+	ConfirmationDialog *edit_theme_item_dialog;
+	VBoxContainer *edit_theme_item_old_vb;
+	Label *theme_item_old_name;
+	LineEdit *theme_item_name;
+
+	enum ItemPopupMode {
+		CREATE_THEME_ITEM,
+		RENAME_THEME_ITEM,
+		ITEM_POPUP_MODE_MAX
+	};
+
+	ItemPopupMode item_popup_mode = ITEM_POPUP_MODE_MAX;
+	String edit_item_old_name;
+	Theme::DataType edit_item_data_type = Theme::DATA_TYPE_MAX;
+
+	ThemeItemImportTree *import_default_theme_items;
+	ThemeItemImportTree *import_editor_theme_items;
+	ThemeItemImportTree *import_other_theme_items;
+
+	LineEdit *import_another_theme_value;
+	Button *import_another_theme_button;
+	EditorFileDialog *import_another_theme_dialog;
+
+	ConfirmationDialog *confirm_closing_dialog;
+
+	void ok_pressed();
+	void _close_dialog();
+
+	void _dialog_about_to_show();
+	void _update_edit_types();
+	void _edited_type_selected(int p_item_idx);
+
+	void _update_edit_item_tree(String p_item_type);
+	void _item_tree_button_pressed(Object *p_item, int p_column, int p_id);
+
+	void _add_theme_type();
+	void _add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type);
+	void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type);
+	void _remove_class_items();
+	void _remove_custom_items();
+	void _remove_all_items();
+
+	void _open_add_theme_item_dialog(int p_data_type);
+	void _open_rename_theme_item_dialog(Theme::DataType p_data_type, String p_item_name);
+	void _confirm_edit_theme_item();
+	void _edit_theme_item_gui_input(const Ref<InputEvent> &p_event);
+
+	void _open_select_another_theme();
+	void _select_another_theme_cbk(const String &p_path);
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+public:
+	void set_edited_theme(const Ref<Theme> &p_theme);
+
+	ThemeItemEditorDialog();
+};
+
 class ThemeEditor : public VBoxContainer {
 	GDCLASS(ThemeEditor, VBoxContainer);
 
-	Panel *main_panel;
-	MarginContainer *main_container;
 	Ref<Theme> theme;
+	double time_left = 0;
 
-	EditorFileDialog *file_dialog;
-
-	double time_left;
-
-	MenuButton *theme_menu;
-	ConfirmationDialog *add_del_dialog;
-	HBoxContainer *type_hbc;
-	MenuButton *type_menu;
-	LineEdit *type_edit;
-	HBoxContainer *name_hbc;
-	MenuButton *name_menu;
-	LineEdit *name_edit;
-	OptionButton *type_select;
-	Label *type_select_label;
-	Label *name_select_label;
-
-	enum PopupMode {
-		POPUP_ADD,
-		POPUP_CLASS_ADD,
-		POPUP_REMOVE,
-		POPUP_CLASS_REMOVE,
-		POPUP_CREATE_EMPTY,
-		POPUP_CREATE_EDITOR_EMPTY,
-		POPUP_IMPORT_EDITOR_THEME
-	};
+	ThemeItemEditorDialog *theme_edit_dialog;
 
-	int popup_mode;
+	void _theme_edit_button_cbk();
 
-	Tree *test_tree;
+	Panel *main_panel;
+	MarginContainer *main_container;
 
-	void _save_template_cbk(String fname);
-	void _dialog_cbk();
-	void _type_menu_cbk(int p_option);
-	void _name_menu_about_to_show();
-	void _name_menu_cbk(int p_option);
-	void _theme_menu_cbk(int p_option);
 	void _propagate_redraw(Control *p_at);
 	void _refresh_interval();
 

+ 79 - 63
scene/resources/theme.cpp

@@ -33,6 +33,11 @@
 #include "core/print_string.h"
 
 void Theme::_emit_theme_changed() {
+	if (no_change_propagation) {
+		return;
+	}
+
+	_change_notify();
 	emit_changed();
 }
 
@@ -381,8 +386,7 @@ void Theme::set_default_theme_font(const Ref<Font> &p_default_font) {
 		default_theme_font->connect("changed", this, "_emit_theme_changed", varray(), CONNECT_REFERENCE_COUNTED);
 	}
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 Ref<Font> Theme::get_default_theme_font() const {
@@ -424,8 +428,6 @@ void Theme::set_default_font(const Ref<Font> &p_font) {
 }
 
 void Theme::set_icon(const StringName &p_name, const StringName &p_node_type, const Ref<Texture> &p_icon) {
-	bool new_value = !icon_map.has(p_node_type) || !icon_map[p_node_type].has(p_name);
-
 	if (icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid()) {
 		icon_map[p_node_type][p_name]->disconnect("changed", this, "_emit_theme_changed");
 	}
@@ -436,10 +438,7 @@ void Theme::set_icon(const StringName &p_name, const StringName &p_node_type, co
 		icon_map[p_node_type][p_name]->connect("changed", this, "_emit_theme_changed", varray(), CONNECT_REFERENCE_COUNTED);
 	}
 
-	if (new_value) {
-		_change_notify();
-		emit_changed();
-	}
+	_emit_theme_changed();
 }
 
 Ref<Texture> Theme::get_icon(const StringName &p_name, const StringName &p_node_type) const {
@@ -454,6 +453,10 @@ bool Theme::has_icon(const StringName &p_name, const StringName &p_node_type) co
 	return (icon_map.has(p_node_type) && icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid());
 }
 
+bool Theme::has_icon_nocheck(const StringName &p_name, const StringName &p_node_type) const {
+	return (icon_map.has(p_node_type) && icon_map[p_node_type].has(p_name));
+}
+
 void Theme::rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) {
 	ERR_FAIL_COND_MSG(!icon_map.has(p_node_type), "Cannot rename the icon '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist.");
 	ERR_FAIL_COND_MSG(icon_map[p_node_type].has(p_name), "Cannot rename the icon '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
@@ -462,8 +465,7 @@ void Theme::rename_icon(const StringName &p_old_name, const StringName &p_name,
 	icon_map[p_node_type][p_name] = icon_map[p_node_type][p_old_name];
 	icon_map[p_node_type].erase(p_old_name);
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::clear_icon(const StringName &p_name, const StringName &p_node_type) {
@@ -476,8 +478,7 @@ void Theme::clear_icon(const StringName &p_name, const StringName &p_node_type)
 
 	icon_map[p_node_type].erase(p_name);
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::get_icon_list(StringName p_node_type, List<StringName> *p_list) const {
@@ -511,14 +512,9 @@ void Theme::get_icon_types(List<StringName> *p_list) const {
 }
 
 void Theme::set_shader(const StringName &p_name, const StringName &p_node_type, const Ref<Shader> &p_shader) {
-	bool new_value = !shader_map.has(p_node_type) || !shader_map[p_node_type].has(p_name);
-
 	shader_map[p_node_type][p_name] = p_shader;
 
-	if (new_value) {
-		_change_notify();
-		emit_changed();
-	}
+	_emit_theme_changed();
 }
 
 Ref<Shader> Theme::get_shader(const StringName &p_name, const StringName &p_node_type) const {
@@ -538,8 +534,8 @@ void Theme::clear_shader(const StringName &p_name, const StringName &p_node_type
 	ERR_FAIL_COND(!shader_map[p_node_type].has(p_name));
 
 	shader_map[p_node_type].erase(p_name);
-	_change_notify();
-	emit_changed();
+
+	_emit_theme_changed();
 }
 
 void Theme::get_shader_list(const StringName &p_node_type, List<StringName> *p_list) const {
@@ -557,8 +553,6 @@ void Theme::get_shader_list(const StringName &p_node_type, List<StringName> *p_l
 }
 
 void Theme::set_stylebox(const StringName &p_name, const StringName &p_node_type, const Ref<StyleBox> &p_style) {
-	bool new_value = !style_map.has(p_node_type) || !style_map[p_node_type].has(p_name);
-
 	if (style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid()) {
 		style_map[p_node_type][p_name]->disconnect("changed", this, "_emit_theme_changed");
 	}
@@ -569,10 +563,7 @@ void Theme::set_stylebox(const StringName &p_name, const StringName &p_node_type
 		style_map[p_node_type][p_name]->connect("changed", this, "_emit_theme_changed", varray(), CONNECT_REFERENCE_COUNTED);
 	}
 
-	if (new_value) {
-		_change_notify();
-	}
-	emit_changed();
+	_emit_theme_changed();
 }
 
 Ref<StyleBox> Theme::get_stylebox(const StringName &p_name, const StringName &p_node_type) const {
@@ -587,6 +578,10 @@ bool Theme::has_stylebox(const StringName &p_name, const StringName &p_node_type
 	return (style_map.has(p_node_type) && style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid());
 }
 
+bool Theme::has_stylebox_nocheck(const StringName &p_name, const StringName &p_node_type) const {
+	return (style_map.has(p_node_type) && style_map[p_node_type].has(p_name));
+}
+
 void Theme::rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) {
 	ERR_FAIL_COND_MSG(!style_map.has(p_node_type), "Cannot rename the stylebox '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist.");
 	ERR_FAIL_COND_MSG(style_map[p_node_type].has(p_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
@@ -595,8 +590,7 @@ void Theme::rename_stylebox(const StringName &p_old_name, const StringName &p_na
 	style_map[p_node_type][p_name] = style_map[p_node_type][p_old_name];
 	style_map[p_node_type].erase(p_old_name);
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::clear_stylebox(const StringName &p_name, const StringName &p_node_type) {
@@ -609,8 +603,7 @@ void Theme::clear_stylebox(const StringName &p_name, const StringName &p_node_ty
 
 	style_map[p_node_type].erase(p_name);
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::get_stylebox_list(StringName p_node_type, List<StringName> *p_list) const {
@@ -644,8 +637,6 @@ void Theme::get_stylebox_types(List<StringName> *p_list) const {
 }
 
 void Theme::set_font(const StringName &p_name, const StringName &p_node_type, const Ref<Font> &p_font) {
-	bool new_value = !font_map.has(p_node_type) || !font_map[p_node_type].has(p_name);
-
 	if (font_map[p_node_type][p_name].is_valid()) {
 		font_map[p_node_type][p_name]->disconnect("changed", this, "_emit_theme_changed");
 	}
@@ -656,10 +647,7 @@ void Theme::set_font(const StringName &p_name, const StringName &p_node_type, co
 		font_map[p_node_type][p_name]->connect("changed", this, "_emit_theme_changed", varray(), CONNECT_REFERENCE_COUNTED);
 	}
 
-	if (new_value) {
-		_change_notify();
-		emit_changed();
-	}
+	_emit_theme_changed();
 }
 
 Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_node_type) const {
@@ -676,6 +664,10 @@ bool Theme::has_font(const StringName &p_name, const StringName &p_node_type) co
 	return (font_map.has(p_node_type) && font_map[p_node_type].has(p_name) && font_map[p_node_type][p_name].is_valid());
 }
 
+bool Theme::has_font_nocheck(const StringName &p_name, const StringName &p_node_type) const {
+	return (font_map.has(p_node_type) && font_map[p_node_type].has(p_name));
+}
+
 void Theme::rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) {
 	ERR_FAIL_COND_MSG(!font_map.has(p_node_type), "Cannot rename the font '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist.");
 	ERR_FAIL_COND_MSG(font_map[p_node_type].has(p_name), "Cannot rename the font '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
@@ -684,8 +676,7 @@ void Theme::rename_font(const StringName &p_old_name, const StringName &p_name,
 	font_map[p_node_type][p_name] = font_map[p_node_type][p_old_name];
 	font_map[p_node_type].erase(p_old_name);
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::clear_font(const StringName &p_name, const StringName &p_node_type) {
@@ -697,8 +688,8 @@ void Theme::clear_font(const StringName &p_name, const StringName &p_node_type)
 	}
 
 	font_map[p_node_type].erase(p_name);
-	_change_notify();
-	emit_changed();
+
+	_emit_theme_changed();
 }
 
 void Theme::get_font_list(StringName p_node_type, List<StringName> *p_list) const {
@@ -732,14 +723,9 @@ void Theme::get_font_types(List<StringName> *p_list) const {
 }
 
 void Theme::set_color(const StringName &p_name, const StringName &p_node_type, const Color &p_color) {
-	bool new_value = !color_map.has(p_node_type) || !color_map[p_node_type].has(p_name);
-
 	color_map[p_node_type][p_name] = p_color;
 
-	if (new_value) {
-		_change_notify();
-		emit_changed();
-	}
+	_emit_theme_changed();
 }
 
 Color Theme::get_color(const StringName &p_name, const StringName &p_node_type) const {
@@ -754,6 +740,10 @@ bool Theme::has_color(const StringName &p_name, const StringName &p_node_type) c
 	return (color_map.has(p_node_type) && color_map[p_node_type].has(p_name));
 }
 
+bool Theme::has_color_nocheck(const StringName &p_name, const StringName &p_node_type) const {
+	return (color_map.has(p_node_type) && color_map[p_node_type].has(p_name));
+}
+
 void Theme::rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) {
 	ERR_FAIL_COND_MSG(!color_map.has(p_node_type), "Cannot rename the color '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist.");
 	ERR_FAIL_COND_MSG(color_map[p_node_type].has(p_name), "Cannot rename the color '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
@@ -762,8 +752,7 @@ void Theme::rename_color(const StringName &p_old_name, const StringName &p_name,
 	color_map[p_node_type][p_name] = color_map[p_node_type][p_old_name];
 	color_map[p_node_type].erase(p_old_name);
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::clear_color(const StringName &p_name, const StringName &p_node_type) {
@@ -771,8 +760,8 @@ void Theme::clear_color(const StringName &p_name, const StringName &p_node_type)
 	ERR_FAIL_COND_MSG(!color_map[p_node_type].has(p_name), "Cannot clear the color '" + String(p_name) + "' because it does not exist.");
 
 	color_map[p_node_type].erase(p_name);
-	_change_notify();
-	emit_changed();
+
+	_emit_theme_changed();
 }
 
 void Theme::get_color_list(StringName p_node_type, List<StringName> *p_list) const {
@@ -806,13 +795,9 @@ void Theme::get_color_types(List<StringName> *p_list) const {
 }
 
 void Theme::set_constant(const StringName &p_name, const StringName &p_node_type, int p_constant) {
-	bool new_value = !constant_map.has(p_node_type) || !constant_map[p_node_type].has(p_name);
 	constant_map[p_node_type][p_name] = p_constant;
 
-	if (new_value) {
-		_change_notify();
-		emit_changed();
-	}
+	_emit_theme_changed();
 }
 
 int Theme::get_constant(const StringName &p_name, const StringName &p_node_type) const {
@@ -827,6 +812,10 @@ bool Theme::has_constant(const StringName &p_name, const StringName &p_node_type
 	return (constant_map.has(p_node_type) && constant_map[p_node_type].has(p_name));
 }
 
+bool Theme::has_constant_nocheck(const StringName &p_name, const StringName &p_node_type) const {
+	return (constant_map.has(p_node_type) && constant_map[p_node_type].has(p_name));
+}
+
 void Theme::rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) {
 	ERR_FAIL_COND_MSG(!constant_map.has(p_node_type), "Cannot rename the constant '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist.");
 	ERR_FAIL_COND_MSG(constant_map[p_node_type].has(p_name), "Cannot rename the constant '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
@@ -835,8 +824,7 @@ void Theme::rename_constant(const StringName &p_old_name, const StringName &p_na
 	constant_map[p_node_type][p_name] = constant_map[p_node_type][p_old_name];
 	constant_map[p_node_type].erase(p_old_name);
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::clear_constant(const StringName &p_name, const StringName &p_node_type) {
@@ -844,8 +832,8 @@ void Theme::clear_constant(const StringName &p_name, const StringName &p_node_ty
 	ERR_FAIL_COND_MSG(!constant_map[p_node_type].has(p_name), "Cannot clear the constant '" + String(p_name) + "' because it does not exist.");
 
 	constant_map[p_node_type].erase(p_name);
-	_change_notify();
-	emit_changed();
+
+	_emit_theme_changed();
 }
 
 void Theme::get_constant_list(StringName p_node_type, List<StringName> *p_list) const {
@@ -953,6 +941,25 @@ bool Theme::has_theme_item(DataType p_data_type, const StringName &p_name, const
 	return false;
 }
 
+bool Theme::has_theme_item_nocheck(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const {
+	switch (p_data_type) {
+		case DATA_TYPE_COLOR:
+			return has_color_nocheck(p_name, p_node_type);
+		case DATA_TYPE_CONSTANT:
+			return has_constant_nocheck(p_name, p_node_type);
+		case DATA_TYPE_FONT:
+			return has_font_nocheck(p_name, p_node_type);
+		case DATA_TYPE_ICON:
+			return has_icon_nocheck(p_name, p_node_type);
+		case DATA_TYPE_STYLEBOX:
+			return has_stylebox_nocheck(p_name, p_node_type);
+		case DATA_TYPE_MAX:
+			break; // Can't happen, but silences warning.
+	}
+
+	return false;
+}
+
 void Theme::rename_theme_item(DataType p_data_type, const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) {
 	switch (p_data_type) {
 		case DATA_TYPE_COLOR:
@@ -1063,6 +1070,15 @@ void Theme::get_theme_item_types(DataType p_data_type, List<StringName> *p_list)
 	}
 }
 
+void Theme::_freeze_change_propagation() {
+	no_change_propagation = true;
+}
+
+void Theme::_unfreeze_and_propagate_changes() {
+	no_change_propagation = false;
+	_emit_theme_changed();
+}
+
 void Theme::clear() {
 	//these need disconnecting
 	{
@@ -1111,8 +1127,7 @@ void Theme::clear() {
 	color_map.clear();
 	constant_map.clear();
 
-	_change_notify();
-	emit_changed();
+	_emit_theme_changed();
 }
 
 void Theme::copy_default_theme() {
@@ -1126,6 +1141,8 @@ void Theme::copy_theme(const Ref<Theme> &p_other) {
 		return;
 	}
 
+	_freeze_change_propagation();
+
 	// These items need reconnecting, so add them normally.
 	{
 		const StringName *K = nullptr;
@@ -1162,8 +1179,7 @@ void Theme::copy_theme(const Ref<Theme> &p_other) {
 	constant_map = p_other->constant_map;
 	shader_map = p_other->shader_map;
 
-	_change_notify();
-	emit_changed();
+	_unfreeze_and_propagate_changes();
 }
 
 void Theme::get_type_list(List<StringName> *p_list) const {

+ 16 - 0
scene/resources/theme.h

@@ -42,6 +42,11 @@ class Theme : public Resource {
 	GDCLASS(Theme, Resource);
 	RES_BASE_EXTENSION("theme");
 
+#ifdef TOOLS_ENABLED
+	friend class ThemeItemImportTree;
+	friend class ThemeItemEditorDialog;
+#endif
+
 public:
 	enum DataType {
 		DATA_TYPE_COLOR,
@@ -53,6 +58,8 @@ public:
 	};
 
 private:
+	bool no_change_propagation = false;
+
 	void _emit_theme_changed();
 
 	HashMap<StringName, HashMap<StringName, Ref<Texture>>> icon_map;
@@ -92,6 +99,9 @@ protected:
 
 	static void _bind_methods();
 
+	void _freeze_change_propagation();
+	void _unfreeze_and_propagate_changes();
+
 public:
 	static Ref<Theme> get_default();
 	static void set_default(const Ref<Theme> &p_default);
@@ -109,6 +119,7 @@ public:
 	void set_icon(const StringName &p_name, const StringName &p_node_type, const Ref<Texture> &p_icon);
 	Ref<Texture> get_icon(const StringName &p_name, const StringName &p_node_type) const;
 	bool has_icon(const StringName &p_name, const StringName &p_node_type) const;
+	bool has_icon_nocheck(const StringName &p_name, const StringName &p_node_type) const;
 	void rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type);
 	void clear_icon(const StringName &p_name, const StringName &p_node_type);
 	void get_icon_list(StringName p_node_type, List<StringName> *p_list) const;
@@ -124,6 +135,7 @@ public:
 	void set_stylebox(const StringName &p_name, const StringName &p_node_type, const Ref<StyleBox> &p_style);
 	Ref<StyleBox> get_stylebox(const StringName &p_name, const StringName &p_node_type) const;
 	bool has_stylebox(const StringName &p_name, const StringName &p_node_type) const;
+	bool has_stylebox_nocheck(const StringName &p_name, const StringName &p_node_type) const;
 	void rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type);
 	void clear_stylebox(const StringName &p_name, const StringName &p_node_type);
 	void get_stylebox_list(StringName p_node_type, List<StringName> *p_list) const;
@@ -133,6 +145,7 @@ public:
 	void set_font(const StringName &p_name, const StringName &p_node_type, const Ref<Font> &p_font);
 	Ref<Font> get_font(const StringName &p_name, const StringName &p_node_type) const;
 	bool has_font(const StringName &p_name, const StringName &p_node_type) const;
+	bool has_font_nocheck(const StringName &p_name, const StringName &p_node_type) const;
 	void rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type);
 	void clear_font(const StringName &p_name, const StringName &p_node_type);
 	void get_font_list(StringName p_node_type, List<StringName> *p_list) const;
@@ -142,6 +155,7 @@ public:
 	void set_color(const StringName &p_name, const StringName &p_node_type, const Color &p_color);
 	Color get_color(const StringName &p_name, const StringName &p_node_type) const;
 	bool has_color(const StringName &p_name, const StringName &p_node_type) const;
+	bool has_color_nocheck(const StringName &p_name, const StringName &p_node_type) const;
 	void rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type);
 	void clear_color(const StringName &p_name, const StringName &p_node_type);
 	void get_color_list(StringName p_node_type, List<StringName> *p_list) const;
@@ -151,6 +165,7 @@ public:
 	void set_constant(const StringName &p_name, const StringName &p_node_type, int p_constant);
 	int get_constant(const StringName &p_name, const StringName &p_node_type) const;
 	bool has_constant(const StringName &p_name, const StringName &p_node_type) const;
+	bool has_constant_nocheck(const StringName &p_name, const StringName &p_node_type) const;
 	void rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type);
 	void clear_constant(const StringName &p_name, const StringName &p_node_type);
 	void get_constant_list(StringName p_node_type, List<StringName> *p_list) const;
@@ -160,6 +175,7 @@ public:
 	void set_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type, const Variant &p_value);
 	Variant get_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const;
 	bool has_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const;
+	bool has_theme_item_nocheck(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const;
 	void rename_theme_item(DataType p_data_type, const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type);
 	void clear_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type);
 	void get_theme_item_list(DataType p_data_type, StringName p_node_type, List<StringName> *p_list) const;

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.