1/**************************************************************************/
2/* theme_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "theme_editor_plugin.h"
32
33#include "core/os/keyboard.h"
34#include "editor/editor_node.h"
35#include "editor/editor_resource_picker.h"
36#include "editor/editor_scale.h"
37#include "editor/editor_string_names.h"
38#include "editor/editor_undo_redo_manager.h"
39#include "editor/gui/editor_file_dialog.h"
40#include "editor/progress_dialog.h"
41#include "scene/gui/check_button.h"
42#include "scene/gui/color_picker.h"
43#include "scene/gui/item_list.h"
44#include "scene/gui/option_button.h"
45#include "scene/gui/panel_container.h"
46#include "scene/gui/scroll_container.h"
47#include "scene/gui/split_container.h"
48#include "scene/gui/tab_bar.h"
49#include "scene/gui/tab_container.h"
50#include "scene/gui/texture_rect.h"
51#include "scene/theme/theme_db.h"
52
53void ThemeItemImportTree::_update_items_tree() {
54 import_items_tree->clear();
55 TreeItem *root = import_items_tree->create_item();
56
57 if (base_theme.is_null()) {
58 return;
59 }
60
61 String filter_text = import_items_filter->get_text();
62
63 List<StringName> types;
64 List<StringName> names;
65 List<StringName> filtered_names;
66 base_theme->get_type_list(&types);
67 types.sort_custom<StringName::AlphCompare>();
68
69 int color_amount = 0;
70 int constant_amount = 0;
71 int font_amount = 0;
72 int font_size_amount = 0;
73 int icon_amount = 0;
74 int stylebox_amount = 0;
75
76 tree_color_items.clear();
77 tree_constant_items.clear();
78 tree_font_items.clear();
79 tree_font_size_items.clear();
80 tree_icon_items.clear();
81 tree_stylebox_items.clear();
82
83 for (const StringName &E : types) {
84 String type_name = (String)E;
85
86 Ref<Texture2D> type_icon;
87 if (E == "") {
88 type_icon = get_editor_theme_icon(SNAME("NodeDisabled"));
89 } else {
90 type_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled");
91 }
92
93 TreeItem *type_node = import_items_tree->create_item(root);
94 type_node->set_meta("_can_be_imported", false);
95 type_node->set_collapsed(true);
96 type_node->set_icon(0, type_icon);
97 type_node->set_text(0, type_name);
98 type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
99 type_node->set_checked(IMPORT_ITEM, false);
100 type_node->set_editable(IMPORT_ITEM, true);
101 type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
102 type_node->set_checked(IMPORT_ITEM_DATA, false);
103 type_node->set_editable(IMPORT_ITEM_DATA, true);
104
105 bool is_matching_filter = (filter_text.is_empty() || type_name.findn(filter_text) > -1);
106 bool has_filtered_items = false;
107
108 for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) {
109 Theme::DataType dt = (Theme::DataType)i;
110
111 names.clear();
112 filtered_names.clear();
113 base_theme->get_theme_item_list(dt, E, &names);
114
115 bool data_type_has_filtered_items = false;
116
117 for (const StringName &F : names) {
118 String item_name = (String)F;
119 bool is_item_matching_filter = (item_name.findn(filter_text) > -1);
120 if (!filter_text.is_empty() && !is_matching_filter && !is_item_matching_filter) {
121 continue;
122 }
123
124 // Only mark this if actual items match the filter and not just the type group.
125 if (!filter_text.is_empty() && is_item_matching_filter) {
126 has_filtered_items = true;
127 data_type_has_filtered_items = true;
128 }
129 filtered_names.push_back(F);
130 }
131
132 if (filtered_names.size() == 0) {
133 continue;
134 }
135
136 TreeItem *data_type_node = import_items_tree->create_item(type_node);
137 data_type_node->set_meta("_can_be_imported", false);
138 data_type_node->set_metadata(0, i);
139 data_type_node->set_collapsed(!data_type_has_filtered_items);
140 data_type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
141 data_type_node->set_checked(IMPORT_ITEM, false);
142 data_type_node->set_editable(IMPORT_ITEM, true);
143 data_type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
144 data_type_node->set_checked(IMPORT_ITEM_DATA, false);
145 data_type_node->set_editable(IMPORT_ITEM_DATA, true);
146
147 List<TreeItem *> *item_list = nullptr;
148
149 switch (dt) {
150 case Theme::DATA_TYPE_COLOR:
151 data_type_node->set_icon(0, get_editor_theme_icon(SNAME("Color")));
152 data_type_node->set_text(0, TTR("Colors"));
153
154 item_list = &tree_color_items;
155 color_amount += filtered_names.size();
156 break;
157
158 case Theme::DATA_TYPE_CONSTANT:
159 data_type_node->set_icon(0, get_editor_theme_icon(SNAME("MemberConstant")));
160 data_type_node->set_text(0, TTR("Constants"));
161
162 item_list = &tree_constant_items;
163 constant_amount += filtered_names.size();
164 break;
165
166 case Theme::DATA_TYPE_FONT:
167 data_type_node->set_icon(0, get_editor_theme_icon(SNAME("Font")));
168 data_type_node->set_text(0, TTR("Fonts"));
169
170 item_list = &tree_font_items;
171 font_amount += filtered_names.size();
172 break;
173
174 case Theme::DATA_TYPE_FONT_SIZE:
175 data_type_node->set_icon(0, get_editor_theme_icon(SNAME("FontSize")));
176 data_type_node->set_text(0, TTR("Font Sizes"));
177
178 item_list = &tree_font_size_items;
179 font_size_amount += filtered_names.size();
180 break;
181
182 case Theme::DATA_TYPE_ICON:
183 data_type_node->set_icon(0, get_editor_theme_icon(SNAME("ImageTexture")));
184 data_type_node->set_text(0, TTR("Icons"));
185
186 item_list = &tree_icon_items;
187 icon_amount += filtered_names.size();
188 break;
189
190 case Theme::DATA_TYPE_STYLEBOX:
191 data_type_node->set_icon(0, get_editor_theme_icon(SNAME("StyleBoxFlat")));
192 data_type_node->set_text(0, TTR("Styleboxes"));
193
194 item_list = &tree_stylebox_items;
195 stylebox_amount += filtered_names.size();
196 break;
197
198 case Theme::DATA_TYPE_MAX:
199 break; // Can't happen, but silences warning.
200 }
201
202 filtered_names.sort_custom<StringName::AlphCompare>();
203 for (const StringName &F : filtered_names) {
204 TreeItem *item_node = import_items_tree->create_item(data_type_node);
205 item_node->set_meta("_can_be_imported", true);
206 item_node->set_text(0, F);
207 item_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
208 item_node->set_checked(IMPORT_ITEM, false);
209 item_node->set_editable(IMPORT_ITEM, true);
210 item_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
211 item_node->set_checked(IMPORT_ITEM_DATA, false);
212 item_node->set_editable(IMPORT_ITEM_DATA, true);
213
214 _restore_selected_item(item_node);
215 item_node->propagate_check(IMPORT_ITEM, false);
216 item_node->propagate_check(IMPORT_ITEM_DATA, false);
217
218 item_list->push_back(item_node);
219 }
220 }
221
222 // Remove the item if it doesn't match the filter in any way.
223 if (!is_matching_filter && !has_filtered_items) {
224 root->remove_child(type_node);
225 memdelete(type_node);
226 continue;
227 }
228
229 // Show one level inside of a type group if there are matches in items.
230 if (!filter_text.is_empty() && has_filtered_items) {
231 type_node->set_collapsed(false);
232 }
233 }
234
235 if (color_amount > 0) {
236 Array arr;
237 arr.push_back(color_amount);
238 select_colors_label->set_text(TTRN("1 color", "{num} colors", color_amount).format(arr, "{num}"));
239 select_all_colors_button->set_visible(true);
240 select_full_colors_button->set_visible(true);
241 deselect_all_colors_button->set_visible(true);
242 } else {
243 select_colors_label->set_text(TTR("No colors found."));
244 select_all_colors_button->set_visible(false);
245 select_full_colors_button->set_visible(false);
246 deselect_all_colors_button->set_visible(false);
247 }
248
249 if (constant_amount > 0) {
250 Array arr;
251 arr.push_back(constant_amount);
252 select_constants_label->set_text(TTRN("1 constant", "{num} constants", constant_amount).format(arr, "{num}"));
253 select_all_constants_button->set_visible(true);
254 select_full_constants_button->set_visible(true);
255 deselect_all_constants_button->set_visible(true);
256 } else {
257 select_constants_label->set_text(TTR("No constants found."));
258 select_all_constants_button->set_visible(false);
259 select_full_constants_button->set_visible(false);
260 deselect_all_constants_button->set_visible(false);
261 }
262
263 if (font_amount > 0) {
264 Array arr;
265 arr.push_back(font_amount);
266 select_fonts_label->set_text(TTRN("1 font", "{num} fonts", font_amount).format(arr, "{num}"));
267 select_all_fonts_button->set_visible(true);
268 select_full_fonts_button->set_visible(true);
269 deselect_all_fonts_button->set_visible(true);
270 } else {
271 select_fonts_label->set_text(TTR("No fonts found."));
272 select_all_fonts_button->set_visible(false);
273 select_full_fonts_button->set_visible(false);
274 deselect_all_fonts_button->set_visible(false);
275 }
276
277 if (font_size_amount > 0) {
278 Array arr;
279 arr.push_back(font_size_amount);
280 select_font_sizes_label->set_text(TTRN("1 font size", "{num} font sizes", font_size_amount).format(arr, "{num}"));
281 select_all_font_sizes_button->set_visible(true);
282 select_full_font_sizes_button->set_visible(true);
283 deselect_all_font_sizes_button->set_visible(true);
284 } else {
285 select_font_sizes_label->set_text(TTR("No font sizes found."));
286 select_all_font_sizes_button->set_visible(false);
287 select_full_font_sizes_button->set_visible(false);
288 deselect_all_font_sizes_button->set_visible(false);
289 }
290
291 if (icon_amount > 0) {
292 Array arr;
293 arr.push_back(icon_amount);
294 select_icons_label->set_text(TTRN("1 icon", "{num} icons", icon_amount).format(arr, "{num}"));
295 select_all_icons_button->set_visible(true);
296 select_full_icons_button->set_visible(true);
297 deselect_all_icons_button->set_visible(true);
298 select_icons_warning_hb->set_visible(true);
299 } else {
300 select_icons_label->set_text(TTR("No icons found."));
301 select_all_icons_button->set_visible(false);
302 select_full_icons_button->set_visible(false);
303 deselect_all_icons_button->set_visible(false);
304 select_icons_warning_hb->set_visible(false);
305 }
306
307 if (stylebox_amount > 0) {
308 Array arr;
309 arr.push_back(stylebox_amount);
310 select_styleboxes_label->set_text(TTRN("1 stylebox", "{num} styleboxes", stylebox_amount).format(arr, "{num}"));
311 select_all_styleboxes_button->set_visible(true);
312 select_full_styleboxes_button->set_visible(true);
313 deselect_all_styleboxes_button->set_visible(true);
314 } else {
315 select_styleboxes_label->set_text(TTR("No styleboxes found."));
316 select_all_styleboxes_button->set_visible(false);
317 select_full_styleboxes_button->set_visible(false);
318 deselect_all_styleboxes_button->set_visible(false);
319 }
320}
321
322void ThemeItemImportTree::_toggle_type_items(bool p_collapse) {
323 TreeItem *root = import_items_tree->get_root();
324 if (!root) {
325 return;
326 }
327
328 TreeItem *type_node = root->get_first_child();
329 while (type_node) {
330 type_node->set_collapsed(p_collapse);
331 type_node = type_node->get_next();
332 }
333}
334
335void ThemeItemImportTree::_filter_text_changed(const String &p_value) {
336 _update_items_tree();
337}
338
339void ThemeItemImportTree::_store_selected_item(TreeItem *p_tree_item) {
340 if (!p_tree_item->get_meta("_can_be_imported")) {
341 return;
342 }
343
344 TreeItem *data_type_node = p_tree_item->get_parent();
345 if (!data_type_node || data_type_node == import_items_tree->get_root()) {
346 return;
347 }
348
349 TreeItem *type_node = data_type_node->get_parent();
350 if (!type_node || type_node == import_items_tree->get_root()) {
351 return;
352 }
353
354 ThemeItem ti;
355 ti.item_name = p_tree_item->get_text(0);
356 ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0);
357 ti.type_name = type_node->get_text(0);
358
359 bool import = p_tree_item->is_checked(IMPORT_ITEM);
360 bool with_data = p_tree_item->is_checked(IMPORT_ITEM_DATA);
361
362 if (import && with_data) {
363 selected_items[ti] = SELECT_IMPORT_FULL;
364 } else if (import) {
365 selected_items[ti] = SELECT_IMPORT_DEFINITION;
366 } else {
367 selected_items.erase(ti);
368 }
369
370 _update_total_selected(ti.data_type);
371}
372
373void ThemeItemImportTree::_restore_selected_item(TreeItem *p_tree_item) {
374 if (!p_tree_item->get_meta("_can_be_imported")) {
375 return;
376 }
377
378 TreeItem *data_type_node = p_tree_item->get_parent();
379 if (!data_type_node || data_type_node == import_items_tree->get_root()) {
380 return;
381 }
382
383 TreeItem *type_node = data_type_node->get_parent();
384 if (!type_node || type_node == import_items_tree->get_root()) {
385 return;
386 }
387
388 ThemeItem ti;
389 ti.item_name = p_tree_item->get_text(0);
390 ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0);
391 ti.type_name = type_node->get_text(0);
392
393 if (!selected_items.has(ti)) {
394 p_tree_item->set_checked(IMPORT_ITEM, false);
395 p_tree_item->set_checked(IMPORT_ITEM_DATA, false);
396 return;
397 }
398
399 if (selected_items[ti] == SELECT_IMPORT_FULL) {
400 p_tree_item->set_checked(IMPORT_ITEM, true);
401 p_tree_item->set_checked(IMPORT_ITEM_DATA, true);
402 } else if (selected_items[ti] == SELECT_IMPORT_DEFINITION) {
403 p_tree_item->set_checked(IMPORT_ITEM, true);
404 p_tree_item->set_checked(IMPORT_ITEM_DATA, false);
405 }
406}
407
408void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) {
409 ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
410
411 Label *total_selected_items_label = nullptr;
412 switch (p_data_type) {
413 case Theme::DATA_TYPE_COLOR:
414 total_selected_items_label = total_selected_colors_label;
415 break;
416
417 case Theme::DATA_TYPE_CONSTANT:
418 total_selected_items_label = total_selected_constants_label;
419 break;
420
421 case Theme::DATA_TYPE_FONT:
422 total_selected_items_label = total_selected_fonts_label;
423 break;
424
425 case Theme::DATA_TYPE_FONT_SIZE:
426 total_selected_items_label = total_selected_font_sizes_label;
427 break;
428
429 case Theme::DATA_TYPE_ICON:
430 total_selected_items_label = total_selected_icons_label;
431 break;
432
433 case Theme::DATA_TYPE_STYLEBOX:
434 total_selected_items_label = total_selected_styleboxes_label;
435 break;
436
437 case Theme::DATA_TYPE_MAX:
438 return; // Can't happen, but silences warning.
439 }
440
441 if (!total_selected_items_label) {
442 return;
443 }
444
445 int count = 0;
446 for (const KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) {
447 ThemeItem ti = E.key;
448 if (ti.data_type == p_data_type) {
449 count++;
450 }
451 }
452
453 if (count == 0) {
454 total_selected_items_label->hide();
455 } else {
456 Array arr;
457 arr.push_back(count);
458 total_selected_items_label->set_text(TTRN("{num} currently selected", "{num} currently selected", count).format(arr, "{num}"));
459 total_selected_items_label->show();
460 }
461}
462
463void ThemeItemImportTree::_tree_item_edited() {
464 if (updating_tree) {
465 return;
466 }
467
468 TreeItem *edited_item = import_items_tree->get_edited();
469 if (!edited_item) {
470 return;
471 }
472
473 updating_tree = true;
474
475 int edited_column = import_items_tree->get_edited_column();
476 bool is_checked = edited_item->is_checked(edited_column);
477 if (is_checked) {
478 if (edited_column == IMPORT_ITEM_DATA) {
479 edited_item->set_checked(IMPORT_ITEM, true);
480 edited_item->propagate_check(IMPORT_ITEM);
481 }
482 } else {
483 if (edited_column == IMPORT_ITEM) {
484 edited_item->set_checked(IMPORT_ITEM_DATA, false);
485 edited_item->propagate_check(IMPORT_ITEM_DATA);
486 }
487 }
488 edited_item->propagate_check(edited_column);
489 updating_tree = false;
490}
491
492void ThemeItemImportTree::_check_propagated_to_tree_item(Object *p_obj, int p_column) {
493 TreeItem *item = Object::cast_to<TreeItem>(p_obj);
494 // Skip "category" tree items by checking for children.
495 if (item && !item->get_first_child()) {
496 _store_selected_item(item);
497 }
498}
499
500void ThemeItemImportTree::_select_all_subitems(TreeItem *p_root_item, bool p_select_with_data) {
501 TreeItem *child_item = p_root_item->get_first_child();
502 while (child_item) {
503 child_item->set_checked(IMPORT_ITEM, true);
504 if (p_select_with_data) {
505 child_item->set_checked(IMPORT_ITEM_DATA, true);
506 }
507 _store_selected_item(child_item);
508
509 _select_all_subitems(child_item, p_select_with_data);
510 child_item = child_item->get_next();
511 }
512}
513
514void ThemeItemImportTree::_deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely) {
515 TreeItem *child_item = p_root_item->get_first_child();
516 while (child_item) {
517 child_item->set_checked(IMPORT_ITEM_DATA, false);
518 if (p_deselect_completely) {
519 child_item->set_checked(IMPORT_ITEM, false);
520 }
521 _store_selected_item(child_item);
522
523 _deselect_all_subitems(child_item, p_deselect_completely);
524 child_item = child_item->get_next();
525 }
526}
527
528void ThemeItemImportTree::_select_all_items_pressed() {
529 if (updating_tree) {
530 return;
531 }
532
533 updating_tree = true;
534
535 TreeItem *root = import_items_tree->get_root();
536 _select_all_subitems(root, false);
537
538 updating_tree = false;
539}
540
541void ThemeItemImportTree::_select_full_items_pressed() {
542 if (updating_tree) {
543 return;
544 }
545
546 updating_tree = true;
547
548 TreeItem *root = import_items_tree->get_root();
549 _select_all_subitems(root, true);
550
551 updating_tree = false;
552}
553
554void ThemeItemImportTree::_deselect_all_items_pressed() {
555 if (updating_tree) {
556 return;
557 }
558
559 updating_tree = true;
560
561 TreeItem *root = import_items_tree->get_root();
562 _deselect_all_subitems(root, true);
563
564 updating_tree = false;
565}
566
567void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) {
568 ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
569
570 if (updating_tree) {
571 return;
572 }
573
574 Theme::DataType data_type = (Theme::DataType)p_data_type;
575 List<TreeItem *> *item_list = nullptr;
576
577 switch (data_type) {
578 case Theme::DATA_TYPE_COLOR:
579 item_list = &tree_color_items;
580 break;
581
582 case Theme::DATA_TYPE_CONSTANT:
583 item_list = &tree_constant_items;
584 break;
585
586 case Theme::DATA_TYPE_FONT:
587 item_list = &tree_font_items;
588 break;
589
590 case Theme::DATA_TYPE_FONT_SIZE:
591 item_list = &tree_font_size_items;
592 break;
593
594 case Theme::DATA_TYPE_ICON:
595 item_list = &tree_icon_items;
596 break;
597
598 case Theme::DATA_TYPE_STYLEBOX:
599 item_list = &tree_stylebox_items;
600 break;
601
602 case Theme::DATA_TYPE_MAX:
603 return; // Can't happen, but silences warning.
604 }
605
606 updating_tree = true;
607
608 for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
609 TreeItem *child_item = E->get();
610 if (!child_item) {
611 continue;
612 }
613
614 child_item->set_checked(IMPORT_ITEM, true);
615 child_item->propagate_check(IMPORT_ITEM, false);
616 _store_selected_item(child_item);
617 }
618
619 updating_tree = false;
620}
621
622void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) {
623 ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
624
625 if (updating_tree) {
626 return;
627 }
628
629 Theme::DataType data_type = (Theme::DataType)p_data_type;
630 List<TreeItem *> *item_list = nullptr;
631
632 switch (data_type) {
633 case Theme::DATA_TYPE_COLOR:
634 item_list = &tree_color_items;
635 break;
636
637 case Theme::DATA_TYPE_CONSTANT:
638 item_list = &tree_constant_items;
639 break;
640
641 case Theme::DATA_TYPE_FONT:
642 item_list = &tree_font_items;
643 break;
644
645 case Theme::DATA_TYPE_FONT_SIZE:
646 item_list = &tree_font_size_items;
647 break;
648
649 case Theme::DATA_TYPE_ICON:
650 item_list = &tree_icon_items;
651 break;
652
653 case Theme::DATA_TYPE_STYLEBOX:
654 item_list = &tree_stylebox_items;
655 break;
656
657 case Theme::DATA_TYPE_MAX:
658 return; // Can't happen, but silences warning.
659 }
660
661 updating_tree = true;
662
663 for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
664 TreeItem *child_item = E->get();
665 if (!child_item) {
666 continue;
667 }
668
669 child_item->set_checked(IMPORT_ITEM, true);
670 child_item->set_checked(IMPORT_ITEM_DATA, true);
671 child_item->propagate_check(IMPORT_ITEM, false);
672 child_item->propagate_check(IMPORT_ITEM_DATA, false);
673 _store_selected_item(child_item);
674 }
675
676 updating_tree = false;
677}
678
679void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) {
680 ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
681
682 if (updating_tree) {
683 return;
684 }
685
686 Theme::DataType data_type = (Theme::DataType)p_data_type;
687 List<TreeItem *> *item_list = nullptr;
688
689 switch (data_type) {
690 case Theme::DATA_TYPE_COLOR:
691 item_list = &tree_color_items;
692 break;
693
694 case Theme::DATA_TYPE_CONSTANT:
695 item_list = &tree_constant_items;
696 break;
697
698 case Theme::DATA_TYPE_FONT:
699 item_list = &tree_font_items;
700 break;
701
702 case Theme::DATA_TYPE_FONT_SIZE:
703 item_list = &tree_font_size_items;
704 break;
705
706 case Theme::DATA_TYPE_ICON:
707 item_list = &tree_icon_items;
708 break;
709
710 case Theme::DATA_TYPE_STYLEBOX:
711 item_list = &tree_stylebox_items;
712 break;
713
714 case Theme::DATA_TYPE_MAX:
715 return; // Can't happen, but silences warning.
716 }
717
718 updating_tree = true;
719
720 for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
721 TreeItem *child_item = E->get();
722 if (!child_item) {
723 continue;
724 }
725
726 child_item->set_checked(IMPORT_ITEM, false);
727 child_item->set_checked(IMPORT_ITEM_DATA, false);
728 child_item->propagate_check(IMPORT_ITEM, false);
729 child_item->propagate_check(IMPORT_ITEM_DATA, false);
730 _store_selected_item(child_item);
731 }
732
733 updating_tree = false;
734}
735
736void ThemeItemImportTree::_import_selected() {
737 if (selected_items.size() == 0) {
738 EditorNode::get_singleton()->show_accept(TTR("Nothing was selected for the import."), TTR("OK"));
739 return;
740 }
741
742 Ref<Theme> old_snapshot = edited_theme->duplicate();
743 Ref<Theme> new_snapshot = edited_theme->duplicate();
744
745 ProgressDialog::get_singleton()->add_task("import_theme_items", TTR("Importing Theme Items"), selected_items.size() + 2);
746
747 int idx = 0;
748 for (KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) {
749 // Arbitrary number of items to skip from reporting.
750 // Reduces the number of UI updates that this causes when copying large themes.
751 if (idx % 10 == 0) {
752 Array arr;
753 arr.push_back(idx + 1);
754 arr.push_back(selected_items.size());
755 ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Importing items {n}/{n}").format(arr, "{n}"), idx);
756 }
757
758 ItemCheckedState cs = E.value;
759 ThemeItem ti = E.key;
760
761 if (cs == SELECT_IMPORT_DEFINITION || cs == SELECT_IMPORT_FULL) {
762 Variant item_value = Variant();
763
764 if (cs == SELECT_IMPORT_FULL) {
765 item_value = base_theme->get_theme_item(ti.data_type, ti.item_name, ti.type_name);
766 } else {
767 switch (ti.data_type) {
768 case Theme::DATA_TYPE_COLOR:
769 item_value = Color();
770 break;
771
772 case Theme::DATA_TYPE_CONSTANT:
773 item_value = 0;
774 break;
775
776 case Theme::DATA_TYPE_FONT:
777 item_value = Ref<Font>();
778 break;
779
780 case Theme::DATA_TYPE_FONT_SIZE:
781 item_value = -1;
782 break;
783
784 case Theme::DATA_TYPE_ICON:
785 item_value = Ref<Texture2D>();
786 break;
787
788 case Theme::DATA_TYPE_STYLEBOX:
789 item_value = Ref<StyleBox>();
790 break;
791
792 case Theme::DATA_TYPE_MAX:
793 break; // Can't happen, but silences warning.
794 }
795 }
796
797 new_snapshot->set_theme_item(ti.data_type, ti.item_name, ti.type_name, item_value);
798 }
799
800 idx++;
801 }
802
803 // Allow changes to be reported now that the operation is finished.
804 ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Updating the editor"), idx++);
805
806 // Make sure the task is not ended before the editor freezes to update the Inspector.
807 ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Finalizing"), idx++);
808
809 ProgressDialog::get_singleton()->end_task("import_theme_items");
810
811 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
812 ur->create_action(TTR("Import Theme Items"));
813
814 ur->add_do_method(*edited_theme, "clear");
815 ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
816 ur->add_undo_method(*edited_theme, "clear");
817 ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
818
819 ur->add_do_method(this, "emit_signal", SNAME("items_imported"));
820 ur->add_undo_method(this, "emit_signal", SNAME("items_imported"));
821
822 ur->commit_action();
823}
824
825void ThemeItemImportTree::set_edited_theme(const Ref<Theme> &p_theme) {
826 edited_theme = p_theme;
827}
828
829void ThemeItemImportTree::set_base_theme(const Ref<Theme> &p_theme) {
830 base_theme = p_theme;
831}
832
833void ThemeItemImportTree::reset_item_tree() {
834 import_items_filter->clear();
835 selected_items.clear();
836
837 total_selected_colors_label->hide();
838 total_selected_constants_label->hide();
839 total_selected_fonts_label->hide();
840 total_selected_font_sizes_label->hide();
841 total_selected_icons_label->hide();
842 total_selected_styleboxes_label->hide();
843
844 _update_items_tree();
845}
846
847bool ThemeItemImportTree::has_selected_items() const {
848 return (selected_items.size() > 0);
849}
850
851void ThemeItemImportTree::_notification(int p_what) {
852 switch (p_what) {
853 case NOTIFICATION_ENTER_TREE:
854 case NOTIFICATION_THEME_CHANGED: {
855 select_icons_warning_icon->set_texture(get_editor_theme_icon(SNAME("StatusWarning")));
856 select_icons_warning->add_theme_color_override("font_color", get_theme_color(SNAME("disabled_font_color"), EditorStringName(Editor)));
857
858 import_items_filter->set_right_icon(get_editor_theme_icon(SNAME("Search")));
859
860 // Bottom panel buttons.
861 import_collapse_types_button->set_icon(get_editor_theme_icon(SNAME("CollapseTree")));
862 import_expand_types_button->set_icon(get_editor_theme_icon(SNAME("ExpandTree")));
863
864 import_select_all_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectAll")));
865 import_select_full_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectFull")));
866 import_deselect_all_button->set_icon(get_editor_theme_icon(SNAME("ThemeDeselectAll")));
867
868 // Side panel buttons.
869 select_colors_icon->set_texture(get_editor_theme_icon(SNAME("Color")));
870 deselect_all_colors_button->set_icon(get_editor_theme_icon(SNAME("ThemeDeselectAll")));
871 select_all_colors_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectAll")));
872 select_full_colors_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectFull")));
873
874 select_constants_icon->set_texture(get_editor_theme_icon(SNAME("MemberConstant")));
875 deselect_all_constants_button->set_icon(get_editor_theme_icon(SNAME("ThemeDeselectAll")));
876 select_all_constants_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectAll")));
877 select_full_constants_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectFull")));
878
879 select_fonts_icon->set_texture(get_editor_theme_icon(SNAME("Font")));
880 deselect_all_fonts_button->set_icon(get_editor_theme_icon(SNAME("ThemeDeselectAll")));
881 select_all_fonts_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectAll")));
882 select_full_fonts_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectFull")));
883
884 select_font_sizes_icon->set_texture(get_editor_theme_icon(SNAME("FontSize")));
885 deselect_all_font_sizes_button->set_icon(get_editor_theme_icon(SNAME("ThemeDeselectAll")));
886 select_all_font_sizes_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectAll")));
887 select_full_font_sizes_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectFull")));
888
889 select_icons_icon->set_texture(get_editor_theme_icon(SNAME("ImageTexture")));
890 deselect_all_icons_button->set_icon(get_editor_theme_icon(SNAME("ThemeDeselectAll")));
891 select_all_icons_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectAll")));
892 select_full_icons_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectFull")));
893
894 select_styleboxes_icon->set_texture(get_editor_theme_icon(SNAME("StyleBoxFlat")));
895 deselect_all_styleboxes_button->set_icon(get_editor_theme_icon(SNAME("ThemeDeselectAll")));
896 select_all_styleboxes_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectAll")));
897 select_full_styleboxes_button->set_icon(get_editor_theme_icon(SNAME("ThemeSelectFull")));
898 } break;
899 }
900}
901
902void ThemeItemImportTree::_bind_methods() {
903 ADD_SIGNAL(MethodInfo("items_imported"));
904}
905
906ThemeItemImportTree::ThemeItemImportTree() {
907 import_items_filter = memnew(LineEdit);
908 import_items_filter->set_placeholder(TTR("Filter Items"));
909 import_items_filter->set_clear_button_enabled(true);
910 add_child(import_items_filter);
911 import_items_filter->connect("text_changed", callable_mp(this, &ThemeItemImportTree::_filter_text_changed));
912
913 HBoxContainer *import_main_hb = memnew(HBoxContainer);
914 import_main_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
915 add_child(import_main_hb);
916
917 import_items_tree = memnew(Tree);
918 import_items_tree->set_hide_root(true);
919 import_items_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL);
920 import_main_hb->add_child(import_items_tree);
921 import_items_tree->connect("item_edited", callable_mp(this, &ThemeItemImportTree::_tree_item_edited));
922 import_items_tree->connect("check_propagated_to_item", callable_mp(this, &ThemeItemImportTree::_check_propagated_to_tree_item));
923
924 import_items_tree->set_columns(3);
925 import_items_tree->set_column_titles_visible(true);
926 import_items_tree->set_column_title(IMPORT_ITEM, TTR("Import"));
927 import_items_tree->set_column_title(IMPORT_ITEM_DATA, TTR("With Data"));
928 import_items_tree->set_column_expand(0, true);
929 import_items_tree->set_column_clip_content(0, true);
930 import_items_tree->set_column_expand(IMPORT_ITEM, false);
931 import_items_tree->set_column_expand(IMPORT_ITEM_DATA, false);
932 import_items_tree->set_column_custom_minimum_width(0, 160 * EDSCALE);
933 import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM, 80 * EDSCALE);
934 import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM_DATA, 80 * EDSCALE);
935 import_items_tree->set_column_clip_content(1, true);
936 import_items_tree->set_column_clip_content(2, true);
937
938 ScrollContainer *import_bulk_sc = memnew(ScrollContainer);
939 import_bulk_sc->set_custom_minimum_size(Size2(260.0, 0.0) * EDSCALE);
940 import_bulk_sc->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
941 import_main_hb->add_child(import_bulk_sc);
942 VBoxContainer *import_bulk_vb = memnew(VBoxContainer);
943 import_bulk_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
944 import_bulk_sc->add_child(import_bulk_vb);
945
946 Label *import_bulk_label = memnew(Label);
947 import_bulk_label->set_text(TTR("Select by data type:"));
948 import_bulk_vb->add_child(import_bulk_label);
949
950 select_colors_icon = memnew(TextureRect);
951 select_colors_label = memnew(Label);
952 deselect_all_colors_button = memnew(Button);
953 select_all_colors_button = memnew(Button);
954 select_full_colors_button = memnew(Button);
955 total_selected_colors_label = memnew(Label);
956
957 select_constants_icon = memnew(TextureRect);
958 select_constants_label = memnew(Label);
959 deselect_all_constants_button = memnew(Button);
960 select_all_constants_button = memnew(Button);
961 select_full_constants_button = memnew(Button);
962 total_selected_constants_label = memnew(Label);
963
964 select_fonts_icon = memnew(TextureRect);
965 select_fonts_label = memnew(Label);
966 deselect_all_fonts_button = memnew(Button);
967 select_all_fonts_button = memnew(Button);
968 select_full_fonts_button = memnew(Button);
969 total_selected_fonts_label = memnew(Label);
970
971 select_font_sizes_icon = memnew(TextureRect);
972 select_font_sizes_label = memnew(Label);
973 deselect_all_font_sizes_button = memnew(Button);
974 select_all_font_sizes_button = memnew(Button);
975 select_full_font_sizes_button = memnew(Button);
976 total_selected_font_sizes_label = memnew(Label);
977
978 select_icons_icon = memnew(TextureRect);
979 select_icons_label = memnew(Label);
980 deselect_all_icons_button = memnew(Button);
981 select_all_icons_button = memnew(Button);
982 select_full_icons_button = memnew(Button);
983 total_selected_icons_label = memnew(Label);
984
985 select_styleboxes_icon = memnew(TextureRect);
986 select_styleboxes_label = memnew(Label);
987 deselect_all_styleboxes_button = memnew(Button);
988 select_all_styleboxes_button = memnew(Button);
989 select_full_styleboxes_button = memnew(Button);
990 total_selected_styleboxes_label = memnew(Label);
991
992 for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) {
993 Theme::DataType dt = (Theme::DataType)i;
994
995 TextureRect *select_items_icon = nullptr;
996 Label *select_items_label = nullptr;
997 Button *deselect_all_items_button = nullptr;
998 Button *select_all_items_button = nullptr;
999 Button *select_full_items_button = nullptr;
1000 Label *total_selected_items_label = nullptr;
1001
1002 String items_title;
1003 String select_all_items_tooltip;
1004 String select_full_items_tooltip;
1005 String deselect_all_items_tooltip;
1006
1007 switch (dt) {
1008 case Theme::DATA_TYPE_COLOR:
1009 select_items_icon = select_colors_icon;
1010 select_items_label = select_colors_label;
1011 deselect_all_items_button = deselect_all_colors_button;
1012 select_all_items_button = select_all_colors_button;
1013 select_full_items_button = select_full_colors_button;
1014 total_selected_items_label = total_selected_colors_label;
1015
1016 items_title = TTR("Colors");
1017 select_all_items_tooltip = TTR("Select all visible color items.");
1018 select_full_items_tooltip = TTR("Select all visible color items and their data.");
1019 deselect_all_items_tooltip = TTR("Deselect all visible color items.");
1020 break;
1021
1022 case Theme::DATA_TYPE_CONSTANT:
1023 select_items_icon = select_constants_icon;
1024 select_items_label = select_constants_label;
1025 deselect_all_items_button = deselect_all_constants_button;
1026 select_all_items_button = select_all_constants_button;
1027 select_full_items_button = select_full_constants_button;
1028 total_selected_items_label = total_selected_constants_label;
1029
1030 items_title = TTR("Constants");
1031 select_all_items_tooltip = TTR("Select all visible constant items.");
1032 select_full_items_tooltip = TTR("Select all visible constant items and their data.");
1033 deselect_all_items_tooltip = TTR("Deselect all visible constant items.");
1034 break;
1035
1036 case Theme::DATA_TYPE_FONT:
1037 select_items_icon = select_fonts_icon;
1038 select_items_label = select_fonts_label;
1039 deselect_all_items_button = deselect_all_fonts_button;
1040 select_all_items_button = select_all_fonts_button;
1041 select_full_items_button = select_full_fonts_button;
1042 total_selected_items_label = total_selected_fonts_label;
1043
1044 items_title = TTR("Fonts");
1045 select_all_items_tooltip = TTR("Select all visible font items.");
1046 select_full_items_tooltip = TTR("Select all visible font items and their data.");
1047 deselect_all_items_tooltip = TTR("Deselect all visible font items.");
1048 break;
1049
1050 case Theme::DATA_TYPE_FONT_SIZE:
1051 select_items_icon = select_font_sizes_icon;
1052 select_items_label = select_font_sizes_label;
1053 deselect_all_items_button = deselect_all_font_sizes_button;
1054 select_all_items_button = select_all_font_sizes_button;
1055 select_full_items_button = select_full_font_sizes_button;
1056 total_selected_items_label = total_selected_font_sizes_label;
1057
1058 items_title = TTR("Font sizes");
1059 select_all_items_tooltip = TTR("Select all visible font size items.");
1060 select_full_items_tooltip = TTR("Select all visible font size items and their data.");
1061 deselect_all_items_tooltip = TTR("Deselect all visible font size items.");
1062 break;
1063
1064 case Theme::DATA_TYPE_ICON:
1065 select_items_icon = select_icons_icon;
1066 select_items_label = select_icons_label;
1067 deselect_all_items_button = deselect_all_icons_button;
1068 select_all_items_button = select_all_icons_button;
1069 select_full_items_button = select_full_icons_button;
1070 total_selected_items_label = total_selected_icons_label;
1071
1072 items_title = TTR("Icons");
1073 select_all_items_tooltip = TTR("Select all visible icon items.");
1074 select_full_items_tooltip = TTR("Select all visible icon items and their data.");
1075 deselect_all_items_tooltip = TTR("Deselect all visible icon items.");
1076 break;
1077
1078 case Theme::DATA_TYPE_STYLEBOX:
1079 select_items_icon = select_styleboxes_icon;
1080 select_items_label = select_styleboxes_label;
1081 deselect_all_items_button = deselect_all_styleboxes_button;
1082 select_all_items_button = select_all_styleboxes_button;
1083 select_full_items_button = select_full_styleboxes_button;
1084 total_selected_items_label = total_selected_styleboxes_label;
1085
1086 items_title = TTR("Styleboxes");
1087 select_all_items_tooltip = TTR("Select all visible stylebox items.");
1088 select_full_items_tooltip = TTR("Select all visible stylebox items and their data.");
1089 deselect_all_items_tooltip = TTR("Deselect all visible stylebox items.");
1090 break;
1091
1092 case Theme::DATA_TYPE_MAX:
1093 continue; // Can't happen, but silences warning.
1094 }
1095
1096 if (i > 0) {
1097 import_bulk_vb->add_child(memnew(HSeparator));
1098 }
1099
1100 HBoxContainer *all_set = memnew(HBoxContainer);
1101 import_bulk_vb->add_child(all_set);
1102
1103 HBoxContainer *label_set = memnew(HBoxContainer);
1104 label_set->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1105 all_set->add_child(label_set);
1106 select_items_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
1107 label_set->add_child(select_items_icon);
1108 select_items_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1109 select_items_label->set_clip_text(true);
1110 select_items_label->set_text(items_title);
1111 label_set->add_child(select_items_label);
1112
1113 HBoxContainer *button_set = memnew(HBoxContainer);
1114 button_set->set_alignment(BoxContainer::ALIGNMENT_END);
1115 all_set->add_child(button_set);
1116 select_all_items_button->set_flat(true);
1117 select_all_items_button->set_tooltip_text(select_all_items_tooltip);
1118 button_set->add_child(select_all_items_button);
1119 select_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_data_type_pressed).bind(i));
1120 select_full_items_button->set_flat(true);
1121 select_full_items_button->set_tooltip_text(select_full_items_tooltip);
1122 button_set->add_child(select_full_items_button);
1123 select_full_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_data_type_pressed).bind(i));
1124 deselect_all_items_button->set_flat(true);
1125 deselect_all_items_button->set_tooltip_text(deselect_all_items_tooltip);
1126 button_set->add_child(deselect_all_items_button);
1127 deselect_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_data_type_pressed).bind(i));
1128
1129 total_selected_items_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
1130 total_selected_items_label->hide();
1131 import_bulk_vb->add_child(total_selected_items_label);
1132
1133 if (dt == Theme::DATA_TYPE_ICON) {
1134 select_icons_warning_hb = memnew(HBoxContainer);
1135 import_bulk_vb->add_child(select_icons_warning_hb);
1136
1137 select_icons_warning_icon = memnew(TextureRect);
1138 select_icons_warning_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
1139 select_icons_warning_hb->add_child(select_icons_warning_icon);
1140
1141 select_icons_warning = memnew(Label);
1142 select_icons_warning->set_text(TTR("Caution: Adding icon data may considerably increase the size of your Theme resource."));
1143 select_icons_warning->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
1144 select_icons_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1145 select_icons_warning_hb->add_child(select_icons_warning);
1146 }
1147 }
1148
1149 add_child(memnew(HSeparator));
1150
1151 HBoxContainer *import_buttons = memnew(HBoxContainer);
1152 add_child(import_buttons);
1153
1154 import_collapse_types_button = memnew(Button);
1155 import_collapse_types_button->set_flat(true);
1156 import_collapse_types_button->set_tooltip_text(TTR("Collapse types."));
1157 import_buttons->add_child(import_collapse_types_button);
1158 import_collapse_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items).bind(true));
1159 import_expand_types_button = memnew(Button);
1160 import_expand_types_button->set_flat(true);
1161 import_expand_types_button->set_tooltip_text(TTR("Expand types."));
1162 import_buttons->add_child(import_expand_types_button);
1163 import_expand_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items).bind(false));
1164
1165 import_buttons->add_child(memnew(VSeparator));
1166
1167 import_select_all_button = memnew(Button);
1168 import_select_all_button->set_flat(true);
1169 import_select_all_button->set_text(TTR("Select All"));
1170 import_select_all_button->set_tooltip_text(TTR("Select all Theme items."));
1171 import_buttons->add_child(import_select_all_button);
1172 import_select_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_items_pressed));
1173 import_select_full_button = memnew(Button);
1174 import_select_full_button->set_flat(true);
1175 import_select_full_button->set_text(TTR("Select With Data"));
1176 import_select_full_button->set_tooltip_text(TTR("Select all Theme items with item data."));
1177 import_buttons->add_child(import_select_full_button);
1178 import_select_full_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_items_pressed));
1179 import_deselect_all_button = memnew(Button);
1180 import_deselect_all_button->set_flat(true);
1181 import_deselect_all_button->set_text(TTR("Deselect All"));
1182 import_deselect_all_button->set_tooltip_text(TTR("Deselect all Theme items."));
1183 import_buttons->add_child(import_deselect_all_button);
1184 import_deselect_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_items_pressed));
1185
1186 import_buttons->add_spacer();
1187
1188 Button *import_add_selected_button = memnew(Button);
1189 import_add_selected_button->set_text(TTR("Import Selected"));
1190 import_buttons->add_child(import_add_selected_button);
1191 import_add_selected_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_import_selected));
1192}
1193
1194///////////////////////
1195
1196void ThemeItemEditorDialog::ok_pressed() {
1197 if (import_default_theme_items->has_selected_items() || import_editor_theme_items->has_selected_items() || import_other_theme_items->has_selected_items()) {
1198 confirm_closing_dialog->set_text(TTR("Import Items tab has some items selected. Selection will be lost upon closing this window.\nClose anyway?"));
1199 confirm_closing_dialog->popup_centered(Size2(380, 120) * EDSCALE);
1200 return;
1201 }
1202
1203 hide();
1204}
1205
1206void ThemeItemEditorDialog::_close_dialog() {
1207 hide();
1208}
1209
1210void ThemeItemEditorDialog::_dialog_about_to_show() {
1211 ERR_FAIL_COND_MSG(edited_theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing.");
1212
1213 _update_edit_types();
1214
1215 import_default_theme_items->set_edited_theme(edited_theme);
1216 import_default_theme_items->set_base_theme(ThemeDB::get_singleton()->get_default_theme());
1217 import_default_theme_items->reset_item_tree();
1218
1219 import_editor_theme_items->set_edited_theme(edited_theme);
1220 import_editor_theme_items->set_base_theme(EditorNode::get_singleton()->get_editor_theme());
1221 import_editor_theme_items->reset_item_tree();
1222
1223 import_other_theme_items->set_edited_theme(edited_theme);
1224 import_other_theme_items->reset_item_tree();
1225}
1226
1227void ThemeItemEditorDialog::_update_edit_types() {
1228 Ref<Theme> base_theme = ThemeDB::get_singleton()->get_default_theme();
1229
1230 List<StringName> theme_types;
1231 edited_theme->get_type_list(&theme_types);
1232 theme_types.sort_custom<StringName::AlphCompare>();
1233
1234 bool item_reselected = false;
1235 edit_type_list->clear();
1236 TreeItem *list_root = edit_type_list->create_item();
1237
1238 for (const StringName &E : theme_types) {
1239 Ref<Texture2D> item_icon;
1240 if (E == "") {
1241 item_icon = get_editor_theme_icon(SNAME("NodeDisabled"));
1242 } else {
1243 item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled");
1244 }
1245 TreeItem *list_item = edit_type_list->create_item(list_root);
1246 list_item->set_text(0, E);
1247 list_item->set_icon(0, item_icon);
1248 list_item->add_button(0, get_editor_theme_icon(SNAME("Remove")), TYPES_TREE_REMOVE_ITEM, false, TTR("Remove Type"));
1249
1250 if (E == edited_item_type) {
1251 list_item->select(0);
1252 item_reselected = true;
1253 }
1254 }
1255 if (!item_reselected) {
1256 edited_item_type = "";
1257
1258 if (list_root->get_child_count() > 0) {
1259 list_root->get_child(0)->select(0);
1260 }
1261 }
1262
1263 List<StringName> default_types;
1264 base_theme->get_type_list(&default_types);
1265 default_types.sort_custom<StringName::AlphCompare>();
1266
1267 String selected_type = "";
1268 TreeItem *selected_item = edit_type_list->get_selected();
1269 if (selected_item) {
1270 selected_type = selected_item->get_text(0);
1271
1272 edit_items_add_color->set_disabled(false);
1273 edit_items_add_constant->set_disabled(false);
1274 edit_items_add_font->set_disabled(false);
1275 edit_items_add_font_size->set_disabled(false);
1276 edit_items_add_icon->set_disabled(false);
1277 edit_items_add_stylebox->set_disabled(false);
1278
1279 edit_items_remove_class->set_disabled(false);
1280 edit_items_remove_custom->set_disabled(false);
1281 edit_items_remove_all->set_disabled(false);
1282
1283 edit_items_message->set_text("");
1284 edit_items_message->hide();
1285 } else {
1286 edit_items_add_color->set_disabled(true);
1287 edit_items_add_constant->set_disabled(true);
1288 edit_items_add_font->set_disabled(true);
1289 edit_items_add_font_size->set_disabled(true);
1290 edit_items_add_icon->set_disabled(true);
1291 edit_items_add_stylebox->set_disabled(true);
1292
1293 edit_items_remove_class->set_disabled(true);
1294 edit_items_remove_custom->set_disabled(true);
1295 edit_items_remove_all->set_disabled(true);
1296
1297 edit_items_message->set_text(TTR("Select a theme type from the list to edit its items.\nYou can add a custom type or import a type with its items from another theme."));
1298 edit_items_message->show();
1299 }
1300
1301 _update_edit_item_tree(selected_type);
1302}
1303
1304void ThemeItemEditorDialog::_edited_type_selected() {
1305 TreeItem *selected_item = edit_type_list->get_selected();
1306 String selected_type = selected_item->get_text(0);
1307 _update_edit_item_tree(selected_type);
1308}
1309
1310void ThemeItemEditorDialog::_edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
1311 if (p_button != MouseButton::LEFT) {
1312 return;
1313 }
1314
1315 TreeItem *item = Object::cast_to<TreeItem>(p_item);
1316 if (!item) {
1317 return;
1318 }
1319
1320 switch (p_id) {
1321 case TYPES_TREE_REMOVE_ITEM: {
1322 String type_name = item->get_text(0);
1323 _remove_theme_type(type_name);
1324 } break;
1325 }
1326}
1327
1328void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
1329 edited_item_type = p_item_type;
1330
1331 edit_items_tree->clear();
1332 TreeItem *root = edit_items_tree->create_item();
1333
1334 List<StringName> names;
1335 bool has_any_items = false;
1336
1337 { // Colors.
1338 names.clear();
1339 edited_theme->get_color_list(p_item_type, &names);
1340
1341 if (names.size() > 0) {
1342 TreeItem *color_root = edit_items_tree->create_item(root);
1343 color_root->set_metadata(0, Theme::DATA_TYPE_COLOR);
1344 color_root->set_icon(0, get_editor_theme_icon(SNAME("Color")));
1345 color_root->set_text(0, TTR("Colors"));
1346 color_root->add_button(0, get_editor_theme_icon(SNAME("Clear")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Color Items"));
1347
1348 names.sort_custom<StringName::AlphCompare>();
1349 for (const StringName &E : names) {
1350 TreeItem *item = edit_items_tree->create_item(color_root);
1351 item->set_text(0, E);
1352 item->add_button(0, get_editor_theme_icon(SNAME("Edit")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
1353 item->add_button(0, get_editor_theme_icon(SNAME("Remove")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
1354 }
1355
1356 has_any_items = true;
1357 }
1358 }
1359
1360 { // Constants.
1361 names.clear();
1362 edited_theme->get_constant_list(p_item_type, &names);
1363
1364 if (names.size() > 0) {
1365 TreeItem *constant_root = edit_items_tree->create_item(root);
1366 constant_root->set_metadata(0, Theme::DATA_TYPE_CONSTANT);
1367 constant_root->set_icon(0, get_editor_theme_icon(SNAME("MemberConstant")));
1368 constant_root->set_text(0, TTR("Constants"));
1369 constant_root->add_button(0, get_editor_theme_icon(SNAME("Clear")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Constant Items"));
1370
1371 names.sort_custom<StringName::AlphCompare>();
1372 for (const StringName &E : names) {
1373 TreeItem *item = edit_items_tree->create_item(constant_root);
1374 item->set_text(0, E);
1375 item->add_button(0, get_editor_theme_icon(SNAME("Edit")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
1376 item->add_button(0, get_editor_theme_icon(SNAME("Remove")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
1377 }
1378
1379 has_any_items = true;
1380 }
1381 }
1382
1383 { // Fonts.
1384 names.clear();
1385 edited_theme->get_font_list(p_item_type, &names);
1386
1387 if (names.size() > 0) {
1388 TreeItem *font_root = edit_items_tree->create_item(root);
1389 font_root->set_metadata(0, Theme::DATA_TYPE_FONT);
1390 font_root->set_icon(0, get_editor_theme_icon(SNAME("Font")));
1391 font_root->set_text(0, TTR("Fonts"));
1392 font_root->add_button(0, get_editor_theme_icon(SNAME("Clear")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Font Items"));
1393
1394 names.sort_custom<StringName::AlphCompare>();
1395 for (const StringName &E : names) {
1396 TreeItem *item = edit_items_tree->create_item(font_root);
1397 item->set_text(0, E);
1398 item->add_button(0, get_editor_theme_icon(SNAME("Edit")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
1399 item->add_button(0, get_editor_theme_icon(SNAME("Remove")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
1400 }
1401
1402 has_any_items = true;
1403 }
1404 }
1405
1406 { // Font sizes.
1407 names.clear();
1408 edited_theme->get_font_size_list(p_item_type, &names);
1409
1410 if (names.size() > 0) {
1411 TreeItem *font_size_root = edit_items_tree->create_item(root);
1412 font_size_root->set_metadata(0, Theme::DATA_TYPE_FONT_SIZE);
1413 font_size_root->set_icon(0, get_editor_theme_icon(SNAME("FontSize")));
1414 font_size_root->set_text(0, TTR("Font Sizes"));
1415 font_size_root->add_button(0, get_editor_theme_icon(SNAME("Clear")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Font Size Items"));
1416
1417 names.sort_custom<StringName::AlphCompare>();
1418 for (const StringName &E : names) {
1419 TreeItem *item = edit_items_tree->create_item(font_size_root);
1420 item->set_text(0, E);
1421 item->add_button(0, get_editor_theme_icon(SNAME("Edit")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
1422 item->add_button(0, get_editor_theme_icon(SNAME("Remove")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
1423 }
1424
1425 has_any_items = true;
1426 }
1427 }
1428
1429 { // Icons.
1430 names.clear();
1431 edited_theme->get_icon_list(p_item_type, &names);
1432
1433 if (names.size() > 0) {
1434 TreeItem *icon_root = edit_items_tree->create_item(root);
1435 icon_root->set_metadata(0, Theme::DATA_TYPE_ICON);
1436 icon_root->set_icon(0, get_editor_theme_icon(SNAME("ImageTexture")));
1437 icon_root->set_text(0, TTR("Icons"));
1438 icon_root->add_button(0, get_editor_theme_icon(SNAME("Clear")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Icon Items"));
1439
1440 names.sort_custom<StringName::AlphCompare>();
1441 for (const StringName &E : names) {
1442 TreeItem *item = edit_items_tree->create_item(icon_root);
1443 item->set_text(0, E);
1444 item->add_button(0, get_editor_theme_icon(SNAME("Edit")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
1445 item->add_button(0, get_editor_theme_icon(SNAME("Remove")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
1446 }
1447
1448 has_any_items = true;
1449 }
1450 }
1451
1452 { // Styleboxes.
1453 names.clear();
1454 edited_theme->get_stylebox_list(p_item_type, &names);
1455
1456 if (names.size() > 0) {
1457 TreeItem *stylebox_root = edit_items_tree->create_item(root);
1458 stylebox_root->set_metadata(0, Theme::DATA_TYPE_STYLEBOX);
1459 stylebox_root->set_icon(0, get_editor_theme_icon(SNAME("StyleBoxFlat")));
1460 stylebox_root->set_text(0, TTR("Styleboxes"));
1461 stylebox_root->add_button(0, get_editor_theme_icon(SNAME("Clear")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All StyleBox Items"));
1462
1463 names.sort_custom<StringName::AlphCompare>();
1464 for (const StringName &E : names) {
1465 TreeItem *item = edit_items_tree->create_item(stylebox_root);
1466 item->set_text(0, E);
1467 item->add_button(0, get_editor_theme_icon(SNAME("Edit")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
1468 item->add_button(0, get_editor_theme_icon(SNAME("Remove")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
1469 }
1470
1471 has_any_items = true;
1472 }
1473 }
1474
1475 // If some type is selected, but it doesn't seem to have any items, show a guiding message.
1476 TreeItem *selected_item = edit_type_list->get_selected();
1477 if (selected_item) {
1478 if (!has_any_items) {
1479 edit_items_message->set_text(TTR("This theme type is empty.\nAdd more items to it manually or by importing from another theme."));
1480 edit_items_message->show();
1481 } else {
1482 edit_items_message->set_text("");
1483 edit_items_message->hide();
1484 }
1485 }
1486}
1487
1488void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
1489 if (p_button != MouseButton::LEFT) {
1490 return;
1491 }
1492
1493 TreeItem *item = Object::cast_to<TreeItem>(p_item);
1494 if (!item) {
1495 return;
1496 }
1497
1498 switch (p_id) {
1499 case ITEMS_TREE_RENAME_ITEM: {
1500 String item_name = item->get_text(0);
1501 int data_type = item->get_parent()->get_metadata(0);
1502 _open_rename_theme_item_dialog((Theme::DataType)data_type, item_name);
1503 _update_edit_item_tree(edited_item_type);
1504 } break;
1505 case ITEMS_TREE_REMOVE_ITEM: {
1506 String item_name = item->get_text(0);
1507 int data_type = item->get_parent()->get_metadata(0);
1508
1509 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1510 ur->create_action(TTR("Remove Theme Item"));
1511 ur->add_do_method(*edited_theme, "clear_theme_item", (Theme::DataType)data_type, item_name, edited_item_type);
1512 ur->add_undo_method(*edited_theme, "set_theme_item", (Theme::DataType)data_type, item_name, edited_item_type, edited_theme->get_theme_item((Theme::DataType)data_type, item_name, edited_item_type));
1513 ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
1514 ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
1515 ur->commit_action();
1516 } break;
1517 case ITEMS_TREE_REMOVE_DATA_TYPE: {
1518 int data_type = item->get_metadata(0);
1519 _remove_data_type_items((Theme::DataType)data_type, edited_item_type);
1520 } break;
1521 }
1522}
1523
1524void ThemeItemEditorDialog::_add_theme_type(const String &p_new_text) {
1525 const String new_type = edit_add_type_value->get_text().strip_edges();
1526 edit_add_type_value->clear();
1527
1528 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1529 ur->create_action(TTR("Add Theme Type"));
1530
1531 ur->add_do_method(*edited_theme, "add_type", new_type);
1532 ur->add_undo_method(*edited_theme, "remove_type", new_type);
1533 ur->add_do_method(this, "_update_edit_types");
1534 ur->add_undo_method(this, "_update_edit_types");
1535
1536 ur->commit_action();
1537}
1538
1539void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type) {
1540 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1541 ur->create_action(TTR("Create Theme Item"));
1542
1543 switch (p_data_type) {
1544 case Theme::DATA_TYPE_ICON:
1545 ur->add_do_method(*edited_theme, "set_icon", p_item_name, p_item_type, Ref<Texture2D>());
1546 ur->add_undo_method(*edited_theme, "clear_icon", p_item_name, p_item_type);
1547 break;
1548 case Theme::DATA_TYPE_STYLEBOX:
1549 ur->add_do_method(*edited_theme, "set_stylebox", p_item_name, p_item_type, Ref<StyleBox>());
1550 ur->add_undo_method(*edited_theme, "clear_stylebox", p_item_name, p_item_type);
1551
1552 if (theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(p_item_name, p_item_type))) {
1553 ur->add_undo_method(theme_type_editor, "_unpin_leading_stylebox");
1554 }
1555 break;
1556 case Theme::DATA_TYPE_FONT:
1557 ur->add_do_method(*edited_theme, "set_font", p_item_name, p_item_type, Ref<Font>());
1558 ur->add_undo_method(*edited_theme, "clear_font", p_item_name, p_item_type);
1559 break;
1560 case Theme::DATA_TYPE_FONT_SIZE:
1561 ur->add_do_method(*edited_theme, "set_font_size", p_item_name, p_item_type, -1);
1562 ur->add_undo_method(*edited_theme, "clear_font_size", p_item_name, p_item_type);
1563 break;
1564 case Theme::DATA_TYPE_COLOR:
1565 ur->add_do_method(*edited_theme, "set_color", p_item_name, p_item_type, Color());
1566 ur->add_undo_method(*edited_theme, "clear_color", p_item_name, p_item_type);
1567 break;
1568 case Theme::DATA_TYPE_CONSTANT:
1569 ur->add_do_method(*edited_theme, "set_constant", p_item_name, p_item_type, 0);
1570 ur->add_undo_method(*edited_theme, "clear_constant", p_item_name, p_item_type);
1571 break;
1572 case Theme::DATA_TYPE_MAX:
1573 break; // Can't happen, but silences warning.
1574 }
1575
1576 ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
1577 ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
1578 ur->commit_action();
1579}
1580
1581void ThemeItemEditorDialog::_remove_theme_type(const String &p_theme_type) {
1582 Ref<Theme> old_snapshot = edited_theme->duplicate();
1583 Ref<Theme> new_snapshot = edited_theme->duplicate();
1584
1585 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1586 ur->create_action(TTR("Remove Theme Type"));
1587
1588 new_snapshot->remove_type(p_theme_type);
1589
1590 ur->add_do_method(*edited_theme, "clear");
1591 ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
1592 // If the type was empty, it cannot be restored with merge, but thankfully we can fake it.
1593 ur->add_undo_method(*edited_theme, "add_type", p_theme_type);
1594 ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
1595
1596 ur->add_do_method(this, "_update_edit_types");
1597 ur->add_undo_method(this, "_update_edit_types");
1598
1599 ur->commit_action();
1600}
1601
1602void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type, String p_item_type) {
1603 List<StringName> names;
1604
1605 Ref<Theme> old_snapshot = edited_theme->duplicate();
1606 Ref<Theme> new_snapshot = edited_theme->duplicate();
1607
1608 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1609 ur->create_action(TTR("Remove Data Type Items From Theme"));
1610
1611 new_snapshot->get_theme_item_list(p_data_type, p_item_type, &names);
1612 for (const StringName &E : names) {
1613 new_snapshot->clear_theme_item(p_data_type, E, edited_item_type);
1614
1615 if (p_data_type == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, p_item_type))) {
1616 ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox");
1617 ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, p_item_type));
1618 }
1619 }
1620
1621 ur->add_do_method(*edited_theme, "clear");
1622 ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
1623 ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
1624
1625 ur->add_do_method(theme_type_editor, "_update_edit_item_tree", edited_item_type);
1626 ur->add_undo_method(theme_type_editor, "_update_edit_item_tree", edited_item_type);
1627
1628 ur->commit_action();
1629}
1630
1631void ThemeItemEditorDialog::_remove_class_items() {
1632 List<StringName> names;
1633
1634 Ref<Theme> old_snapshot = edited_theme->duplicate();
1635 Ref<Theme> new_snapshot = edited_theme->duplicate();
1636
1637 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1638 ur->create_action(TTR("Remove Class Items From Theme"));
1639
1640 for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
1641 Theme::DataType data_type = (Theme::DataType)dt;
1642
1643 names.clear();
1644 ThemeDB::get_singleton()->get_default_theme()->get_theme_item_list(data_type, edited_item_type, &names);
1645 for (const StringName &E : names) {
1646 if (new_snapshot->has_theme_item_nocheck(data_type, E, edited_item_type)) {
1647 new_snapshot->clear_theme_item(data_type, E, edited_item_type);
1648
1649 if (dt == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, edited_item_type))) {
1650 ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox");
1651 ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, edited_item_type));
1652 }
1653 }
1654 }
1655 }
1656
1657 ur->add_do_method(*edited_theme, "clear");
1658 ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
1659 ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
1660
1661 ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
1662 ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
1663
1664 ur->commit_action();
1665}
1666
1667void ThemeItemEditorDialog::_remove_custom_items() {
1668 List<StringName> names;
1669
1670 Ref<Theme> old_snapshot = edited_theme->duplicate();
1671 Ref<Theme> new_snapshot = edited_theme->duplicate();
1672
1673 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1674 ur->create_action(TTR("Remove Custom Items From Theme"));
1675
1676 for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
1677 Theme::DataType data_type = (Theme::DataType)dt;
1678
1679 names.clear();
1680 new_snapshot->get_theme_item_list(data_type, edited_item_type, &names);
1681 for (const StringName &E : names) {
1682 if (!ThemeDB::get_singleton()->get_default_theme()->has_theme_item_nocheck(data_type, E, edited_item_type)) {
1683 new_snapshot->clear_theme_item(data_type, E, edited_item_type);
1684
1685 if (dt == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, edited_item_type))) {
1686 ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox");
1687 ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, edited_item_type));
1688 }
1689 }
1690 }
1691 }
1692
1693 ur->add_do_method(*edited_theme, "clear");
1694 ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
1695 ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
1696
1697 ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
1698 ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
1699
1700 ur->commit_action();
1701}
1702
1703void ThemeItemEditorDialog::_remove_all_items() {
1704 List<StringName> names;
1705
1706 Ref<Theme> old_snapshot = edited_theme->duplicate();
1707 Ref<Theme> new_snapshot = edited_theme->duplicate();
1708
1709 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1710 ur->create_action(TTR("Remove All Items From Theme"));
1711
1712 for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
1713 Theme::DataType data_type = (Theme::DataType)dt;
1714
1715 names.clear();
1716 new_snapshot->get_theme_item_list(data_type, edited_item_type, &names);
1717 for (const StringName &E : names) {
1718 new_snapshot->clear_theme_item(data_type, E, edited_item_type);
1719
1720 if (dt == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, edited_item_type))) {
1721 ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox");
1722 ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, edited_item_type));
1723 }
1724 }
1725 }
1726
1727 ur->add_do_method(*edited_theme, "clear");
1728 ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
1729 ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
1730
1731 ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
1732 ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
1733
1734 ur->commit_action();
1735}
1736
1737void ThemeItemEditorDialog::_open_add_theme_item_dialog(int p_data_type) {
1738 ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
1739
1740 item_popup_mode = CREATE_THEME_ITEM;
1741 edit_item_data_type = (Theme::DataType)p_data_type;
1742
1743 switch (edit_item_data_type) {
1744 case Theme::DATA_TYPE_COLOR:
1745 edit_theme_item_dialog->set_title(TTR("Add Color Item"));
1746 break;
1747 case Theme::DATA_TYPE_CONSTANT:
1748 edit_theme_item_dialog->set_title(TTR("Add Constant Item"));
1749 break;
1750 case Theme::DATA_TYPE_FONT:
1751 edit_theme_item_dialog->set_title(TTR("Add Font Item"));
1752 break;
1753 case Theme::DATA_TYPE_FONT_SIZE:
1754 edit_theme_item_dialog->set_title(TTR("Add Font Size Item"));
1755 break;
1756 case Theme::DATA_TYPE_ICON:
1757 edit_theme_item_dialog->set_title(TTR("Add Icon Item"));
1758 break;
1759 case Theme::DATA_TYPE_STYLEBOX:
1760 edit_theme_item_dialog->set_title(TTR("Add Stylebox Item"));
1761 break;
1762 case Theme::DATA_TYPE_MAX:
1763 break; // Can't happen, but silences warning.
1764 }
1765
1766 edit_theme_item_old_vb->hide();
1767 theme_item_name->clear();
1768 edit_theme_item_dialog->popup_centered(Size2(380, 110) * EDSCALE);
1769 theme_item_name->grab_focus();
1770}
1771
1772void ThemeItemEditorDialog::_open_rename_theme_item_dialog(Theme::DataType p_data_type, String p_item_name) {
1773 ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
1774
1775 item_popup_mode = RENAME_THEME_ITEM;
1776 edit_item_data_type = p_data_type;
1777 edit_item_old_name = p_item_name;
1778
1779 switch (edit_item_data_type) {
1780 case Theme::DATA_TYPE_COLOR:
1781 edit_theme_item_dialog->set_title(TTR("Rename Color Item"));
1782 break;
1783 case Theme::DATA_TYPE_CONSTANT:
1784 edit_theme_item_dialog->set_title(TTR("Rename Constant Item"));
1785 break;
1786 case Theme::DATA_TYPE_FONT:
1787 edit_theme_item_dialog->set_title(TTR("Rename Font Item"));
1788 break;
1789 case Theme::DATA_TYPE_FONT_SIZE:
1790 edit_theme_item_dialog->set_title(TTR("Rename Font Size Item"));
1791 break;
1792 case Theme::DATA_TYPE_ICON:
1793 edit_theme_item_dialog->set_title(TTR("Rename Icon Item"));
1794 break;
1795 case Theme::DATA_TYPE_STYLEBOX:
1796 edit_theme_item_dialog->set_title(TTR("Rename Stylebox Item"));
1797 break;
1798 case Theme::DATA_TYPE_MAX:
1799 break; // Can't happen, but silences warning.
1800 }
1801
1802 edit_theme_item_old_vb->show();
1803 theme_item_old_name->set_text(p_item_name);
1804 theme_item_name->set_text(p_item_name);
1805 edit_theme_item_dialog->popup_centered(Size2(380, 140) * EDSCALE);
1806 theme_item_name->grab_focus();
1807}
1808
1809void ThemeItemEditorDialog::_confirm_edit_theme_item() {
1810 if (item_popup_mode == CREATE_THEME_ITEM) {
1811 _add_theme_item(edit_item_data_type, theme_item_name->get_text(), edited_item_type);
1812 } else if (item_popup_mode == RENAME_THEME_ITEM) {
1813 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
1814 ur->create_action(TTR("Rename Theme Item"));
1815
1816 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);
1817 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);
1818
1819 ur->add_do_method(this, "_update_edit_item_tree", edited_item_type);
1820 ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type);
1821
1822 ur->commit_action();
1823 }
1824
1825 item_popup_mode = ITEM_POPUP_MODE_MAX;
1826 edit_item_data_type = Theme::DATA_TYPE_MAX;
1827 edit_item_old_name = "";
1828}
1829
1830void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_event) {
1831 Ref<InputEventKey> k = p_event;
1832
1833 if (k.is_valid()) {
1834 if (!k->is_pressed()) {
1835 return;
1836 }
1837
1838 if (k->is_action_pressed(SNAME("ui_text_submit"), false, true)) {
1839 _confirm_edit_theme_item();
1840 edit_theme_item_dialog->hide();
1841 edit_theme_item_dialog->set_input_as_handled();
1842 } else if (k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
1843 edit_theme_item_dialog->hide();
1844 edit_theme_item_dialog->set_input_as_handled();
1845 }
1846 }
1847}
1848
1849void ThemeItemEditorDialog::_open_select_another_theme() {
1850 import_another_theme_dialog->popup_file_dialog();
1851}
1852
1853void ThemeItemEditorDialog::_select_another_theme_cbk(const String &p_path) {
1854 Ref<Theme> loaded_theme = ResourceLoader::load(p_path);
1855 if (loaded_theme.is_null()) {
1856 EditorNode::get_singleton()->show_warning(TTR("Invalid file, not a Theme resource."));
1857 return;
1858 }
1859 if (loaded_theme == edited_theme) {
1860 EditorNode::get_singleton()->show_warning(TTR("Invalid file, same as the edited Theme resource."));
1861 return;
1862 }
1863
1864 import_another_theme_value->set_text(p_path);
1865 import_other_theme_items->set_base_theme(loaded_theme);
1866 import_other_theme_items->reset_item_tree();
1867}
1868
1869void ThemeItemEditorDialog::_notification(int p_what) {
1870 switch (p_what) {
1871 case NOTIFICATION_ENTER_TREE: {
1872 connect("about_to_popup", callable_mp(this, &ThemeItemEditorDialog::_dialog_about_to_show));
1873 [[fallthrough]];
1874 }
1875 case NOTIFICATION_THEME_CHANGED: {
1876 edit_items_add_color->set_icon(get_editor_theme_icon(SNAME("Color")));
1877 edit_items_add_constant->set_icon(get_editor_theme_icon(SNAME("MemberConstant")));
1878 edit_items_add_font->set_icon(get_editor_theme_icon(SNAME("Font")));
1879 edit_items_add_font_size->set_icon(get_editor_theme_icon(SNAME("FontSize")));
1880 edit_items_add_icon->set_icon(get_editor_theme_icon(SNAME("ImageTexture")));
1881 edit_items_add_stylebox->set_icon(get_editor_theme_icon(SNAME("StyleBoxFlat")));
1882
1883 edit_items_remove_class->set_icon(get_editor_theme_icon(SNAME("Control")));
1884 edit_items_remove_custom->set_icon(get_editor_theme_icon(SNAME("ThemeRemoveCustomItems")));
1885 edit_items_remove_all->set_icon(get_editor_theme_icon(SNAME("ThemeRemoveAllItems")));
1886
1887 edit_add_type_button->set_icon(get_editor_theme_icon(SNAME("Add")));
1888
1889 import_another_theme_button->set_icon(get_editor_theme_icon(SNAME("Folder")));
1890 } break;
1891 }
1892}
1893
1894void ThemeItemEditorDialog::_bind_methods() {
1895 ClassDB::bind_method(D_METHOD("_update_edit_types"), &ThemeItemEditorDialog::_update_edit_types);
1896 ClassDB::bind_method(D_METHOD("_update_edit_item_tree"), &ThemeItemEditorDialog::_update_edit_item_tree);
1897}
1898
1899void ThemeItemEditorDialog::set_edited_theme(const Ref<Theme> &p_theme) {
1900 edited_theme = p_theme;
1901}
1902
1903ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_editor) {
1904 set_title(TTR("Manage Theme Items"));
1905 set_ok_button_text(TTR("Close"));
1906 set_hide_on_ok(false); // Closing may require a confirmation in some cases.
1907
1908 theme_type_editor = p_theme_type_editor;
1909
1910 tc = memnew(TabContainer);
1911 add_child(tc);
1912 tc->set_theme_type_variation("TabContainerOdd");
1913
1914 // Edit Items tab.
1915 HSplitContainer *edit_dialog_hs = memnew(HSplitContainer);
1916 tc->add_child(edit_dialog_hs);
1917 tc->set_tab_title(0, TTR("Edit Items"));
1918
1919 VBoxContainer *edit_dialog_side_vb = memnew(VBoxContainer);
1920 edit_dialog_side_vb->set_custom_minimum_size(Size2(200.0, 0.0) * EDSCALE);
1921 edit_dialog_hs->add_child(edit_dialog_side_vb);
1922
1923 Label *edit_type_label = memnew(Label);
1924 edit_type_label->set_text(TTR("Types:"));
1925 edit_dialog_side_vb->add_child(edit_type_label);
1926
1927 edit_type_list = memnew(Tree);
1928 edit_type_list->set_hide_root(true);
1929 edit_type_list->set_hide_folding(true);
1930 edit_type_list->set_columns(1);
1931 edit_type_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1932 edit_dialog_side_vb->add_child(edit_type_list);
1933 edit_type_list->connect("item_selected", callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected));
1934 edit_type_list->connect("button_clicked", callable_mp(this, &ThemeItemEditorDialog::_edited_type_button_pressed));
1935
1936 Label *edit_add_type_label = memnew(Label);
1937 edit_add_type_label->set_text(TTR("Add Type:"));
1938 edit_dialog_side_vb->add_child(edit_add_type_label);
1939
1940 HBoxContainer *edit_add_type_hb = memnew(HBoxContainer);
1941 edit_dialog_side_vb->add_child(edit_add_type_hb);
1942 edit_add_type_value = memnew(LineEdit);
1943 edit_add_type_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1944 edit_add_type_value->connect("text_submitted", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type));
1945 edit_add_type_hb->add_child(edit_add_type_value);
1946 edit_add_type_button = memnew(Button);
1947 edit_add_type_hb->add_child(edit_add_type_button);
1948 edit_add_type_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type).bind(""));
1949
1950 VBoxContainer *edit_items_vb = memnew(VBoxContainer);
1951 edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1952 edit_dialog_hs->add_child(edit_items_vb);
1953
1954 HBoxContainer *edit_items_toolbar = memnew(HBoxContainer);
1955 edit_items_vb->add_child(edit_items_toolbar);
1956
1957 Label *edit_items_toolbar_add_label = memnew(Label);
1958 edit_items_toolbar_add_label->set_text(TTR("Add Item:"));
1959 edit_items_toolbar->add_child(edit_items_toolbar_add_label);
1960
1961 edit_items_add_color = memnew(Button);
1962 edit_items_add_color->set_tooltip_text(TTR("Add Color Item"));
1963 edit_items_add_color->set_flat(true);
1964 edit_items_add_color->set_disabled(true);
1965 edit_items_toolbar->add_child(edit_items_add_color);
1966 edit_items_add_color->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_COLOR));
1967
1968 edit_items_add_constant = memnew(Button);
1969 edit_items_add_constant->set_tooltip_text(TTR("Add Constant Item"));
1970 edit_items_add_constant->set_flat(true);
1971 edit_items_add_constant->set_disabled(true);
1972 edit_items_toolbar->add_child(edit_items_add_constant);
1973 edit_items_add_constant->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_CONSTANT));
1974
1975 edit_items_add_font = memnew(Button);
1976 edit_items_add_font->set_tooltip_text(TTR("Add Font Item"));
1977 edit_items_add_font->set_flat(true);
1978 edit_items_add_font->set_disabled(true);
1979 edit_items_toolbar->add_child(edit_items_add_font);
1980 edit_items_add_font->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_FONT));
1981
1982 edit_items_add_font_size = memnew(Button);
1983 edit_items_add_font_size->set_tooltip_text(TTR("Add Font Size Item"));
1984 edit_items_add_font_size->set_flat(true);
1985 edit_items_add_font_size->set_disabled(true);
1986 edit_items_toolbar->add_child(edit_items_add_font_size);
1987 edit_items_add_font_size->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_FONT_SIZE));
1988
1989 edit_items_add_icon = memnew(Button);
1990 edit_items_add_icon->set_tooltip_text(TTR("Add Icon Item"));
1991 edit_items_add_icon->set_flat(true);
1992 edit_items_add_icon->set_disabled(true);
1993 edit_items_toolbar->add_child(edit_items_add_icon);
1994 edit_items_add_icon->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_ICON));
1995
1996 edit_items_add_stylebox = memnew(Button);
1997 edit_items_add_stylebox->set_tooltip_text(TTR("Add StyleBox Item"));
1998 edit_items_add_stylebox->set_flat(true);
1999 edit_items_add_stylebox->set_disabled(true);
2000 edit_items_toolbar->add_child(edit_items_add_stylebox);
2001 edit_items_add_stylebox->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_STYLEBOX));
2002
2003 edit_items_toolbar->add_child(memnew(VSeparator));
2004
2005 Label *edit_items_toolbar_remove_label = memnew(Label);
2006 edit_items_toolbar_remove_label->set_text(TTR("Remove Items:"));
2007 edit_items_toolbar->add_child(edit_items_toolbar_remove_label);
2008
2009 edit_items_remove_class = memnew(Button);
2010 edit_items_remove_class->set_tooltip_text(TTR("Remove Class Items"));
2011 edit_items_remove_class->set_flat(true);
2012 edit_items_remove_class->set_disabled(true);
2013 edit_items_toolbar->add_child(edit_items_remove_class);
2014 edit_items_remove_class->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_class_items));
2015
2016 edit_items_remove_custom = memnew(Button);
2017 edit_items_remove_custom->set_tooltip_text(TTR("Remove Custom Items"));
2018 edit_items_remove_custom->set_flat(true);
2019 edit_items_remove_custom->set_disabled(true);
2020 edit_items_toolbar->add_child(edit_items_remove_custom);
2021 edit_items_remove_custom->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_custom_items));
2022
2023 edit_items_remove_all = memnew(Button);
2024 edit_items_remove_all->set_tooltip_text(TTR("Remove All Items"));
2025 edit_items_remove_all->set_flat(true);
2026 edit_items_remove_all->set_disabled(true);
2027 edit_items_toolbar->add_child(edit_items_remove_all);
2028 edit_items_remove_all->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_all_items));
2029
2030 edit_items_tree = memnew(Tree);
2031 edit_items_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2032 edit_items_tree->set_hide_root(true);
2033 edit_items_tree->set_columns(1);
2034 edit_items_vb->add_child(edit_items_tree);
2035 edit_items_tree->connect("button_clicked", callable_mp(this, &ThemeItemEditorDialog::_item_tree_button_pressed));
2036
2037 edit_items_message = memnew(Label);
2038 edit_items_message->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
2039 edit_items_message->set_mouse_filter(Control::MOUSE_FILTER_STOP);
2040 edit_items_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
2041 edit_items_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
2042 edit_items_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
2043 edit_items_tree->add_child(edit_items_message);
2044
2045 edit_theme_item_dialog = memnew(ConfirmationDialog);
2046 edit_theme_item_dialog->set_title(TTR("Add Theme Item"));
2047 add_child(edit_theme_item_dialog);
2048 VBoxContainer *edit_theme_item_vb = memnew(VBoxContainer);
2049 edit_theme_item_dialog->add_child(edit_theme_item_vb);
2050
2051 edit_theme_item_old_vb = memnew(VBoxContainer);
2052 edit_theme_item_vb->add_child(edit_theme_item_old_vb);
2053 Label *edit_theme_item_old = memnew(Label);
2054 edit_theme_item_old->set_text(TTR("Old Name:"));
2055 edit_theme_item_old_vb->add_child(edit_theme_item_old);
2056 theme_item_old_name = memnew(Label);
2057 edit_theme_item_old_vb->add_child(theme_item_old_name);
2058
2059 Label *edit_theme_item_label = memnew(Label);
2060 edit_theme_item_label->set_text(TTR("Name:"));
2061 edit_theme_item_vb->add_child(edit_theme_item_label);
2062 theme_item_name = memnew(LineEdit);
2063 edit_theme_item_vb->add_child(theme_item_name);
2064 theme_item_name->connect("gui_input", callable_mp(this, &ThemeItemEditorDialog::_edit_theme_item_gui_input));
2065 edit_theme_item_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_confirm_edit_theme_item));
2066
2067 // Import Items tab.
2068 TabContainer *import_tc = memnew(TabContainer);
2069 import_tc->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
2070 tc->add_child(import_tc);
2071 tc->set_tab_title(1, TTR("Import Items"));
2072
2073 import_default_theme_items = memnew(ThemeItemImportTree);
2074 import_tc->add_child(import_default_theme_items);
2075 import_tc->set_tab_title(0, TTR("Default Theme"));
2076 import_default_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
2077
2078 import_editor_theme_items = memnew(ThemeItemImportTree);
2079 import_tc->add_child(import_editor_theme_items);
2080 import_tc->set_tab_title(1, TTR("Editor Theme"));
2081 import_editor_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
2082
2083 VBoxContainer *import_another_theme_vb = memnew(VBoxContainer);
2084
2085 HBoxContainer *import_another_file_hb = memnew(HBoxContainer);
2086 import_another_theme_vb->add_child(import_another_file_hb);
2087 import_another_theme_value = memnew(LineEdit);
2088 import_another_theme_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
2089 import_another_theme_value->set_editable(false);
2090 import_another_file_hb->add_child(import_another_theme_value);
2091 import_another_theme_button = memnew(Button);
2092 import_another_file_hb->add_child(import_another_theme_button);
2093 import_another_theme_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_select_another_theme));
2094
2095 import_another_theme_dialog = memnew(EditorFileDialog);
2096 import_another_theme_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
2097 import_another_theme_dialog->set_title(TTR("Select Another Theme Resource:"));
2098 List<String> ext;
2099 ResourceLoader::get_recognized_extensions_for_type("Theme", &ext);
2100 for (const String &E : ext) {
2101 import_another_theme_dialog->add_filter("*." + E, TTR("Theme Resource"));
2102 }
2103 import_another_file_hb->add_child(import_another_theme_dialog);
2104 import_another_theme_dialog->connect("file_selected", callable_mp(this, &ThemeItemEditorDialog::_select_another_theme_cbk));
2105
2106 import_other_theme_items = memnew(ThemeItemImportTree);
2107 import_other_theme_items->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2108 import_another_theme_vb->add_child(import_other_theme_items);
2109
2110 import_tc->add_child(import_another_theme_vb);
2111 import_tc->set_tab_title(2, TTR("Another Theme"));
2112 import_other_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
2113
2114 confirm_closing_dialog = memnew(ConfirmationDialog);
2115 confirm_closing_dialog->set_autowrap(true);
2116 add_child(confirm_closing_dialog);
2117 confirm_closing_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_close_dialog));
2118}
2119
2120///////////////////////
2121
2122void ThemeTypeDialog::_dialog_about_to_show() {
2123 add_type_filter->set_text("");
2124 add_type_filter->grab_focus();
2125
2126 _update_add_type_options();
2127}
2128
2129void ThemeTypeDialog::ok_pressed() {
2130 _add_type_selected(add_type_filter->get_text().strip_edges());
2131}
2132
2133void ThemeTypeDialog::_update_add_type_options(const String &p_filter) {
2134 add_type_options->clear();
2135
2136 List<StringName> names;
2137 ThemeDB::get_singleton()->get_default_theme()->get_type_list(&names);
2138 if (include_own_types) {
2139 edited_theme->get_type_list(&names);
2140 }
2141 names.sort_custom<StringName::AlphCompare>();
2142
2143 Vector<StringName> unique_names;
2144 for (const StringName &E : names) {
2145 // Filter out undesired values.
2146 if (!p_filter.is_subsequence_ofn(String(E))) {
2147 continue;
2148 }
2149
2150 // Skip duplicate values.
2151 if (unique_names.has(E)) {
2152 continue;
2153 }
2154 unique_names.append(E);
2155
2156 Ref<Texture2D> item_icon;
2157 if (E == "") {
2158 item_icon = get_editor_theme_icon(SNAME("NodeDisabled"));
2159 } else {
2160 item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled");
2161 }
2162
2163 add_type_options->add_item(E, item_icon);
2164 }
2165}
2166
2167void ThemeTypeDialog::_add_type_filter_cbk(const String &p_value) {
2168 _update_add_type_options(p_value);
2169}
2170
2171void ThemeTypeDialog::_add_type_options_cbk(int p_index) {
2172 add_type_filter->set_text(add_type_options->get_item_text(p_index));
2173}
2174
2175void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) {
2176 _add_type_selected(p_value.strip_edges());
2177}
2178
2179void ThemeTypeDialog::_add_type_dialog_activated(int p_index) {
2180 _add_type_selected(add_type_options->get_item_text(p_index));
2181}
2182
2183void ThemeTypeDialog::_add_type_selected(const String &p_type_name) {
2184 pre_submitted_value = p_type_name;
2185 if (p_type_name.is_empty()) {
2186 add_type_confirmation->popup_centered();
2187 return;
2188 }
2189
2190 _add_type_confirmed();
2191}
2192
2193void ThemeTypeDialog::_add_type_confirmed() {
2194 emit_signal(SNAME("type_selected"), pre_submitted_value);
2195 hide();
2196}
2197
2198void ThemeTypeDialog::_notification(int p_what) {
2199 switch (p_what) {
2200 case NOTIFICATION_ENTER_TREE: {
2201 connect("about_to_popup", callable_mp(this, &ThemeTypeDialog::_dialog_about_to_show));
2202 [[fallthrough]];
2203 }
2204 case NOTIFICATION_THEME_CHANGED: {
2205 _update_add_type_options();
2206 } break;
2207
2208 case NOTIFICATION_VISIBILITY_CHANGED: {
2209 if (is_visible()) {
2210 add_type_filter->grab_focus();
2211 }
2212 } break;
2213 }
2214}
2215
2216void ThemeTypeDialog::_bind_methods() {
2217 ADD_SIGNAL(MethodInfo("type_selected", PropertyInfo(Variant::STRING, "type_name")));
2218}
2219
2220void ThemeTypeDialog::set_edited_theme(const Ref<Theme> &p_theme) {
2221 edited_theme = p_theme;
2222}
2223
2224void ThemeTypeDialog::set_include_own_types(bool p_enable) {
2225 include_own_types = p_enable;
2226}
2227
2228ThemeTypeDialog::ThemeTypeDialog() {
2229 set_hide_on_ok(false);
2230
2231 VBoxContainer *add_type_vb = memnew(VBoxContainer);
2232 add_child(add_type_vb);
2233
2234 Label *add_type_filter_label = memnew(Label);
2235 add_type_filter_label->set_text(TTR("Filter the list of types or create a new custom type:"));
2236 add_type_vb->add_child(add_type_filter_label);
2237
2238 add_type_filter = memnew(LineEdit);
2239 add_type_vb->add_child(add_type_filter);
2240 add_type_filter->connect("text_changed", callable_mp(this, &ThemeTypeDialog::_add_type_filter_cbk));
2241 add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_entered));
2242
2243 Label *add_type_options_label = memnew(Label);
2244 add_type_options_label->set_text(TTR("Available Node-based types:"));
2245 add_type_vb->add_child(add_type_options_label);
2246
2247 add_type_options = memnew(ItemList);
2248 add_type_options->set_v_size_flags(Control::SIZE_EXPAND_FILL);
2249 add_type_vb->add_child(add_type_options);
2250 add_type_options->connect("item_selected", callable_mp(this, &ThemeTypeDialog::_add_type_options_cbk));
2251 add_type_options->connect("item_activated", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_activated));
2252
2253 add_type_confirmation = memnew(ConfirmationDialog);
2254 add_type_confirmation->set_title(TTR("Type name is empty!"));
2255 add_type_confirmation->set_text(TTR("Are you sure you want to create an empty type?"));
2256 add_type_confirmation->connect("confirmed", callable_mp(this, &ThemeTypeDialog::_add_type_confirmed));
2257 add_child(add_type_confirmation);
2258}
2259
2260///////////////////////
2261
2262VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) {
2263 VBoxContainer *items_tab = memnew(VBoxContainer);
2264 items_tab->set_custom_minimum_size(Size2(0, 160) * EDSCALE);
2265 data_type_tabs->add_child(items_tab);
2266 data_type_tabs->set_tab_title(data_type_tabs->get_tab_count() - 1, "");
2267
2268 ScrollContainer *items_sc = memnew(ScrollContainer);
2269 items_sc->set_v_size_flags(SIZE_EXPAND_FILL);
2270 items_sc->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
2271 items_tab->add_child(items_sc);
2272 VBoxContainer *items_list = memnew(VBoxContainer);
2273 items_list->set_h_size_flags(SIZE_EXPAND_FILL);
2274 items_sc->add_child(items_list);
2275
2276 HBoxContainer *item_add_hb = memnew(HBoxContainer);
2277 items_tab->add_child(item_add_hb);
2278 LineEdit *item_add_edit = memnew(LineEdit);
2279 item_add_edit->set_h_size_flags(SIZE_EXPAND_FILL);
2280 item_add_hb->add_child(item_add_edit);
2281 item_add_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_add_lineedit_cbk).bind(p_data_type, item_add_edit));
2282 Button *item_add_button = memnew(Button);
2283 item_add_button->set_text(TTR("Add"));
2284 item_add_hb->add_child(item_add_button);
2285 item_add_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_add_cbk).bind(p_data_type, item_add_edit));
2286
2287 return items_list;
2288}
2289
2290void ThemeTypeEditor::_update_type_list() {
2291 ERR_FAIL_COND(edited_theme.is_null());
2292
2293 if (updating) {
2294 return;
2295 }
2296 updating = true;
2297
2298 Control *focused = get_viewport()->gui_get_focus_owner();
2299 if (focused) {
2300 if (focusables.has(focused)) {
2301 // If focus is currently on one of the internal property editors, don't update.
2302 updating = false;
2303 return;
2304 }
2305
2306 Node *focus_parent = focused->get_parent();
2307 while (focus_parent) {
2308 Control *c = Object::cast_to<Control>(focus_parent);
2309 if (c && focusables.has(c)) {
2310 // If focus is currently on one of the internal property editors, don't update.
2311 updating = false;
2312 return;
2313 }
2314
2315 focus_parent = focus_parent->get_parent();
2316 }
2317 }
2318
2319 List<StringName> theme_types;
2320 edited_theme->get_type_list(&theme_types);
2321 theme_types.sort_custom<StringName::AlphCompare>();
2322
2323 theme_type_list->clear();
2324
2325 if (theme_types.size() > 0) {
2326 theme_type_list->set_disabled(false);
2327
2328 bool item_reselected = false;
2329 int e_idx = 0;
2330 for (const StringName &E : theme_types) {
2331 Ref<Texture2D> item_icon;
2332 if (E == "") {
2333 item_icon = get_editor_theme_icon(SNAME("NodeDisabled"));
2334 } else {
2335 item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled");
2336 }
2337 theme_type_list->add_icon_item(item_icon, E);
2338
2339 if (E == edited_type) {
2340 theme_type_list->select(e_idx);
2341 item_reselected = true;
2342 }
2343 e_idx++;
2344 }
2345
2346 if (!item_reselected) {
2347 theme_type_list->select(0);
2348 _list_type_selected(0);
2349 } else {
2350 _update_type_items();
2351 }
2352 } else {
2353 theme_type_list->set_disabled(true);
2354 theme_type_list->add_item(TTR("None"));
2355
2356 edited_type = "";
2357 _update_type_items();
2358 }
2359
2360 updating = false;
2361}
2362
2363void ThemeTypeEditor::_update_type_list_debounced() {
2364 update_debounce_timer->start();
2365}
2366
2367HashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default) {
2368 HashMap<StringName, bool> items;
2369 List<StringName> names;
2370
2371 if (include_default) {
2372 names.clear();
2373 String default_type = p_type_name;
2374 if (edited_theme->get_type_variation_base(p_type_name) != StringName()) {
2375 default_type = edited_theme->get_type_variation_base(p_type_name);
2376 }
2377
2378 (ThemeDB::get_singleton()->get_default_theme().operator->()->*get_list_func)(default_type, &names);
2379 names.sort_custom<StringName::AlphCompare>();
2380 for (const StringName &E : names) {
2381 items[E] = false;
2382 }
2383 }
2384
2385 {
2386 names.clear();
2387 (edited_theme.operator->()->*get_list_func)(p_type_name, &names);
2388 names.sort_custom<StringName::AlphCompare>();
2389 for (const StringName &E : names) {
2390 items[E] = true;
2391 }
2392 }
2393
2394 List<StringName> keys;
2395 for (const KeyValue<StringName, bool> &E : items) {
2396 keys.push_back(E.key);
2397 }
2398 keys.sort_custom<StringName::AlphCompare>();
2399
2400 HashMap<StringName, bool> ordered_items;
2401 for (const StringName &E : keys) {
2402 ordered_items[E] = items[E];
2403 }
2404
2405 return ordered_items;
2406}
2407
2408HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable) {
2409 HBoxContainer *item_control = memnew(HBoxContainer);
2410
2411 HBoxContainer *item_name_container = memnew(HBoxContainer);
2412 item_name_container->set_h_size_flags(SIZE_EXPAND_FILL);
2413 item_name_container->set_stretch_ratio(2.0);
2414 item_control->add_child(item_name_container);
2415
2416 Label *item_name = memnew(Label);
2417 item_name->set_h_size_flags(SIZE_EXPAND_FILL);
2418 item_name->set_clip_text(true);
2419 item_name->set_text(p_item_name);
2420 item_name->set_tooltip_text(p_item_name);
2421 item_name_container->add_child(item_name);
2422
2423 if (p_editable) {
2424 LineEdit *item_name_edit = memnew(LineEdit);
2425 item_name_edit->set_h_size_flags(SIZE_EXPAND_FILL);
2426 item_name_edit->set_text(p_item_name);
2427 item_name_container->add_child(item_name_edit);
2428 item_name_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_rename_entered).bind(p_data_type, p_item_name, item_name_container));
2429 item_name_edit->hide();
2430
2431 Button *item_rename_button = memnew(Button);
2432 item_rename_button->set_icon(get_editor_theme_icon(SNAME("Edit")));
2433 item_rename_button->set_tooltip_text(TTR("Rename Item"));
2434 item_rename_button->set_flat(true);
2435 item_name_container->add_child(item_rename_button);
2436 item_rename_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_cbk).bind(p_data_type, p_item_name, item_name_container));
2437
2438 Button *item_remove_button = memnew(Button);
2439 item_remove_button->set_icon(get_editor_theme_icon(SNAME("Remove")));
2440 item_remove_button->set_tooltip_text(TTR("Remove Item"));
2441 item_remove_button->set_flat(true);
2442 item_name_container->add_child(item_remove_button);
2443 item_remove_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_remove_cbk).bind(p_data_type, p_item_name));
2444
2445 Button *item_rename_confirm_button = memnew(Button);
2446 item_rename_confirm_button->set_icon(get_editor_theme_icon(SNAME("ImportCheck")));
2447 item_rename_confirm_button->set_tooltip_text(TTR("Confirm Item Rename"));
2448 item_rename_confirm_button->set_flat(true);
2449 item_name_container->add_child(item_rename_confirm_button);
2450 item_rename_confirm_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_confirmed).bind(p_data_type, p_item_name, item_name_container));
2451 item_rename_confirm_button->hide();
2452
2453 Button *item_rename_cancel_button = memnew(Button);
2454 item_rename_cancel_button->set_icon(get_editor_theme_icon(SNAME("ImportFail")));
2455 item_rename_cancel_button->set_tooltip_text(TTR("Cancel Item Rename"));
2456 item_rename_cancel_button->set_flat(true);
2457 item_name_container->add_child(item_rename_cancel_button);
2458 item_rename_cancel_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_canceled).bind(p_data_type, p_item_name, item_name_container));
2459 item_rename_cancel_button->hide();
2460 } else {
2461 item_name->add_theme_color_override("font_color", get_theme_color(SNAME("disabled_font_color"), EditorStringName(Editor)));
2462
2463 Button *item_override_button = memnew(Button);
2464 item_override_button->set_icon(get_editor_theme_icon(SNAME("Add")));
2465 item_override_button->set_tooltip_text(TTR("Override Item"));
2466 item_override_button->set_flat(true);
2467 item_name_container->add_child(item_override_button);
2468 item_override_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_override_cbk).bind(p_data_type, p_item_name));
2469 }
2470
2471 return item_control;
2472}
2473
2474void ThemeTypeEditor::_add_focusable(Control *p_control) {
2475 focusables.append(p_control);
2476}
2477
2478void ThemeTypeEditor::_update_type_items() {
2479 bool show_default = show_default_items_button->is_pressed();
2480 List<StringName> names;
2481
2482 focusables.clear();
2483
2484 // Colors.
2485 {
2486 for (int i = color_items_list->get_child_count() - 1; i >= 0; i--) {
2487 Node *node = color_items_list->get_child(i);
2488 node->queue_free();
2489 color_items_list->remove_child(node);
2490 }
2491
2492 HashMap<StringName, bool> color_items = _get_type_items(edited_type, &Theme::get_color_list, show_default);
2493 for (const KeyValue<StringName, bool> &E : color_items) {
2494 HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_COLOR, E.key, E.value);
2495 ColorPickerButton *item_editor = memnew(ColorPickerButton);
2496 item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
2497 item_control->add_child(item_editor);
2498
2499 if (E.value) {
2500 item_editor->set_pick_color(edited_theme->get_color(E.key, edited_type));
2501 item_editor->connect("color_changed", callable_mp(this, &ThemeTypeEditor::_color_item_changed).bind(E.key));
2502 item_editor->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(item_editor->get_picker()));
2503 } else {
2504 item_editor->set_pick_color(ThemeDB::get_singleton()->get_default_theme()->get_color(E.key, edited_type));
2505 item_editor->set_disabled(true);
2506 }
2507
2508 _add_focusable(item_editor);
2509 color_items_list->add_child(item_control);
2510 }
2511 }
2512
2513 // Constants.
2514 {
2515 for (int i = constant_items_list->get_child_count() - 1; i >= 0; i--) {
2516 Node *node = constant_items_list->get_child(i);
2517 node->queue_free();
2518 constant_items_list->remove_child(node);
2519 }
2520
2521 HashMap<StringName, bool> constant_items = _get_type_items(edited_type, &Theme::get_constant_list, show_default);
2522 for (const KeyValue<StringName, bool> &E : constant_items) {
2523 HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_CONSTANT, E.key, E.value);
2524 SpinBox *item_editor = memnew(SpinBox);
2525 item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
2526 item_editor->set_min(-100000);
2527 item_editor->set_max(100000);
2528 item_editor->set_step(1);
2529 item_editor->set_allow_lesser(true);
2530 item_editor->set_allow_greater(true);
2531 item_control->add_child(item_editor);
2532
2533 if (E.value) {
2534 item_editor->set_value(edited_theme->get_constant(E.key, edited_type));
2535 item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_constant_item_changed).bind(E.key));
2536 } else {
2537 item_editor->set_value(ThemeDB::get_singleton()->get_default_theme()->get_constant(E.key, edited_type));
2538 item_editor->set_editable(false);
2539 }
2540
2541 _add_focusable(item_editor);
2542 constant_items_list->add_child(item_control);
2543 }
2544 }
2545
2546 // Fonts.
2547 {
2548 for (int i = font_items_list->get_child_count() - 1; i >= 0; i--) {
2549 Node *node = font_items_list->get_child(i);
2550 node->queue_free();
2551 font_items_list->remove_child(node);
2552 }
2553
2554 HashMap<StringName, bool> font_items = _get_type_items(edited_type, &Theme::get_font_list, show_default);
2555 for (const KeyValue<StringName, bool> &E : font_items) {
2556 HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT, E.key, E.value);
2557 EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
2558 item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
2559 item_editor->set_base_type("Font");
2560 item_control->add_child(item_editor);
2561
2562 if (E.value) {
2563 if (edited_theme->has_font(E.key, edited_type)) {
2564 item_editor->set_edited_resource(edited_theme->get_font(E.key, edited_type));
2565 } else {
2566 item_editor->set_edited_resource(Ref<Resource>());
2567 }
2568 item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
2569 item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_font_item_changed).bind(E.key));
2570 } else {
2571 if (ThemeDB::get_singleton()->get_default_theme()->has_font(E.key, edited_type)) {
2572 item_editor->set_edited_resource(ThemeDB::get_singleton()->get_default_theme()->get_font(E.key, edited_type));
2573 } else {
2574 item_editor->set_edited_resource(Ref<Resource>());
2575 }
2576 item_editor->set_editable(false);
2577 }
2578
2579 _add_focusable(item_editor);
2580 font_items_list->add_child(item_control);
2581 }
2582 }
2583
2584 // Fonts sizes.
2585 {
2586 for (int i = font_size_items_list->get_child_count() - 1; i >= 0; i--) {
2587 Node *node = font_size_items_list->get_child(i);
2588 node->queue_free();
2589 font_size_items_list->remove_child(node);
2590 }
2591
2592 HashMap<StringName, bool> font_size_items = _get_type_items(edited_type, &Theme::get_font_size_list, show_default);
2593 for (const KeyValue<StringName, bool> &E : font_size_items) {
2594 HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT_SIZE, E.key, E.value);
2595 SpinBox *item_editor = memnew(SpinBox);
2596 item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
2597 item_editor->set_min(-100000);
2598 item_editor->set_max(100000);
2599 item_editor->set_step(1);
2600 item_editor->set_allow_lesser(true);
2601 item_editor->set_allow_greater(true);
2602 item_control->add_child(item_editor);
2603
2604 if (E.value) {
2605 item_editor->set_value(edited_theme->get_font_size(E.key, edited_type));
2606 item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_font_size_item_changed).bind(E.key));
2607 } else {
2608 item_editor->set_value(ThemeDB::get_singleton()->get_default_theme()->get_font_size(E.key, edited_type));
2609 item_editor->set_editable(false);
2610 }
2611
2612 _add_focusable(item_editor);
2613 font_size_items_list->add_child(item_control);
2614 }
2615 }
2616
2617 // Icons.
2618 {
2619 for (int i = icon_items_list->get_child_count() - 1; i >= 0; i--) {
2620 Node *node = icon_items_list->get_child(i);
2621 node->queue_free();
2622 icon_items_list->remove_child(node);
2623 }
2624
2625 HashMap<StringName, bool> icon_items = _get_type_items(edited_type, &Theme::get_icon_list, show_default);
2626 for (const KeyValue<StringName, bool> &E : icon_items) {
2627 HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_ICON, E.key, E.value);
2628 EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
2629 item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
2630 item_editor->set_base_type("Texture2D");
2631 item_control->add_child(item_editor);
2632
2633 if (E.value) {
2634 if (edited_theme->has_icon(E.key, edited_type)) {
2635 item_editor->set_edited_resource(edited_theme->get_icon(E.key, edited_type));
2636 } else {
2637 item_editor->set_edited_resource(Ref<Resource>());
2638 }
2639 item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
2640 item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_icon_item_changed).bind(E.key));
2641 } else {
2642 if (ThemeDB::get_singleton()->get_default_theme()->has_icon(E.key, edited_type)) {
2643 item_editor->set_edited_resource(ThemeDB::get_singleton()->get_default_theme()->get_icon(E.key, edited_type));
2644 } else {
2645 item_editor->set_edited_resource(Ref<Resource>());
2646 }
2647 item_editor->set_editable(false);
2648 }
2649
2650 _add_focusable(item_editor);
2651 icon_items_list->add_child(item_control);
2652 }
2653 }
2654
2655 // Styleboxes.
2656 {
2657 for (int i = stylebox_items_list->get_child_count() - 1; i >= 0; i--) {
2658 Node *node = stylebox_items_list->get_child(i);
2659 node->queue_free();
2660 stylebox_items_list->remove_child(node);
2661 }
2662
2663 if (leading_stylebox.pinned) {
2664 HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, leading_stylebox.item_name, true);
2665 EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
2666 item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
2667 item_editor->set_stretch_ratio(1.5);
2668 item_editor->set_base_type("StyleBox");
2669
2670 Button *pin_leader_button = memnew(Button);
2671 pin_leader_button->set_flat(true);
2672 pin_leader_button->set_toggle_mode(true);
2673 pin_leader_button->set_pressed(true);
2674 pin_leader_button->set_icon(get_editor_theme_icon(SNAME("Pin")));
2675 pin_leader_button->set_tooltip_text(TTR("Unpin this StyleBox as a main style."));
2676 item_control->add_child(pin_leader_button);
2677 pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_on_unpin_leader_button_pressed));
2678
2679 item_control->add_child(item_editor);
2680
2681 if (edited_theme->has_stylebox(leading_stylebox.item_name, edited_type)) {
2682 item_editor->set_edited_resource(leading_stylebox.stylebox);
2683 } else {
2684 item_editor->set_edited_resource(Ref<Resource>());
2685 }
2686 item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
2687 item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed).bind(leading_stylebox.item_name));
2688
2689 stylebox_items_list->add_child(item_control);
2690 stylebox_items_list->add_child(memnew(HSeparator));
2691 }
2692
2693 HashMap<StringName, bool> stylebox_items = _get_type_items(edited_type, &Theme::get_stylebox_list, show_default);
2694 for (const KeyValue<StringName, bool> &E : stylebox_items) {
2695 if (leading_stylebox.pinned && leading_stylebox.item_name == E.key) {
2696 continue;
2697 }
2698
2699 HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, E.key, E.value);
2700 EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
2701 item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
2702 item_editor->set_stretch_ratio(1.5);
2703 item_editor->set_base_type("StyleBox");
2704
2705 if (E.value) {
2706 if (edited_theme->has_stylebox(E.key, edited_type)) {
2707 item_editor->set_edited_resource(edited_theme->get_stylebox(E.key, edited_type));
2708 } else {
2709 item_editor->set_edited_resource(Ref<Resource>());
2710 }
2711 item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
2712 item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed).bind(E.key));
2713
2714 Button *pin_leader_button = memnew(Button);
2715 pin_leader_button->set_flat(true);
2716 pin_leader_button->set_toggle_mode(true);
2717 pin_leader_button->set_icon(get_editor_theme_icon(SNAME("Pin")));
2718 pin_leader_button->set_tooltip_text(TTR("Pin this StyleBox as a main style. Editing its properties will update the same properties in all other StyleBoxes of this type."));
2719 item_control->add_child(pin_leader_button);
2720 pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_on_pin_leader_button_pressed).bind(item_editor, E.key));
2721 } else {
2722 if (ThemeDB::get_singleton()->get_default_theme()->has_stylebox(E.key, edited_type)) {
2723 item_editor->set_edited_resource(ThemeDB::get_singleton()->get_default_theme()->get_stylebox(E.key, edited_type));
2724 } else {
2725 item_editor->set_edited_resource(Ref<Resource>());
2726 }
2727 item_editor->set_editable(false);
2728 }
2729
2730 item_control->add_child(item_editor);
2731 _add_focusable(item_editor);
2732 stylebox_items_list->add_child(item_control);
2733 }
2734 }
2735
2736 // Various type settings.
2737 if (edited_type.is_empty() || ClassDB::class_exists(edited_type)) {
2738 type_variation_edit->set_editable(false);
2739 type_variation_edit->set_text("");
2740 type_variation_button->hide();
2741 type_variation_locked->set_visible(!edited_type.is_empty());
2742 } else {
2743 type_variation_edit->set_editable(true);
2744 type_variation_edit->set_text(edited_theme->get_type_variation_base(edited_type));
2745 _add_focusable(type_variation_edit);
2746 type_variation_button->show();
2747 type_variation_locked->hide();
2748 }
2749}
2750
2751void ThemeTypeEditor::_list_type_selected(int p_index) {
2752 edited_type = theme_type_list->get_item_text(p_index);
2753 _update_type_items();
2754}
2755
2756void ThemeTypeEditor::_add_type_button_cbk() {
2757 add_type_mode = ADD_THEME_TYPE;
2758 add_type_dialog->set_title(TTR("Add Item Type"));
2759 add_type_dialog->set_ok_button_text(TTR("Add Type"));
2760 add_type_dialog->set_include_own_types(false);
2761 add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE);
2762}
2763
2764void ThemeTypeEditor::_add_default_type_items() {
2765 List<StringName> names;
2766 String default_type = edited_type;
2767 if (edited_theme->get_type_variation_base(edited_type) != StringName()) {
2768 default_type = edited_theme->get_type_variation_base(edited_type);
2769 }
2770
2771 Ref<Theme> old_snapshot = edited_theme->duplicate();
2772 Ref<Theme> new_snapshot = edited_theme->duplicate();
2773
2774 updating = true;
2775
2776 {
2777 names.clear();
2778 ThemeDB::get_singleton()->get_default_theme()->get_icon_list(default_type, &names);
2779 for (const StringName &E : names) {
2780 if (!new_snapshot->has_icon(E, edited_type)) {
2781 new_snapshot->set_icon(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_icon(E, edited_type));
2782 }
2783 }
2784 }
2785 {
2786 names.clear();
2787 ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(default_type, &names);
2788 for (const StringName &E : names) {
2789 if (!new_snapshot->has_stylebox(E, edited_type)) {
2790 new_snapshot->set_stylebox(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_stylebox(E, edited_type));
2791 }
2792 }
2793 }
2794 {
2795 names.clear();
2796 ThemeDB::get_singleton()->get_default_theme()->get_font_list(default_type, &names);
2797 for (const StringName &E : names) {
2798 if (!new_snapshot->has_font(E, edited_type)) {
2799 new_snapshot->set_font(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_font(E, edited_type));
2800 }
2801 }
2802 }
2803 {
2804 names.clear();
2805 ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(default_type, &names);
2806 for (const StringName &E : names) {
2807 if (!new_snapshot->has_font_size(E, edited_type)) {
2808 new_snapshot->set_font_size(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_font_size(E, edited_type));
2809 }
2810 }
2811 }
2812 {
2813 names.clear();
2814 ThemeDB::get_singleton()->get_default_theme()->get_color_list(default_type, &names);
2815 for (const StringName &E : names) {
2816 if (!new_snapshot->has_color(E, edited_type)) {
2817 new_snapshot->set_color(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_color(E, edited_type));
2818 }
2819 }
2820 }
2821 {
2822 names.clear();
2823 ThemeDB::get_singleton()->get_default_theme()->get_constant_list(default_type, &names);
2824 for (const StringName &E : names) {
2825 if (!new_snapshot->has_constant(E, edited_type)) {
2826 new_snapshot->set_constant(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_constant(E, edited_type));
2827 }
2828 }
2829 }
2830
2831 updating = false;
2832
2833 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
2834 ur->create_action(TTR("Override All Default Theme Items"));
2835
2836 ur->add_do_method(*edited_theme, "merge_with", new_snapshot);
2837 ur->add_undo_method(*edited_theme, "clear");
2838 ur->add_undo_method(*edited_theme, "merge_with", old_snapshot);
2839
2840 ur->add_do_method(this, "_update_type_items");
2841 ur->add_undo_method(this, "_update_type_items");
2842
2843 ur->commit_action();
2844}
2845
2846void ThemeTypeEditor::_item_add_cbk(int p_data_type, Control *p_control) {
2847 LineEdit *le = Object::cast_to<LineEdit>(p_control);
2848 if (le->get_text().strip_edges().is_empty()) {
2849 return;
2850 }
2851
2852 String item_name = le->get_text().strip_edges();
2853 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
2854 ur->create_action(TTR("Add Theme Item"));
2855
2856 switch (p_data_type) {
2857 case Theme::DATA_TYPE_COLOR: {
2858 ur->add_do_method(*edited_theme, "set_color", item_name, edited_type, Color());
2859 ur->add_undo_method(*edited_theme, "clear_color", item_name, edited_type);
2860 } break;
2861 case Theme::DATA_TYPE_CONSTANT: {
2862 ur->add_do_method(*edited_theme, "set_constant", item_name, edited_type, 0);
2863 ur->add_undo_method(*edited_theme, "clear_constant", item_name, edited_type);
2864 } break;
2865 case Theme::DATA_TYPE_FONT: {
2866 ur->add_do_method(*edited_theme, "set_font", item_name, edited_type, Ref<Font>());
2867 ur->add_undo_method(*edited_theme, "clear_font", item_name, edited_type);
2868 } break;
2869 case Theme::DATA_TYPE_FONT_SIZE: {
2870 ur->add_do_method(*edited_theme, "set_font_size", item_name, edited_type, -1);
2871 ur->add_undo_method(*edited_theme, "clear_font_size", item_name, edited_type);
2872 } break;
2873 case Theme::DATA_TYPE_ICON: {
2874 ur->add_do_method(*edited_theme, "set_icon", item_name, edited_type, Ref<Texture2D>());
2875 ur->add_undo_method(*edited_theme, "clear_icon", item_name, edited_type);
2876 } break;
2877 case Theme::DATA_TYPE_STYLEBOX: {
2878 Ref<StyleBox> sb;
2879 ur->add_do_method(*edited_theme, "set_stylebox", item_name, edited_type, sb);
2880 ur->add_undo_method(*edited_theme, "clear_stylebox", item_name, edited_type);
2881
2882 if (is_stylebox_pinned(sb)) {
2883 ur->add_undo_method(this, "_unpin_leading_stylebox");
2884 }
2885 } break;
2886 }
2887
2888 ur->commit_action();
2889
2890 le->set_text("");
2891}
2892
2893void ThemeTypeEditor::_item_add_lineedit_cbk(String p_value, int p_data_type, Control *p_control) {
2894 _item_add_cbk(p_data_type, p_control);
2895}
2896
2897void ThemeTypeEditor::_item_override_cbk(int p_data_type, String p_item_name) {
2898 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
2899 ur->create_action(TTR("Override Theme Item"));
2900
2901 switch (p_data_type) {
2902 case Theme::DATA_TYPE_COLOR: {
2903 ur->add_do_method(*edited_theme, "set_color", p_item_name, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_color(p_item_name, edited_type));
2904 ur->add_undo_method(*edited_theme, "clear_color", p_item_name, edited_type);
2905 } break;
2906 case Theme::DATA_TYPE_CONSTANT: {
2907 ur->add_do_method(*edited_theme, "set_constant", p_item_name, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_constant(p_item_name, edited_type));
2908 ur->add_undo_method(*edited_theme, "clear_constant", p_item_name, edited_type);
2909 } break;
2910 case Theme::DATA_TYPE_FONT: {
2911 ur->add_do_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>());
2912 ur->add_undo_method(*edited_theme, "clear_font", p_item_name, edited_type);
2913 } break;
2914 case Theme::DATA_TYPE_FONT_SIZE: {
2915 ur->add_do_method(*edited_theme, "set_font_size", p_item_name, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_font_size(p_item_name, edited_type));
2916 ur->add_undo_method(*edited_theme, "clear_font_size", p_item_name, edited_type);
2917 } break;
2918 case Theme::DATA_TYPE_ICON: {
2919 ur->add_do_method(*edited_theme, "set_icon", p_item_name, edited_type, Ref<Texture2D>());
2920 ur->add_undo_method(*edited_theme, "clear_icon", p_item_name, edited_type);
2921 } break;
2922 case Theme::DATA_TYPE_STYLEBOX: {
2923 Ref<StyleBox> sb;
2924 ur->add_do_method(*edited_theme, "set_stylebox", p_item_name, edited_type, sb);
2925 ur->add_undo_method(*edited_theme, "clear_stylebox", p_item_name, edited_type);
2926
2927 if (is_stylebox_pinned(sb)) {
2928 ur->add_undo_method(this, "_unpin_leading_stylebox");
2929 }
2930 } break;
2931 }
2932
2933 ur->commit_action();
2934}
2935
2936void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) {
2937 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
2938 ur->create_action(TTR("Remove Theme Item"));
2939
2940 switch (p_data_type) {
2941 case Theme::DATA_TYPE_COLOR: {
2942 ur->add_do_method(*edited_theme, "clear_color", p_item_name, edited_type);
2943 ur->add_undo_method(*edited_theme, "set_color", p_item_name, edited_type, edited_theme->get_color(p_item_name, edited_type));
2944 } break;
2945 case Theme::DATA_TYPE_CONSTANT: {
2946 ur->add_do_method(*edited_theme, "clear_constant", p_item_name, edited_type);
2947 ur->add_undo_method(*edited_theme, "set_constant", p_item_name, edited_type, edited_theme->get_constant(p_item_name, edited_type));
2948 } break;
2949 case Theme::DATA_TYPE_FONT: {
2950 ur->add_do_method(*edited_theme, "clear_font", p_item_name, edited_type);
2951 if (edited_theme->has_font(p_item_name, edited_type)) {
2952 ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, edited_theme->get_font(p_item_name, edited_type));
2953 } else {
2954 ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>());
2955 }
2956 } break;
2957 case Theme::DATA_TYPE_FONT_SIZE: {
2958 ur->add_do_method(*edited_theme, "clear_font_size", p_item_name, edited_type);
2959 ur->add_undo_method(*edited_theme, "set_font_size", p_item_name, edited_type, edited_theme->get_font_size(p_item_name, edited_type));
2960 } break;
2961 case Theme::DATA_TYPE_ICON: {
2962 ur->add_do_method(*edited_theme, "clear_icon", p_item_name, edited_type);
2963 if (edited_theme->has_icon(p_item_name, edited_type)) {
2964 ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, edited_theme->get_icon(p_item_name, edited_type));
2965 } else {
2966 ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, Ref<Texture2D>());
2967 }
2968 } break;
2969 case Theme::DATA_TYPE_STYLEBOX: {
2970 Ref<StyleBox> sb = edited_theme->get_stylebox(p_item_name, edited_type);
2971 ur->add_do_method(*edited_theme, "clear_stylebox", p_item_name, edited_type);
2972 if (edited_theme->has_stylebox(p_item_name, edited_type)) {
2973 ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, sb);
2974 } else {
2975 ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, Ref<StyleBox>());
2976 }
2977
2978 if (is_stylebox_pinned(sb)) {
2979 ur->add_do_method(this, "_unpin_leading_stylebox");
2980 ur->add_undo_method(this, "_pin_leading_stylebox", p_item_name, sb);
2981 }
2982 } break;
2983 }
2984
2985 ur->commit_action();
2986}
2987
2988void ThemeTypeEditor::_item_rename_cbk(int p_data_type, String p_item_name, Control *p_control) {
2989 // Label
2990 Object::cast_to<Label>(p_control->get_child(0))->hide();
2991 // Label buttons
2992 Object::cast_to<Button>(p_control->get_child(2))->hide();
2993 Object::cast_to<Button>(p_control->get_child(3))->hide();
2994
2995 // LineEdit
2996 Object::cast_to<LineEdit>(p_control->get_child(1))->set_text(p_item_name);
2997 Object::cast_to<LineEdit>(p_control->get_child(1))->show();
2998 // LineEdit buttons
2999 Object::cast_to<Button>(p_control->get_child(4))->show();
3000 Object::cast_to<Button>(p_control->get_child(5))->show();
3001}
3002
3003void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name, Control *p_control) {
3004 LineEdit *le = Object::cast_to<LineEdit>(p_control->get_child(1));
3005 if (le->get_text().strip_edges().is_empty()) {
3006 return;
3007 }
3008
3009 String new_name = le->get_text().strip_edges();
3010 if (new_name == p_item_name) {
3011 _item_rename_canceled(p_data_type, p_item_name, p_control);
3012 return;
3013 }
3014
3015 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3016 ur->create_action(TTR("Rename Theme Item"));
3017
3018 switch (p_data_type) {
3019 case Theme::DATA_TYPE_COLOR: {
3020 ur->add_do_method(*edited_theme, "rename_color", p_item_name, new_name, edited_type);
3021 ur->add_undo_method(*edited_theme, "rename_color", new_name, p_item_name, edited_type);
3022 } break;
3023 case Theme::DATA_TYPE_CONSTANT: {
3024 ur->add_do_method(*edited_theme, "rename_constant", p_item_name, new_name, edited_type);
3025 ur->add_undo_method(*edited_theme, "rename_constant", new_name, p_item_name, edited_type);
3026 } break;
3027 case Theme::DATA_TYPE_FONT: {
3028 ur->add_do_method(*edited_theme, "rename_font", p_item_name, new_name, edited_type);
3029 ur->add_undo_method(*edited_theme, "rename_font", new_name, p_item_name, edited_type);
3030 } break;
3031 case Theme::DATA_TYPE_FONT_SIZE: {
3032 ur->add_do_method(*edited_theme, "rename_font_size", p_item_name, new_name, edited_type);
3033 ur->add_undo_method(*edited_theme, "rename_font_size", new_name, p_item_name, edited_type);
3034 } break;
3035 case Theme::DATA_TYPE_ICON: {
3036 ur->add_do_method(*edited_theme, "rename_icon", p_item_name, new_name, edited_type);
3037 ur->add_undo_method(*edited_theme, "rename_icon", new_name, p_item_name, edited_type);
3038 } break;
3039 case Theme::DATA_TYPE_STYLEBOX: {
3040 ur->add_do_method(*edited_theme, "rename_stylebox", p_item_name, new_name, edited_type);
3041 ur->add_undo_method(*edited_theme, "rename_stylebox", new_name, p_item_name, edited_type);
3042
3043 if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) {
3044 leading_stylebox.item_name = new_name;
3045 }
3046 } break;
3047 }
3048
3049 ur->commit_action();
3050}
3051
3052void ThemeTypeEditor::_item_rename_entered(String p_value, int p_data_type, String p_item_name, Control *p_control) {
3053 _item_rename_confirmed(p_data_type, p_item_name, p_control);
3054}
3055
3056void ThemeTypeEditor::_item_rename_canceled(int p_data_type, String p_item_name, Control *p_control) {
3057 // LineEdit
3058 Object::cast_to<LineEdit>(p_control->get_child(1))->hide();
3059 // LineEdit buttons
3060 Object::cast_to<Button>(p_control->get_child(4))->hide();
3061 Object::cast_to<Button>(p_control->get_child(5))->hide();
3062
3063 // Label
3064 Object::cast_to<Label>(p_control->get_child(0))->show();
3065 // Label buttons
3066 Object::cast_to<Button>(p_control->get_child(2))->show();
3067 Object::cast_to<Button>(p_control->get_child(3))->show();
3068}
3069
3070void ThemeTypeEditor::_color_item_changed(Color p_value, String p_item_name) {
3071 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3072 ur->create_action(TTR("Set Color Item in Theme"), UndoRedo::MERGE_ENDS);
3073 ur->add_do_method(*edited_theme, "set_color", p_item_name, edited_type, p_value);
3074 ur->add_undo_method(*edited_theme, "set_color", p_item_name, edited_type, edited_theme->get_color(p_item_name, edited_type));
3075 ur->commit_action();
3076}
3077
3078void ThemeTypeEditor::_constant_item_changed(float p_value, String p_item_name) {
3079 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3080 ur->create_action(TTR("Set Constant Item in Theme"));
3081 ur->add_do_method(*edited_theme, "set_constant", p_item_name, edited_type, p_value);
3082 ur->add_undo_method(*edited_theme, "set_constant", p_item_name, edited_type, edited_theme->get_constant(p_item_name, edited_type));
3083 ur->commit_action();
3084}
3085
3086void ThemeTypeEditor::_font_size_item_changed(float p_value, String p_item_name) {
3087 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3088 ur->create_action(TTR("Set Font Size Item in Theme"));
3089 ur->add_do_method(*edited_theme, "set_font_size", p_item_name, edited_type, p_value);
3090 ur->add_undo_method(*edited_theme, "set_font_size", p_item_name, edited_type, edited_theme->get_font_size(p_item_name, edited_type));
3091 ur->commit_action();
3092}
3093
3094void ThemeTypeEditor::_edit_resource_item(Ref<Resource> p_resource, bool p_edit) {
3095 EditorNode::get_singleton()->edit_resource(p_resource);
3096}
3097
3098void ThemeTypeEditor::_font_item_changed(Ref<Font> p_value, String p_item_name) {
3099 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3100 ur->create_action(TTR("Set Font Item in Theme"));
3101
3102 ur->add_do_method(*edited_theme, "set_font", p_item_name, edited_type, p_value.is_valid() ? p_value : Ref<Font>());
3103 if (edited_theme->has_font(p_item_name, edited_type)) {
3104 ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, edited_theme->get_font(p_item_name, edited_type));
3105 } else {
3106 ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>());
3107 }
3108
3109 ur->add_do_method(this, "call_deferred", "_update_type_items");
3110 ur->add_undo_method(this, "call_deferred", "_update_type_items");
3111
3112 ur->commit_action();
3113}
3114
3115void ThemeTypeEditor::_icon_item_changed(Ref<Texture2D> p_value, String p_item_name) {
3116 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3117 ur->create_action(TTR("Set Icon Item in Theme"));
3118
3119 ur->add_do_method(*edited_theme, "set_icon", p_item_name, edited_type, p_value.is_valid() ? p_value : Ref<Texture2D>());
3120 if (edited_theme->has_icon(p_item_name, edited_type)) {
3121 ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, edited_theme->get_icon(p_item_name, edited_type));
3122 } else {
3123 ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, Ref<Texture2D>());
3124 }
3125
3126 ur->add_do_method(this, "call_deferred", "_update_type_items");
3127 ur->add_undo_method(this, "call_deferred", "_update_type_items");
3128
3129 ur->commit_action();
3130}
3131
3132void ThemeTypeEditor::_stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name) {
3133 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3134 ur->create_action(TTR("Set Stylebox Item in Theme"));
3135
3136 ur->add_do_method(*edited_theme, "set_stylebox", p_item_name, edited_type, p_value.is_valid() ? p_value : Ref<StyleBox>());
3137 if (edited_theme->has_stylebox(p_item_name, edited_type)) {
3138 ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, edited_theme->get_stylebox(p_item_name, edited_type));
3139 } else {
3140 ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, Ref<StyleBox>());
3141 }
3142
3143 ur->add_do_method(this, "_change_pinned_stylebox");
3144 ur->add_undo_method(this, "_change_pinned_stylebox");
3145
3146 ur->add_do_method(this, "call_deferred", "_update_type_items");
3147 ur->add_undo_method(this, "call_deferred", "_update_type_items");
3148
3149 ur->commit_action();
3150}
3151
3152void ThemeTypeEditor::_change_pinned_stylebox() {
3153 if (leading_stylebox.pinned) {
3154 if (leading_stylebox.stylebox.is_valid()) {
3155 leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
3156 }
3157
3158 Ref<StyleBox> new_stylebox = edited_theme->get_stylebox(leading_stylebox.item_name, edited_type);
3159 leading_stylebox.stylebox = new_stylebox;
3160 leading_stylebox.ref_stylebox = (new_stylebox.is_valid() ? new_stylebox->duplicate() : Ref<Resource>());
3161
3162 if (leading_stylebox.stylebox.is_valid()) {
3163 new_stylebox->connect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
3164 }
3165 } else if (leading_stylebox.stylebox.is_valid()) {
3166 leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
3167 }
3168}
3169
3170void ThemeTypeEditor::_on_pin_leader_button_pressed(Control *p_editor, String p_item_name) {
3171 Ref<StyleBox> stylebox;
3172 if (Object::cast_to<EditorResourcePicker>(p_editor)) {
3173 stylebox = Object::cast_to<EditorResourcePicker>(p_editor)->get_edited_resource();
3174 }
3175
3176 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3177 ur->create_action(TTR("Pin Stylebox"));
3178 ur->add_do_method(this, "_pin_leading_stylebox", p_item_name, stylebox);
3179
3180 if (leading_stylebox.pinned) {
3181 ur->add_undo_method(this, "_pin_leading_stylebox", leading_stylebox.item_name, leading_stylebox.stylebox);
3182 } else {
3183 ur->add_undo_method(this, "_unpin_leading_stylebox");
3184 }
3185
3186 ur->commit_action();
3187}
3188
3189void ThemeTypeEditor::_pin_leading_stylebox(String p_item_name, Ref<StyleBox> p_stylebox) {
3190 if (leading_stylebox.stylebox.is_valid()) {
3191 leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
3192 }
3193
3194 LeadingStylebox leader;
3195 leader.pinned = true;
3196 leader.item_name = p_item_name;
3197 leader.stylebox = p_stylebox;
3198 leader.ref_stylebox = (p_stylebox.is_valid() ? p_stylebox->duplicate() : Ref<Resource>());
3199
3200 leading_stylebox = leader;
3201 if (p_stylebox.is_valid()) {
3202 p_stylebox->connect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
3203 }
3204
3205 _update_type_items();
3206}
3207
3208void ThemeTypeEditor::_on_unpin_leader_button_pressed() {
3209 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3210 ur->create_action(TTR("Unpin Stylebox"));
3211 ur->add_do_method(this, "_unpin_leading_stylebox");
3212 ur->add_undo_method(this, "_pin_leading_stylebox", leading_stylebox.item_name, leading_stylebox.stylebox);
3213 ur->commit_action();
3214}
3215
3216void ThemeTypeEditor::_unpin_leading_stylebox() {
3217 if (leading_stylebox.stylebox.is_valid()) {
3218 leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
3219 }
3220
3221 LeadingStylebox leader;
3222 leader.pinned = false;
3223 leading_stylebox = leader;
3224
3225 _update_type_items();
3226}
3227
3228void ThemeTypeEditor::_update_stylebox_from_leading() {
3229 if (!leading_stylebox.pinned || leading_stylebox.stylebox.is_null()) {
3230 return;
3231 }
3232 ERR_FAIL_COND_MSG(edited_theme.is_null(), "Leading stylebox does not have an edited theme to update");
3233
3234 // Prevent changes from immediately being reported while the operation is still ongoing.
3235 edited_theme->_freeze_change_propagation();
3236
3237 List<StringName> names;
3238 edited_theme->get_stylebox_list(edited_type, &names);
3239 List<Ref<StyleBox>> styleboxes;
3240 for (const StringName &E : names) {
3241 Ref<StyleBox> sb = edited_theme->get_stylebox(E, edited_type);
3242
3243 // Avoid itself, stylebox can be shared between items.
3244 if (sb == leading_stylebox.stylebox) {
3245 continue;
3246 }
3247
3248 if (sb->get_class() == leading_stylebox.stylebox->get_class()) {
3249 styleboxes.push_back(sb);
3250 }
3251 }
3252
3253 List<PropertyInfo> props;
3254 leading_stylebox.stylebox->get_property_list(&props);
3255 for (const PropertyInfo &E : props) {
3256 if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
3257 continue;
3258 }
3259
3260 Variant value = leading_stylebox.stylebox->get(E.name);
3261 Variant ref_value = leading_stylebox.ref_stylebox->get(E.name);
3262 if (value == ref_value) {
3263 continue;
3264 }
3265
3266 for (const Ref<StyleBox> &F : styleboxes) {
3267 Ref<StyleBox> sb = F;
3268 sb->set(E.name, value);
3269 }
3270 }
3271
3272 leading_stylebox.ref_stylebox = leading_stylebox.stylebox->duplicate();
3273
3274 // Allow changes to be reported now that the operation is finished.
3275 edited_theme->_unfreeze_and_propagate_changes();
3276}
3277
3278void ThemeTypeEditor::_type_variation_changed(const String p_value) {
3279 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
3280 ur->create_action(TTR("Set Theme Type Variation"));
3281
3282 if (p_value.is_empty()) {
3283 ur->add_do_method(*edited_theme, "clear_type_variation", edited_type);
3284 } else {
3285 ur->add_do_method(*edited_theme, "set_type_variation", edited_type, StringName(p_value));
3286 }
3287
3288 if (edited_theme->get_type_variation_base(edited_type) == "") {
3289 ur->add_undo_method(*edited_theme, "clear_type_variation", edited_type);
3290 } else {
3291 ur->add_undo_method(*edited_theme, "set_type_variation", edited_type, edited_theme->get_type_variation_base(edited_type));
3292 }
3293
3294 ur->commit_action();
3295}
3296
3297void ThemeTypeEditor::_add_type_variation_cbk() {
3298 add_type_mode = ADD_VARIATION_BASE;
3299 add_type_dialog->set_title(TTR("Set Variation Base Type"));
3300 add_type_dialog->set_ok_button_text(TTR("Set Base Type"));
3301 add_type_dialog->set_include_own_types(true);
3302 add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE);
3303}
3304
3305void ThemeTypeEditor::_add_type_dialog_selected(const String p_type_name) {
3306 if (add_type_mode == ADD_THEME_TYPE) {
3307 select_type(p_type_name);
3308 } else if (add_type_mode == ADD_VARIATION_BASE) {
3309 _type_variation_changed(p_type_name);
3310 }
3311}
3312
3313void ThemeTypeEditor::_notification(int p_what) {
3314 switch (p_what) {
3315 case NOTIFICATION_ENTER_TREE:
3316 case NOTIFICATION_THEME_CHANGED: {
3317 add_type_button->set_icon(get_editor_theme_icon(SNAME("Add")));
3318
3319 data_type_tabs->set_tab_icon(0, get_editor_theme_icon(SNAME("Color")));
3320 data_type_tabs->set_tab_icon(1, get_editor_theme_icon(SNAME("MemberConstant")));
3321 data_type_tabs->set_tab_icon(2, get_editor_theme_icon(SNAME("Font")));
3322 data_type_tabs->set_tab_icon(3, get_editor_theme_icon(SNAME("FontSize")));
3323 data_type_tabs->set_tab_icon(4, get_editor_theme_icon(SNAME("ImageTexture")));
3324 data_type_tabs->set_tab_icon(5, get_editor_theme_icon(SNAME("StyleBoxFlat")));
3325 data_type_tabs->set_tab_icon(6, get_editor_theme_icon(SNAME("Tools")));
3326
3327 type_variation_button->set_icon(get_editor_theme_icon(SNAME("Add")));
3328 } break;
3329 }
3330}
3331
3332void ThemeTypeEditor::_bind_methods() {
3333 ClassDB::bind_method(D_METHOD("_update_type_items"), &ThemeTypeEditor::_update_type_items);
3334 ClassDB::bind_method(D_METHOD("_pin_leading_stylebox"), &ThemeTypeEditor::_pin_leading_stylebox);
3335 ClassDB::bind_method(D_METHOD("_unpin_leading_stylebox"), &ThemeTypeEditor::_unpin_leading_stylebox);
3336 ClassDB::bind_method(D_METHOD("_change_pinned_stylebox"), &ThemeTypeEditor::_change_pinned_stylebox);
3337}
3338
3339void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) {
3340 if (edited_theme.is_valid()) {
3341 edited_theme->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced));
3342 }
3343
3344 edited_theme = p_theme;
3345 if (edited_theme.is_valid()) {
3346 edited_theme->connect_changed(callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced));
3347 _update_type_list();
3348 }
3349
3350 add_type_dialog->set_edited_theme(edited_theme);
3351}
3352
3353void ThemeTypeEditor::select_type(String p_type_name) {
3354 edited_type = p_type_name;
3355 bool type_exists = false;
3356
3357 for (int i = 0; i < theme_type_list->get_item_count(); i++) {
3358 String type_name = theme_type_list->get_item_text(i);
3359 if (type_name == edited_type) {
3360 theme_type_list->select(i);
3361 type_exists = true;
3362 break;
3363 }
3364 }
3365
3366 if (type_exists) {
3367 _update_type_items();
3368 } else {
3369 edited_theme->add_icon_type(edited_type);
3370 edited_theme->add_stylebox_type(edited_type);
3371 edited_theme->add_font_type(edited_type);
3372 edited_theme->add_font_size_type(edited_type);
3373 edited_theme->add_color_type(edited_type);
3374 edited_theme->add_constant_type(edited_type);
3375
3376 _update_type_list();
3377 }
3378}
3379
3380bool ThemeTypeEditor::is_stylebox_pinned(Ref<StyleBox> p_stylebox) {
3381 return leading_stylebox.pinned && leading_stylebox.stylebox == p_stylebox;
3382}
3383
3384ThemeTypeEditor::ThemeTypeEditor() {
3385 VBoxContainer *main_vb = memnew(VBoxContainer);
3386 add_child(main_vb);
3387
3388 HBoxContainer *type_list_hb = memnew(HBoxContainer);
3389 main_vb->add_child(type_list_hb);
3390
3391 Label *type_list_label = memnew(Label);
3392 type_list_label->set_text(TTR("Type:"));
3393 type_list_hb->add_child(type_list_label);
3394
3395 theme_type_list = memnew(OptionButton);
3396 theme_type_list->set_h_size_flags(SIZE_EXPAND_FILL);
3397 theme_type_list->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
3398 type_list_hb->add_child(theme_type_list);
3399 theme_type_list->connect("item_selected", callable_mp(this, &ThemeTypeEditor::_list_type_selected));
3400
3401 add_type_button = memnew(Button);
3402 add_type_button->set_tooltip_text(TTR("Add a type from a list of available types or create a new one."));
3403 type_list_hb->add_child(add_type_button);
3404 add_type_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_button_cbk));
3405
3406 HBoxContainer *type_controls = memnew(HBoxContainer);
3407 main_vb->add_child(type_controls);
3408
3409 show_default_items_button = memnew(CheckButton);
3410 show_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL);
3411 show_default_items_button->set_text(TTR("Show Default"));
3412 show_default_items_button->set_tooltip_text(TTR("Show default type items alongside items that have been overridden."));
3413 show_default_items_button->set_pressed(true);
3414 type_controls->add_child(show_default_items_button);
3415 show_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_update_type_items));
3416
3417 Button *add_default_items_button = memnew(Button);
3418 add_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL);
3419 add_default_items_button->set_text(TTR("Override All"));
3420 add_default_items_button->set_tooltip_text(TTR("Override all default type items."));
3421 type_controls->add_child(add_default_items_button);
3422 add_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_default_type_items));
3423
3424 data_type_tabs = memnew(TabContainer);
3425 data_type_tabs->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
3426 main_vb->add_child(data_type_tabs);
3427 data_type_tabs->set_v_size_flags(SIZE_EXPAND_FILL);
3428 data_type_tabs->set_use_hidden_tabs_for_min_size(true);
3429 data_type_tabs->set_theme_type_variation("TabContainerOdd");
3430
3431 color_items_list = _create_item_list(Theme::DATA_TYPE_COLOR);
3432 constant_items_list = _create_item_list(Theme::DATA_TYPE_CONSTANT);
3433 font_items_list = _create_item_list(Theme::DATA_TYPE_FONT);
3434 font_size_items_list = _create_item_list(Theme::DATA_TYPE_FONT_SIZE);
3435 icon_items_list = _create_item_list(Theme::DATA_TYPE_ICON);
3436 stylebox_items_list = _create_item_list(Theme::DATA_TYPE_STYLEBOX);
3437
3438 VBoxContainer *type_settings_tab = memnew(VBoxContainer);
3439 type_settings_tab->set_custom_minimum_size(Size2(0, 160) * EDSCALE);
3440 data_type_tabs->add_child(type_settings_tab);
3441 data_type_tabs->set_tab_title(data_type_tabs->get_tab_count() - 1, "");
3442
3443 ScrollContainer *type_settings_sc = memnew(ScrollContainer);
3444 type_settings_sc->set_v_size_flags(SIZE_EXPAND_FILL);
3445 type_settings_sc->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
3446 type_settings_tab->add_child(type_settings_sc);
3447 VBoxContainer *type_settings_list = memnew(VBoxContainer);
3448 type_settings_list->set_h_size_flags(SIZE_EXPAND_FILL);
3449 type_settings_sc->add_child(type_settings_list);
3450
3451 VBoxContainer *type_variation_vb = memnew(VBoxContainer);
3452 type_settings_list->add_child(type_variation_vb);
3453
3454 HBoxContainer *type_variation_hb = memnew(HBoxContainer);
3455 type_variation_vb->add_child(type_variation_hb);
3456 Label *type_variation_label = memnew(Label);
3457 type_variation_hb->add_child(type_variation_label);
3458 type_variation_label->set_text(TTR("Base Type"));
3459 type_variation_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
3460 type_variation_edit = memnew(LineEdit);
3461 type_variation_hb->add_child(type_variation_edit);
3462 type_variation_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
3463 type_variation_edit->connect("text_changed", callable_mp(this, &ThemeTypeEditor::_type_variation_changed));
3464 type_variation_edit->connect("focus_exited", callable_mp(this, &ThemeTypeEditor::_update_type_items));
3465 type_variation_button = memnew(Button);
3466 type_variation_hb->add_child(type_variation_button);
3467 type_variation_button->set_tooltip_text(TTR("Select the variation base type from a list of available types."));
3468 type_variation_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_variation_cbk));
3469
3470 type_variation_locked = memnew(Label);
3471 type_variation_vb->add_child(type_variation_locked);
3472 type_variation_locked->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
3473 type_variation_locked->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
3474 type_variation_locked->set_text(TTR("A type associated with a built-in class cannot be marked as a variation of another type."));
3475 type_variation_locked->hide();
3476
3477 add_type_dialog = memnew(ThemeTypeDialog);
3478 add_child(add_type_dialog);
3479 add_type_dialog->connect("type_selected", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_selected));
3480
3481 update_debounce_timer = memnew(Timer);
3482 update_debounce_timer->set_one_shot(true);
3483 update_debounce_timer->set_wait_time(0.5);
3484 update_debounce_timer->connect("timeout", callable_mp(this, &ThemeTypeEditor::_update_type_list));
3485 add_child(update_debounce_timer);
3486}
3487
3488///////////////////////
3489
3490void ThemeEditor::edit(const Ref<Theme> &p_theme) {
3491 if (theme == p_theme) {
3492 return;
3493 }
3494
3495 theme = p_theme;
3496 theme_type_editor->set_edited_theme(p_theme);
3497 theme_edit_dialog->set_edited_theme(p_theme);
3498
3499 for (int i = 0; i < preview_tabs_content->get_child_count(); i++) {
3500 ThemeEditorPreview *preview_tab = Object::cast_to<ThemeEditorPreview>(preview_tabs_content->get_child(i));
3501 if (!preview_tab) {
3502 continue;
3503 }
3504
3505 preview_tab->set_preview_theme(p_theme);
3506 }
3507
3508 if (theme.is_valid()) {
3509 theme_name->set_text(TTR("Theme:") + " " + theme->get_path().get_file());
3510 }
3511}
3512
3513Ref<Theme> ThemeEditor::get_edited_theme() {
3514 return theme;
3515}
3516
3517void ThemeEditor::_theme_save_button_cbk(bool p_save_as) {
3518 ERR_FAIL_COND_MSG(theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing.");
3519
3520 if (p_save_as) {
3521 EditorNode::get_singleton()->save_resource_as(theme);
3522 } else {
3523 EditorNode::get_singleton()->save_resource(theme);
3524 }
3525}
3526
3527void ThemeEditor::_theme_edit_button_cbk() {
3528 theme_edit_dialog->popup_centered(Size2(850, 700) * EDSCALE);
3529}
3530
3531void ThemeEditor::_add_preview_button_cbk() {
3532 preview_scene_dialog->popup_file_dialog();
3533}
3534
3535void ThemeEditor::_preview_scene_dialog_cbk(const String &p_path) {
3536 SceneThemeEditorPreview *preview_tab = memnew(SceneThemeEditorPreview);
3537 if (!preview_tab->set_preview_scene(p_path)) {
3538 return;
3539 }
3540
3541 _add_preview_tab(preview_tab, p_path.get_file(), get_editor_theme_icon(SNAME("PackedScene")));
3542 preview_tab->connect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid).bind(preview_tab));
3543 preview_tab->connect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab).bind(preview_tab));
3544}
3545
3546void ThemeEditor::_add_preview_tab(ThemeEditorPreview *p_preview_tab, const String &p_preview_name, const Ref<Texture2D> &p_icon) {
3547 p_preview_tab->set_preview_theme(theme);
3548
3549 preview_tabs->add_tab(p_preview_name, p_icon);
3550 preview_tabs_content->add_child(p_preview_tab);
3551 preview_tabs->set_tab_button_icon(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("close"), SNAME("TabBar")));
3552 p_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked));
3553
3554 preview_tabs->set_current_tab(preview_tabs->get_tab_count() - 1);
3555}
3556
3557void ThemeEditor::_change_preview_tab(int p_tab) {
3558 ERR_FAIL_INDEX_MSG(p_tab, preview_tabs_content->get_child_count(), "Attempting to open a preview tab that doesn't exist.");
3559
3560 for (int i = 0; i < preview_tabs_content->get_child_count(); i++) {
3561 Control *c = Object::cast_to<Control>(preview_tabs_content->get_child(i));
3562 if (!c) {
3563 continue;
3564 }
3565
3566 c->set_visible(i == p_tab);
3567 }
3568}
3569
3570void ThemeEditor::_remove_preview_tab(int p_tab) {
3571 ERR_FAIL_INDEX_MSG(p_tab, preview_tabs_content->get_child_count(), "Attempting to remove a preview tab that doesn't exist.");
3572
3573 ThemeEditorPreview *preview_tab = Object::cast_to<ThemeEditorPreview>(preview_tabs_content->get_child(p_tab));
3574 ERR_FAIL_COND_MSG(Object::cast_to<DefaultThemeEditorPreview>(preview_tab), "Attemptying to remove the default preview tab.");
3575
3576 if (preview_tab) {
3577 preview_tab->disconnect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked));
3578 if (preview_tab->is_connected("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid))) {
3579 preview_tab->disconnect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid));
3580 }
3581 if (preview_tab->is_connected("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab))) {
3582 preview_tab->disconnect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab));
3583 }
3584
3585 preview_tabs_content->remove_child(preview_tab);
3586 preview_tabs->remove_tab(p_tab);
3587 _change_preview_tab(preview_tabs->get_current_tab());
3588 }
3589}
3590
3591void ThemeEditor::_remove_preview_tab_invalid(Node *p_tab_control) {
3592 int tab_index = p_tab_control->get_index();
3593 _remove_preview_tab(tab_index);
3594}
3595
3596void ThemeEditor::_update_preview_tab(Node *p_tab_control) {
3597 if (!Object::cast_to<SceneThemeEditorPreview>(p_tab_control)) {
3598 return;
3599 }
3600
3601 int tab_index = p_tab_control->get_index();
3602 SceneThemeEditorPreview *scene_preview = Object::cast_to<SceneThemeEditorPreview>(p_tab_control);
3603 preview_tabs->set_tab_title(tab_index, scene_preview->get_preview_scene_path().get_file());
3604}
3605
3606void ThemeEditor::_preview_control_picked(String p_class_name) {
3607 theme_type_editor->select_type(p_class_name);
3608}
3609
3610void ThemeEditor::_notification(int p_what) {
3611 switch (p_what) {
3612 case NOTIFICATION_ENTER_TREE:
3613 case NOTIFICATION_THEME_CHANGED: {
3614 preview_tabs->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("ThemeEditorPreviewFG"), EditorStringName(EditorStyles)));
3615 preview_tabs->add_theme_style_override("tab_unselected", get_theme_stylebox(SNAME("ThemeEditorPreviewBG"), EditorStringName(EditorStyles)));
3616 preview_tabs_content->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainerOdd")));
3617
3618 add_preview_button->set_icon(get_editor_theme_icon(SNAME("Add")));
3619 } break;
3620 }
3621}
3622
3623ThemeEditor::ThemeEditor() {
3624 HBoxContainer *top_menu = memnew(HBoxContainer);
3625 add_child(top_menu);
3626
3627 theme_name = memnew(Label);
3628 theme_name->set_text(TTR("Theme:"));
3629 theme_name->set_theme_type_variation("HeaderSmall");
3630 top_menu->add_child(theme_name);
3631
3632 top_menu->add_spacer(false);
3633
3634 Button *theme_save_button = memnew(Button);
3635 theme_save_button->set_text(TTR("Save"));
3636 theme_save_button->set_flat(true);
3637 theme_save_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk).bind(false));
3638 top_menu->add_child(theme_save_button);
3639
3640 Button *theme_save_as_button = memnew(Button);
3641 theme_save_as_button->set_text(TTR("Save As..."));
3642 theme_save_as_button->set_flat(true);
3643 theme_save_as_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk).bind(true));
3644 top_menu->add_child(theme_save_as_button);
3645
3646 top_menu->add_child(memnew(VSeparator));
3647
3648 Button *theme_edit_button = memnew(Button);
3649 theme_edit_button->set_text(TTR("Manage Items..."));
3650 theme_edit_button->set_tooltip_text(TTR("Add, remove, organize and import Theme items."));
3651 theme_edit_button->set_flat(true);
3652 theme_edit_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_edit_button_cbk));
3653 top_menu->add_child(theme_edit_button);
3654
3655 theme_type_editor = memnew(ThemeTypeEditor);
3656
3657 theme_edit_dialog = memnew(ThemeItemEditorDialog(theme_type_editor));
3658 theme_edit_dialog->hide();
3659 top_menu->add_child(theme_edit_dialog);
3660
3661 HSplitContainer *main_hs = memnew(HSplitContainer);
3662 main_hs->set_v_size_flags(SIZE_EXPAND_FILL);
3663 add_child(main_hs);
3664
3665 VBoxContainer *preview_tabs_vb = memnew(VBoxContainer);
3666 preview_tabs_vb->set_h_size_flags(SIZE_EXPAND_FILL);
3667 preview_tabs_vb->set_custom_minimum_size(Size2(520, 0) * EDSCALE);
3668 preview_tabs_vb->add_theme_constant_override("separation", 2 * EDSCALE);
3669 main_hs->add_child(preview_tabs_vb);
3670 HBoxContainer *preview_tabbar_hb = memnew(HBoxContainer);
3671 preview_tabs_vb->add_child(preview_tabbar_hb);
3672 preview_tabs_content = memnew(PanelContainer);
3673 preview_tabs_content->set_v_size_flags(SIZE_EXPAND_FILL);
3674 preview_tabs_content->set_draw_behind_parent(true);
3675 preview_tabs_vb->add_child(preview_tabs_content);
3676
3677 preview_tabs = memnew(TabBar);
3678 preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL);
3679 preview_tabbar_hb->add_child(preview_tabs);
3680 preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab));
3681 preview_tabs->connect("tab_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab));
3682
3683 HBoxContainer *add_preview_button_hb = memnew(HBoxContainer);
3684 preview_tabbar_hb->add_child(add_preview_button_hb);
3685 add_preview_button = memnew(Button);
3686 add_preview_button->set_text(TTR("Add Preview"));
3687 add_preview_button_hb->add_child(add_preview_button);
3688 add_preview_button->connect("pressed", callable_mp(this, &ThemeEditor::_add_preview_button_cbk));
3689
3690 DefaultThemeEditorPreview *default_preview_tab = memnew(DefaultThemeEditorPreview);
3691 preview_tabs_content->add_child(default_preview_tab);
3692 default_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked));
3693 preview_tabs->add_tab(TTR("Default Preview"));
3694
3695 preview_scene_dialog = memnew(EditorFileDialog);
3696 preview_scene_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
3697 preview_scene_dialog->set_title(TTR("Select UI Scene:"));
3698 List<String> ext;
3699 ResourceLoader::get_recognized_extensions_for_type("PackedScene", &ext);
3700 for (const String &E : ext) {
3701 preview_scene_dialog->add_filter("*." + E, TTR("Scene"));
3702 }
3703 main_hs->add_child(preview_scene_dialog);
3704 preview_scene_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_preview_scene_dialog_cbk));
3705
3706 main_hs->add_child(theme_type_editor);
3707 theme_type_editor->set_custom_minimum_size(Size2(280, 0) * EDSCALE);
3708}
3709
3710///////////////////////
3711
3712void ThemeEditorPlugin::edit(Object *p_node) {
3713 if (Object::cast_to<Theme>(p_node)) {
3714 theme_editor->edit(Object::cast_to<Theme>(p_node));
3715 } else {
3716 // We intentionally keep a reference to the last used theme to work around
3717 // the the editor being hidden while base resources are edited. Uncomment
3718 // the following line again and remove this comment once that bug has been
3719 // fixed (scheduled for Godot 4.1 in PR 73098):
3720 // theme_editor->edit(Ref<Theme>());
3721 }
3722}
3723
3724bool ThemeEditorPlugin::handles(Object *p_node) const {
3725 return Object::cast_to<Theme>(p_node) != nullptr;
3726}
3727
3728void ThemeEditorPlugin::make_visible(bool p_visible) {
3729 if (p_visible) {
3730 button->show();
3731 EditorNode::get_singleton()->make_bottom_panel_item_visible(theme_editor);
3732 } else {
3733 if (theme_editor->is_visible_in_tree()) {
3734 EditorNode::get_singleton()->hide_bottom_panel();
3735 }
3736
3737 button->hide();
3738 }
3739}
3740
3741ThemeEditorPlugin::ThemeEditorPlugin() {
3742 theme_editor = memnew(ThemeEditor);
3743 theme_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
3744
3745 button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Theme"), theme_editor);
3746 button->hide();
3747}
3748