Bläddra i källkod

Editor: Add ability to rename theme types

Danil Alexeev 2 månader sedan
förälder
incheckning
e6c9a832c1

+ 9 - 0
doc/classes/Theme.xml

@@ -462,6 +462,15 @@
 				[b]Note:[/b] This method is analogous to calling the corresponding data type specific method, but can be used for more generalized logic.
 				[b]Note:[/b] This method is analogous to calling the corresponding data type specific method, but can be used for more generalized logic.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="rename_type">
+			<return type="void" />
+			<param index="0" name="old_theme_type" type="StringName" />
+			<param index="1" name="theme_type" type="StringName" />
+			<description>
+				Renames the theme type [param old_theme_type] to [param theme_type], if the old type exists and the new one doesn't exist.
+				[b]Note:[/b] Renaming a theme type to an empty name or a variation to a type associated with a built-in class removes type variation connections in a way that cannot be undone by reversing the rename alone.
+			</description>
+		</method>
 		<method name="set_color">
 		<method name="set_color">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="name" type="StringName" />
 			<param index="0" name="name" type="StringName" />

+ 177 - 26
editor/plugins/theme_editor_plugin.cpp

@@ -54,9 +54,30 @@
 #include "scene/gui/tab_bar.h"
 #include "scene/gui/tab_bar.h"
 #include "scene/gui/tab_container.h"
 #include "scene/gui/tab_container.h"
 #include "scene/gui/texture_rect.h"
 #include "scene/gui/texture_rect.h"
-#include "scene/resources/packed_scene.h"
 #include "scene/theme/theme_db.h"
 #include "scene/theme/theme_db.h"
 
 
+static void _rename_theme_type(EditorUndoRedoManager *p_ur, Theme *p_theme, const String &p_old_theme_type, const String &p_new_theme_type) {
+	p_ur->add_do_method(p_theme, "rename_type", p_old_theme_type, p_new_theme_type);
+	p_ur->add_undo_method(p_theme, "rename_type", p_new_theme_type, p_old_theme_type);
+
+	// Renaming a theme type to an empty name or a variation to a type associated with a built-in class
+	// removes type variation connections in a way that cannot be undone by reversing the rename alone.
+	const StringName old_base_type = p_theme->get_type_variation_base(p_old_theme_type);
+	if ((!p_old_theme_type.is_empty() && p_new_theme_type.is_empty()) || (old_base_type != StringName() && ClassDB::class_exists(p_new_theme_type))) {
+		if (old_base_type != StringName()) {
+			p_ur->add_undo_method(p_theme, "set_type_variation", p_old_theme_type, old_base_type);
+		}
+
+		List<StringName> names;
+		p_theme->get_type_variation_list(p_old_theme_type, &names);
+		for (const StringName &E : names) {
+			p_ur->add_undo_method(p_theme, "set_type_variation", E, p_old_theme_type);
+		}
+	}
+}
+
+///////////////////////
+
 void ThemeItemImportTree::_update_items_tree() {
 void ThemeItemImportTree::_update_items_tree() {
 	import_items_tree->clear();
 	import_items_tree->clear();
 	TreeItem *root = import_items_tree->create_item();
 	TreeItem *root = import_items_tree->create_item();
@@ -1243,8 +1264,10 @@ void ThemeItemEditorDialog::_update_edit_types() {
 		}
 		}
 		TreeItem *list_item = edit_type_list->create_item(list_root);
 		TreeItem *list_item = edit_type_list->create_item(list_root);
 		list_item->set_text(0, E);
 		list_item->set_text(0, E);
+		list_item->set_metadata(0, E);
+		list_item->set_editable(0, true);
 		list_item->set_icon(0, item_icon);
 		list_item->set_icon(0, item_icon);
-		list_item->add_button(0, get_editor_theme_icon(SNAME("Remove")), TYPES_TREE_REMOVE_ITEM, false, TTR("Remove Type"));
+		list_item->add_button(0, get_editor_theme_icon(SNAME("Remove")), TYPES_TREE_REMOVE_ITEM, false, TTRC("Remove Type"));
 
 
 		if (E == edited_item_type) {
 		if (E == edited_item_type) {
 			list_item->select(0);
 			list_item->select(0);
@@ -1306,6 +1329,46 @@ void ThemeItemEditorDialog::_edited_type_selected() {
 	_update_edit_item_tree(selected_type);
 	_update_edit_item_tree(selected_type);
 }
 }
 
 
+void ThemeItemEditorDialog::_edited_type_edited() {
+	TreeItem *edited_item = edit_type_list->get_selected();
+	const String old_type_name = edited_item->get_metadata(0);
+
+	String new_type_name = edited_item->get_text(0).strip_edges();
+	if (!new_type_name.is_empty()) { // The type name can be empty, unlike the item name.
+		new_type_name = new_type_name.validate_ascii_identifier();
+	}
+
+	if (old_type_name == new_type_name) {
+		edited_item->set_text(0, old_type_name);
+		return;
+	}
+
+	List<StringName> theme_types;
+	edited_theme->get_type_list(&theme_types);
+	if (theme_types.find(new_type_name) != nullptr) {
+		edited_item->set_text(0, old_type_name);
+		return;
+	}
+
+	// The list will be recreated, but let's update the item just in case.
+	edited_item->set_metadata(0, new_type_name);
+	edited_item->set_text(0, new_type_name);
+
+	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+	ur->create_action(TTR("Rename Theme Type"));
+
+	_rename_theme_type(ur, *edited_theme, old_type_name, new_type_name);
+
+	// Set `edited_item_type`.
+	ur->add_do_method(this, "_update_edit_item_tree", new_type_name);
+	ur->add_undo_method(this, "_update_edit_item_tree", old_type_name);
+
+	ur->add_do_method(this, "_update_edit_types");
+	ur->add_undo_method(this, "_update_edit_types");
+
+	ur->commit_action();
+}
+
 void ThemeItemEditorDialog::_edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
 void ThemeItemEditorDialog::_edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
 	if (p_button != MouseButton::LEFT) {
 	if (p_button != MouseButton::LEFT) {
 		return;
 		return;
@@ -1520,22 +1583,26 @@ void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_colu
 	}
 	}
 }
 }
 
 
-void ThemeItemEditorDialog::_add_theme_type(const String &p_new_text) {
-	const String new_type = edit_add_type_value->get_text().strip_edges();
+void ThemeItemEditorDialog::_add_theme_type() {
+	String new_type_name = edit_add_type_value->get_text().strip_edges();
+	if (!new_type_name.is_empty()) { // The type name can be empty, unlike the item name.
+		new_type_name = new_type_name.validate_ascii_identifier();
+	}
 	edit_add_type_value->clear();
 	edit_add_type_value->clear();
 
 
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 	ur->create_action(TTR("Add Theme Type"));
 	ur->create_action(TTR("Add Theme Type"));
 
 
-	ur->add_do_method(*edited_theme, "add_type", new_type);
-	ur->add_undo_method(*edited_theme, "remove_type", new_type);
+	ur->add_do_method(*edited_theme, "add_type", new_type_name);
+	ur->add_undo_method(*edited_theme, "remove_type", new_type_name);
+
 	ur->add_do_method(this, "_update_edit_types");
 	ur->add_do_method(this, "_update_edit_types");
 	ur->add_undo_method(this, "_update_edit_types");
 	ur->add_undo_method(this, "_update_edit_types");
 
 
 	ur->commit_action();
 	ur->commit_action();
 }
 }
 
 
-void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type) {
+void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, const String &p_item_name, const String &p_item_type) {
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 	ur->create_action(TTR("Create Theme Item"));
 	ur->create_action(TTR("Create Theme Item"));
 
 
@@ -1617,8 +1684,8 @@ void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type,
 	ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
 	ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
 	ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
 	ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
 
 
-	ur->add_do_method(theme_type_editor, "_update_edit_item_tree", edited_item_type);
-	ur->add_undo_method(theme_type_editor, "_update_edit_item_tree", edited_item_type);
+	ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
+	ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
 
 
 	ur->commit_action();
 	ur->commit_action();
 }
 }
@@ -1802,14 +1869,16 @@ void ThemeItemEditorDialog::_open_rename_theme_item_dialog(Theme::DataType p_dat
 }
 }
 
 
 void ThemeItemEditorDialog::_confirm_edit_theme_item() {
 void ThemeItemEditorDialog::_confirm_edit_theme_item() {
+	const String new_item_name = theme_item_name->get_text().strip_edges().validate_ascii_identifier();
+
 	if (item_popup_mode == CREATE_THEME_ITEM) {
 	if (item_popup_mode == CREATE_THEME_ITEM) {
-		_add_theme_item(edit_item_data_type, theme_item_name->get_text(), edited_item_type);
+		_add_theme_item(edit_item_data_type, new_item_name, edited_item_type);
 	} else if (item_popup_mode == RENAME_THEME_ITEM) {
 	} else if (item_popup_mode == RENAME_THEME_ITEM) {
 		EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 		EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 		ur->create_action(TTR("Rename Theme Item"));
 		ur->create_action(TTR("Rename Theme Item"));
 
 
-		ur->add_do_method(*edited_theme, "rename_theme_item", edit_item_data_type, edit_item_old_name, theme_item_name->get_text(), edited_item_type);
-		ur->add_undo_method(*edited_theme, "rename_theme_item", edit_item_data_type, theme_item_name->get_text(), edit_item_old_name, edited_item_type);
+		ur->add_do_method(*edited_theme, "rename_theme_item", edit_item_data_type, edit_item_old_name, new_item_name, edited_item_type);
+		ur->add_undo_method(*edited_theme, "rename_theme_item", edit_item_data_type, new_item_name, edit_item_old_name, edited_item_type);
 
 
 		ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
 		ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
 		ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
 		ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
@@ -1927,6 +1996,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito
 	edit_type_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	edit_type_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	edit_dialog_side_vb->add_child(edit_type_list);
 	edit_dialog_side_vb->add_child(edit_type_list);
 	edit_type_list->connect(SceneStringName(item_selected), callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected));
 	edit_type_list->connect(SceneStringName(item_selected), callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected));
+	edit_type_list->connect("item_edited", callable_mp(this, &ThemeItemEditorDialog::_edited_type_edited));
 	edit_type_list->connect("button_clicked", callable_mp(this, &ThemeItemEditorDialog::_edited_type_button_pressed));
 	edit_type_list->connect("button_clicked", callable_mp(this, &ThemeItemEditorDialog::_edited_type_button_pressed));
 	edit_type_list->set_theme_type_variation("TreeSecondary");
 	edit_type_list->set_theme_type_variation("TreeSecondary");
 
 
@@ -1938,11 +2008,11 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito
 	edit_dialog_side_vb->add_child(edit_add_type_hb);
 	edit_dialog_side_vb->add_child(edit_add_type_hb);
 	edit_add_type_value = memnew(LineEdit);
 	edit_add_type_value = memnew(LineEdit);
 	edit_add_type_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	edit_add_type_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	edit_add_type_value->connect(SceneStringName(text_submitted), callable_mp(this, &ThemeItemEditorDialog::_add_theme_type));
+	edit_add_type_value->connect(SceneStringName(text_submitted), callable_mp(this, &ThemeItemEditorDialog::_add_theme_type).unbind(1));
 	edit_add_type_hb->add_child(edit_add_type_value);
 	edit_add_type_hb->add_child(edit_add_type_value);
 	edit_add_type_button = memnew(Button);
 	edit_add_type_button = memnew(Button);
 	edit_add_type_hb->add_child(edit_add_type_button);
 	edit_add_type_hb->add_child(edit_add_type_button);
-	edit_add_type_button->connect(SceneStringName(pressed), callable_mp(this, &ThemeItemEditorDialog::_add_theme_type).bind(""));
+	edit_add_type_button->connect(SceneStringName(pressed), callable_mp(this, &ThemeItemEditorDialog::_add_theme_type));
 
 
 	VBoxContainer *edit_items_vb = memnew(VBoxContainer);
 	VBoxContainer *edit_items_vb = memnew(VBoxContainer);
 	edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -2350,7 +2420,14 @@ void ThemeTypeEditor::_update_type_list() {
 
 
 	theme_type_list->clear();
 	theme_type_list->clear();
 
 
-	if (theme_types.size() > 0) {
+	if (theme_types.is_empty()) {
+		theme_type_list->set_disabled(true);
+		theme_type_list->add_item(TTRC("None"));
+		theme_type_list->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
+
+		edited_type = "";
+		_update_type_items();
+	} else {
 		theme_type_list->set_disabled(false);
 		theme_type_list->set_disabled(false);
 
 
 		bool item_reselected = false;
 		bool item_reselected = false;
@@ -2371,20 +2448,17 @@ void ThemeTypeEditor::_update_type_list() {
 			e_idx++;
 			e_idx++;
 		}
 		}
 
 
-		if (!item_reselected) {
+		if (item_reselected) {
+			_update_type_items();
+		} else {
 			theme_type_list->select(0);
 			theme_type_list->select(0);
 			_list_type_selected(0);
 			_list_type_selected(0);
-		} else {
-			_update_type_items();
 		}
 		}
-	} else {
-		theme_type_list->set_disabled(true);
-		theme_type_list->add_item(TTR("None"));
-
-		edited_type = "";
-		_update_type_items();
 	}
 	}
 
 
+	rename_type_button->set_disabled(theme_types.is_empty());
+	remove_type_button->set_disabled(theme_types.is_empty());
+
 	updating = false;
 	updating = false;
 }
 }
 
 
@@ -2818,6 +2892,54 @@ void ThemeTypeEditor::_add_type_button_cbk() {
 	add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE);
 	add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE);
 }
 }
 
 
+void ThemeTypeEditor::_rename_type_button_cbk() {
+	theme_type_rename_line_edit->set_text(edited_type);
+	theme_type_rename_dialog->reset_size();
+	theme_type_rename_dialog->popup_centered();
+	theme_type_rename_line_edit->grab_focus();
+}
+
+void ThemeTypeEditor::_theme_type_rename_dialog_confirmed() {
+	String new_type_name = theme_type_rename_line_edit->get_text().strip_edges();
+	if (!new_type_name.is_empty()) { // The type name can be empty, unlike the item name.
+		new_type_name = new_type_name.validate_ascii_identifier();
+	}
+
+	if (edited_type == new_type_name) {
+		return;
+	}
+
+	List<StringName> theme_types;
+	edited_theme->get_type_list(&theme_types);
+	if (theme_types.find(new_type_name) != nullptr) {
+		return;
+	}
+
+	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+	ur->create_action(TTR("Rename Theme Type"));
+
+	_rename_theme_type(ur, *edited_theme, edited_type, new_type_name);
+
+	ur->add_do_method(this, "select_type", new_type_name);
+	ur->add_undo_method(this, "select_type", edited_type);
+
+	ur->commit_action();
+}
+
+void ThemeTypeEditor::_remove_type_button_cbk() {
+	Ref<Theme> old_snapshot = edited_theme->duplicate();
+
+	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+	ur->create_action(TTR("Remove Theme Type"));
+
+	ur->add_do_method(*edited_theme, "remove_type", edited_type);
+	// If the type was empty, it cannot be restored with merge, but thankfully we can fake it.
+	ur->add_undo_method(*edited_theme, "add_type", edited_type);
+	ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
+
+	ur->commit_action();
+}
+
 void ThemeTypeEditor::_add_default_type_items() {
 void ThemeTypeEditor::_add_default_type_items() {
 	List<StringName> names;
 	List<StringName> names;
 	String default_type = edited_type;
 	String default_type = edited_type;
@@ -2911,7 +3033,8 @@ void ThemeTypeEditor::_item_add_cbk(int p_data_type, Control *p_control) {
 		return;
 		return;
 	}
 	}
 
 
-	String item_name = le->get_text().strip_edges();
+	const String item_name = le->get_text().strip_edges().validate_ascii_identifier();
+
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 	EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
 	ur->create_action(TTR("Add Theme Item"));
 	ur->create_action(TTR("Add Theme Item"));
 
 
@@ -3069,7 +3192,7 @@ void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name
 		return;
 		return;
 	}
 	}
 
 
-	String new_name = le->get_text().strip_edges();
+	const String new_name = le->get_text().strip_edges().validate_ascii_identifier();
 	if (new_name == p_item_name) {
 	if (new_name == p_item_name) {
 		_item_rename_canceled(p_data_type, p_item_name, p_control);
 		_item_rename_canceled(p_data_type, p_item_name, p_control);
 		return;
 		return;
@@ -3377,6 +3500,8 @@ void ThemeTypeEditor::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			add_type_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
 			add_type_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
+			rename_type_button->set_button_icon(get_editor_theme_icon(SNAME("Rename")));
+			remove_type_button->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
 
 
 			data_type_tabs->set_tab_icon(0, get_editor_theme_icon(SNAME("Color")));
 			data_type_tabs->set_tab_icon(0, get_editor_theme_icon(SNAME("Color")));
 			data_type_tabs->set_tab_icon(1, get_editor_theme_icon(SNAME("MemberConstant")));
 			data_type_tabs->set_tab_icon(1, get_editor_theme_icon(SNAME("MemberConstant")));
@@ -3396,6 +3521,7 @@ void ThemeTypeEditor::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_pin_leading_stylebox"), &ThemeTypeEditor::_pin_leading_stylebox);
 	ClassDB::bind_method(D_METHOD("_pin_leading_stylebox"), &ThemeTypeEditor::_pin_leading_stylebox);
 	ClassDB::bind_method(D_METHOD("_unpin_leading_stylebox"), &ThemeTypeEditor::_unpin_leading_stylebox);
 	ClassDB::bind_method(D_METHOD("_unpin_leading_stylebox"), &ThemeTypeEditor::_unpin_leading_stylebox);
 	ClassDB::bind_method(D_METHOD("_change_pinned_stylebox"), &ThemeTypeEditor::_change_pinned_stylebox);
 	ClassDB::bind_method(D_METHOD("_change_pinned_stylebox"), &ThemeTypeEditor::_change_pinned_stylebox);
+	ClassDB::bind_method(D_METHOD("select_type", "type_name"), &ThemeTypeEditor::select_type);
 }
 }
 
 
 void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) {
 void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) {
@@ -3468,6 +3594,31 @@ ThemeTypeEditor::ThemeTypeEditor() {
 	type_list_hb->add_child(add_type_button);
 	type_list_hb->add_child(add_type_button);
 	add_type_button->connect(SceneStringName(pressed), callable_mp(this, &ThemeTypeEditor::_add_type_button_cbk));
 	add_type_button->connect(SceneStringName(pressed), callable_mp(this, &ThemeTypeEditor::_add_type_button_cbk));
 
 
+	rename_type_button = memnew(Button);
+	rename_type_button->set_disabled(true);
+	rename_type_button->set_tooltip_text(TTRC("Rename current type."));
+	rename_type_button->set_accessibility_name(TTRC("Rename Current Type"));
+	type_list_hb->add_child(rename_type_button);
+	rename_type_button->connect(SceneStringName(pressed), callable_mp(this, &ThemeTypeEditor::_rename_type_button_cbk));
+
+	theme_type_rename_dialog = memnew(ConfirmationDialog);
+	theme_type_rename_dialog->set_title(TTRC("Rename Theme Type"));
+	theme_type_rename_dialog->set_min_size(Size2(256, 64) * EDSCALE);
+	add_child(theme_type_rename_dialog);
+	theme_type_rename_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ThemeTypeEditor::_theme_type_rename_dialog_confirmed));
+
+	theme_type_rename_line_edit = memnew(LineEdit);
+	theme_type_rename_line_edit->set_select_all_on_focus(true);
+	theme_type_rename_dialog->add_child(theme_type_rename_line_edit);
+	theme_type_rename_dialog->register_text_enter(theme_type_rename_line_edit);
+
+	remove_type_button = memnew(Button);
+	remove_type_button->set_disabled(true);
+	remove_type_button->set_tooltip_text(TTRC("Remove current type."));
+	remove_type_button->set_accessibility_name(TTRC("Remove Current Type"));
+	type_list_hb->add_child(remove_type_button);
+	remove_type_button->connect(SceneStringName(pressed), callable_mp(this, &ThemeTypeEditor::_remove_type_button_cbk));
+
 	HBoxContainer *type_controls = memnew(HBoxContainer);
 	HBoxContainer *type_controls = memnew(HBoxContainer);
 	main_vb->add_child(type_controls);
 	main_vb->add_child(type_controls);
 
 

+ 11 - 2
editor/plugins/theme_editor_plugin.h

@@ -42,6 +42,7 @@ class CheckButton;
 class EditorFileDialog;
 class EditorFileDialog;
 class ItemList;
 class ItemList;
 class Label;
 class Label;
+class LineEdit;
 class OptionButton;
 class OptionButton;
 class PanelContainer;
 class PanelContainer;
 class TabBar;
 class TabBar;
@@ -253,13 +254,14 @@ class ThemeItemEditorDialog : public AcceptDialog {
 	void _dialog_about_to_show();
 	void _dialog_about_to_show();
 	void _update_edit_types();
 	void _update_edit_types();
 	void _edited_type_selected();
 	void _edited_type_selected();
+	void _edited_type_edited();
 	void _edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
 	void _edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
 
 
 	void _update_edit_item_tree(String p_item_type);
 	void _update_edit_item_tree(String p_item_type);
 	void _item_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
 	void _item_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
 
 
-	void _add_theme_type(const String &p_new_text);
-	void _add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type);
+	void _add_theme_type();
+	void _add_theme_item(Theme::DataType p_data_type, const String &p_item_name, const String &p_item_type);
 	void _remove_theme_type(const String &p_theme_type);
 	void _remove_theme_type(const String &p_theme_type);
 	void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type);
 	void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type);
 	void _remove_class_items();
 	void _remove_class_items();
@@ -344,6 +346,10 @@ class ThemeTypeEditor : public MarginContainer {
 
 
 	OptionButton *theme_type_list = nullptr;
 	OptionButton *theme_type_list = nullptr;
 	Button *add_type_button = nullptr;
 	Button *add_type_button = nullptr;
+	Button *rename_type_button = nullptr;
+	ConfirmationDialog *theme_type_rename_dialog = nullptr;
+	LineEdit *theme_type_rename_line_edit = nullptr;
+	Button *remove_type_button = nullptr;
 
 
 	CheckButton *show_default_items_button = nullptr;
 	CheckButton *show_default_items_button = nullptr;
 
 
@@ -380,6 +386,9 @@ class ThemeTypeEditor : public MarginContainer {
 
 
 	void _list_type_selected(int p_index);
 	void _list_type_selected(int p_index);
 	void _add_type_button_cbk();
 	void _add_type_button_cbk();
+	void _rename_type_button_cbk();
+	void _theme_type_rename_dialog_confirmed();
+	void _remove_type_button_cbk();
 	void _add_default_type_items();
 	void _add_default_type_items();
 
 
 	void _update_add_button(const String &p_text, LineEdit *p_for_edit);
 	void _update_add_button(const String &p_text, LineEdit *p_for_edit);

+ 121 - 0
scene/resources/theme.cpp

@@ -361,6 +361,17 @@ void Theme::remove_icon_type(const StringName &p_theme_type) {
 	_unfreeze_and_propagate_changes();
 	_unfreeze_and_propagate_changes();
 }
 }
 
 
+void Theme::rename_icon_type(const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
+	if (!icon_map.has(p_old_theme_type) || icon_map.has(p_theme_type)) {
+		return;
+	}
+
+	icon_map[p_theme_type] = icon_map[p_old_theme_type];
+	icon_map.erase(p_old_theme_type);
+}
+
 void Theme::get_icon_type_list(List<StringName> *p_list) const {
 void Theme::get_icon_type_list(List<StringName> *p_list) const {
 	ERR_FAIL_NULL(p_list);
 	ERR_FAIL_NULL(p_list);
 
 
@@ -471,6 +482,17 @@ void Theme::remove_stylebox_type(const StringName &p_theme_type) {
 	_unfreeze_and_propagate_changes();
 	_unfreeze_and_propagate_changes();
 }
 }
 
 
+void Theme::rename_stylebox_type(const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
+	if (!style_map.has(p_old_theme_type) || style_map.has(p_theme_type)) {
+		return;
+	}
+
+	style_map[p_theme_type] = style_map[p_old_theme_type];
+	style_map.erase(p_old_theme_type);
+}
+
 void Theme::get_stylebox_type_list(List<StringName> *p_list) const {
 void Theme::get_stylebox_type_list(List<StringName> *p_list) const {
 	ERR_FAIL_NULL(p_list);
 	ERR_FAIL_NULL(p_list);
 
 
@@ -587,6 +609,17 @@ void Theme::remove_font_type(const StringName &p_theme_type) {
 	_unfreeze_and_propagate_changes();
 	_unfreeze_and_propagate_changes();
 }
 }
 
 
+void Theme::rename_font_type(const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
+	if (!font_map.has(p_old_theme_type) || font_map.has(p_theme_type)) {
+		return;
+	}
+
+	font_map[p_theme_type] = font_map[p_old_theme_type];
+	font_map.erase(p_old_theme_type);
+}
+
 void Theme::get_font_type_list(List<StringName> *p_list) const {
 void Theme::get_font_type_list(List<StringName> *p_list) const {
 	ERR_FAIL_NULL(p_list);
 	ERR_FAIL_NULL(p_list);
 
 
@@ -679,6 +712,17 @@ void Theme::remove_font_size_type(const StringName &p_theme_type) {
 	font_size_map.erase(p_theme_type);
 	font_size_map.erase(p_theme_type);
 }
 }
 
 
+void Theme::rename_font_size_type(const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
+	if (!font_size_map.has(p_old_theme_type) || font_size_map.has(p_theme_type)) {
+		return;
+	}
+
+	font_size_map[p_theme_type] = font_size_map[p_old_theme_type];
+	font_size_map.erase(p_old_theme_type);
+}
+
 void Theme::get_font_size_type_list(List<StringName> *p_list) const {
 void Theme::get_font_size_type_list(List<StringName> *p_list) const {
 	ERR_FAIL_NULL(p_list);
 	ERR_FAIL_NULL(p_list);
 
 
@@ -765,6 +809,17 @@ void Theme::remove_color_type(const StringName &p_theme_type) {
 	color_map.erase(p_theme_type);
 	color_map.erase(p_theme_type);
 }
 }
 
 
+void Theme::rename_color_type(const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
+	if (!color_map.has(p_old_theme_type) || color_map.has(p_theme_type)) {
+		return;
+	}
+
+	color_map[p_theme_type] = color_map[p_old_theme_type];
+	color_map.erase(p_old_theme_type);
+}
+
 void Theme::get_color_type_list(List<StringName> *p_list) const {
 void Theme::get_color_type_list(List<StringName> *p_list) const {
 	ERR_FAIL_NULL(p_list);
 	ERR_FAIL_NULL(p_list);
 
 
@@ -851,6 +906,17 @@ void Theme::remove_constant_type(const StringName &p_theme_type) {
 	constant_map.erase(p_theme_type);
 	constant_map.erase(p_theme_type);
 }
 }
 
 
+void Theme::rename_constant_type(const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
+	if (!constant_map.has(p_old_theme_type) || constant_map.has(p_theme_type)) {
+		return;
+	}
+
+	constant_map[p_theme_type] = constant_map[p_old_theme_type];
+	constant_map.erase(p_old_theme_type);
+}
+
 void Theme::get_constant_type_list(List<StringName> *p_list) const {
 void Theme::get_constant_type_list(List<StringName> *p_list) const {
 	ERR_FAIL_NULL(p_list);
 	ERR_FAIL_NULL(p_list);
 
 
@@ -1099,6 +1165,31 @@ void Theme::remove_theme_item_type(DataType p_data_type, const StringName &p_the
 	}
 	}
 }
 }
 
 
+void Theme::rename_theme_item_type(DataType p_data_type, const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	switch (p_data_type) {
+		case DATA_TYPE_COLOR:
+			rename_color_type(p_old_theme_type, p_theme_type);
+			break;
+		case DATA_TYPE_CONSTANT:
+			rename_constant_type(p_old_theme_type, p_theme_type);
+			break;
+		case DATA_TYPE_FONT:
+			rename_font_type(p_old_theme_type, p_theme_type);
+			break;
+		case DATA_TYPE_FONT_SIZE:
+			rename_font_size_type(p_old_theme_type, p_theme_type);
+			break;
+		case DATA_TYPE_ICON:
+			rename_icon_type(p_old_theme_type, p_theme_type);
+			break;
+		case DATA_TYPE_STYLEBOX:
+			rename_stylebox_type(p_old_theme_type, p_theme_type);
+			break;
+		case DATA_TYPE_MAX:
+			break; // Can't happen, but silences warning.
+	}
+}
+
 void Theme::get_theme_item_type_list(DataType p_data_type, List<StringName> *p_list) const {
 void Theme::get_theme_item_type_list(DataType p_data_type, List<StringName> *p_list) const {
 	switch (p_data_type) {
 	switch (p_data_type) {
 		case DATA_TYPE_COLOR:
 		case DATA_TYPE_COLOR:
@@ -1217,6 +1308,35 @@ void Theme::remove_type(const StringName &p_theme_type) {
 	_emit_theme_changed(true);
 	_emit_theme_changed(true);
 }
 }
 
 
+void Theme::rename_type(const StringName &p_old_theme_type, const StringName &p_theme_type) {
+	// Gracefully rename the record in every data type map.
+	for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) {
+		Theme::DataType dt = (Theme::DataType)i;
+		rename_theme_item_type(dt, p_old_theme_type, p_theme_type);
+	}
+
+	// If type is a variation, replace that connection.
+	const StringName base_type = get_type_variation_base(p_old_theme_type);
+	if (base_type != StringName()) {
+		clear_type_variation(p_old_theme_type);
+		if (p_theme_type != StringName() && !ClassDB::class_exists(p_theme_type)) {
+			set_type_variation(p_theme_type, base_type);
+		}
+	}
+
+	// If type is a variation base, replace all those connections.
+	List<StringName> names;
+	get_type_variation_list(p_old_theme_type, &names);
+	for (const StringName &E : names) {
+		clear_type_variation(E);
+		if (p_theme_type != StringName()) {
+			set_type_variation(E, p_theme_type);
+		}
+	}
+
+	_emit_theme_changed(true);
+}
+
 void Theme::get_type_list(List<StringName> *p_list) const {
 void Theme::get_type_list(List<StringName> *p_list) const {
 	ERR_FAIL_NULL(p_list);
 	ERR_FAIL_NULL(p_list);
 
 
@@ -1769,6 +1889,7 @@ void Theme::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("add_type", "theme_type"), &Theme::add_type);
 	ClassDB::bind_method(D_METHOD("add_type", "theme_type"), &Theme::add_type);
 	ClassDB::bind_method(D_METHOD("remove_type", "theme_type"), &Theme::remove_type);
 	ClassDB::bind_method(D_METHOD("remove_type", "theme_type"), &Theme::remove_type);
+	ClassDB::bind_method(D_METHOD("rename_type", "old_theme_type", "theme_type"), &Theme::rename_type);
 	ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list);
 	ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list);
 
 
 	ClassDB::bind_method(D_METHOD("merge_with", "other"), &Theme::merge_with);
 	ClassDB::bind_method(D_METHOD("merge_with", "other"), &Theme::merge_with);

+ 8 - 0
scene/resources/theme.h

@@ -138,6 +138,7 @@ public:
 	void get_icon_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void get_icon_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void add_icon_type(const StringName &p_theme_type);
 	void add_icon_type(const StringName &p_theme_type);
 	void remove_icon_type(const StringName &p_theme_type);
 	void remove_icon_type(const StringName &p_theme_type);
+	void rename_icon_type(const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_icon_type_list(List<StringName> *p_list) const;
 	void get_icon_type_list(List<StringName> *p_list) const;
 
 
 	void set_stylebox(const StringName &p_name, const StringName &p_theme_type, const Ref<StyleBox> &p_style);
 	void set_stylebox(const StringName &p_name, const StringName &p_theme_type, const Ref<StyleBox> &p_style);
@@ -149,6 +150,7 @@ public:
 	void get_stylebox_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void get_stylebox_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void add_stylebox_type(const StringName &p_theme_type);
 	void add_stylebox_type(const StringName &p_theme_type);
 	void remove_stylebox_type(const StringName &p_theme_type);
 	void remove_stylebox_type(const StringName &p_theme_type);
+	void rename_stylebox_type(const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_stylebox_type_list(List<StringName> *p_list) const;
 	void get_stylebox_type_list(List<StringName> *p_list) const;
 
 
 	void set_font(const StringName &p_name, const StringName &p_theme_type, const Ref<Font> &p_font);
 	void set_font(const StringName &p_name, const StringName &p_theme_type, const Ref<Font> &p_font);
@@ -161,6 +163,7 @@ public:
 	void get_font_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void get_font_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void add_font_type(const StringName &p_theme_type);
 	void add_font_type(const StringName &p_theme_type);
 	void remove_font_type(const StringName &p_theme_type);
 	void remove_font_type(const StringName &p_theme_type);
+	void rename_font_type(const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_font_type_list(List<StringName> *p_list) const;
 	void get_font_type_list(List<StringName> *p_list) const;
 
 
 	void set_font_size(const StringName &p_name, const StringName &p_theme_type, int p_font_size);
 	void set_font_size(const StringName &p_name, const StringName &p_theme_type, int p_font_size);
@@ -173,6 +176,7 @@ public:
 	void get_font_size_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void get_font_size_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void add_font_size_type(const StringName &p_theme_type);
 	void add_font_size_type(const StringName &p_theme_type);
 	void remove_font_size_type(const StringName &p_theme_type);
 	void remove_font_size_type(const StringName &p_theme_type);
+	void rename_font_size_type(const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_font_size_type_list(List<StringName> *p_list) const;
 	void get_font_size_type_list(List<StringName> *p_list) const;
 
 
 	void set_color(const StringName &p_name, const StringName &p_theme_type, const Color &p_color);
 	void set_color(const StringName &p_name, const StringName &p_theme_type, const Color &p_color);
@@ -184,6 +188,7 @@ public:
 	void get_color_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void get_color_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void add_color_type(const StringName &p_theme_type);
 	void add_color_type(const StringName &p_theme_type);
 	void remove_color_type(const StringName &p_theme_type);
 	void remove_color_type(const StringName &p_theme_type);
+	void rename_color_type(const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_color_type_list(List<StringName> *p_list) const;
 	void get_color_type_list(List<StringName> *p_list) const;
 
 
 	void set_constant(const StringName &p_name, const StringName &p_theme_type, int p_constant);
 	void set_constant(const StringName &p_name, const StringName &p_theme_type, int p_constant);
@@ -195,6 +200,7 @@ public:
 	void get_constant_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void get_constant_list(const StringName &p_theme_type, List<StringName> *p_list) const;
 	void add_constant_type(const StringName &p_theme_type);
 	void add_constant_type(const StringName &p_theme_type);
 	void remove_constant_type(const StringName &p_theme_type);
 	void remove_constant_type(const StringName &p_theme_type);
+	void rename_constant_type(const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_constant_type_list(List<StringName> *p_list) const;
 	void get_constant_type_list(List<StringName> *p_list) const;
 
 
 	void set_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type, const Variant &p_value);
 	void set_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type, const Variant &p_value);
@@ -206,6 +212,7 @@ public:
 	void get_theme_item_list(DataType p_data_type, const StringName &p_theme_type, List<StringName> *p_list) const;
 	void get_theme_item_list(DataType p_data_type, const StringName &p_theme_type, List<StringName> *p_list) const;
 	void add_theme_item_type(DataType p_data_type, const StringName &p_theme_type);
 	void add_theme_item_type(DataType p_data_type, const StringName &p_theme_type);
 	void remove_theme_item_type(DataType p_data_type, const StringName &p_theme_type);
 	void remove_theme_item_type(DataType p_data_type, const StringName &p_theme_type);
+	void rename_theme_item_type(DataType p_data_type, const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_theme_item_type_list(DataType p_data_type, List<StringName> *p_list) const;
 	void get_theme_item_type_list(DataType p_data_type, List<StringName> *p_list) const;
 
 
 	void set_type_variation(const StringName &p_theme_type, const StringName &p_base_type);
 	void set_type_variation(const StringName &p_theme_type, const StringName &p_base_type);
@@ -216,6 +223,7 @@ public:
 
 
 	void add_type(const StringName &p_theme_type);
 	void add_type(const StringName &p_theme_type);
 	void remove_type(const StringName &p_theme_type);
 	void remove_type(const StringName &p_theme_type);
+	void rename_type(const StringName &p_old_theme_type, const StringName &p_theme_type);
 	void get_type_list(List<StringName> *p_list) const;
 	void get_type_list(List<StringName> *p_list) const;
 	void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, Vector<StringName> &r_result);
 	void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, Vector<StringName> &r_result);