1/**************************************************************************/
2/* font_config_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 "font_config_plugin.h"
32
33#include "editor/editor_scale.h"
34#include "editor/editor_settings.h"
35#include "editor/import/dynamic_font_import_settings.h"
36
37/*************************************************************************/
38/* EditorPropertyFontMetaObject */
39/*************************************************************************/
40
41bool EditorPropertyFontMetaObject::_set(const StringName &p_name, const Variant &p_value) {
42 String name = p_name;
43
44 if (name.begins_with("keys")) {
45 String key = name.get_slicec('/', 1);
46 dict[key] = p_value;
47 return true;
48 }
49
50 return false;
51}
52
53bool EditorPropertyFontMetaObject::_get(const StringName &p_name, Variant &r_ret) const {
54 String name = p_name;
55
56 if (name.begins_with("keys")) {
57 String key = name.get_slicec('/', 1);
58 r_ret = dict[key];
59 return true;
60 }
61
62 return false;
63}
64
65void EditorPropertyFontMetaObject::_bind_methods() {
66}
67
68void EditorPropertyFontMetaObject::set_dict(const Dictionary &p_dict) {
69 dict = p_dict;
70}
71
72Dictionary EditorPropertyFontMetaObject::get_dict() {
73 return dict;
74}
75
76/*************************************************************************/
77/* EditorPropertyFontOTObject */
78/*************************************************************************/
79
80bool EditorPropertyFontOTObject::_set(const StringName &p_name, const Variant &p_value) {
81 String name = p_name;
82
83 if (name.begins_with("keys")) {
84 int key = name.get_slicec('/', 1).to_int();
85 dict[key] = p_value;
86 return true;
87 }
88
89 return false;
90}
91
92bool EditorPropertyFontOTObject::_get(const StringName &p_name, Variant &r_ret) const {
93 String name = p_name;
94
95 if (name.begins_with("keys")) {
96 int key = name.get_slicec('/', 1).to_int();
97 r_ret = dict[key];
98 return true;
99 }
100
101 return false;
102}
103
104void EditorPropertyFontOTObject::set_dict(const Dictionary &p_dict) {
105 dict = p_dict;
106}
107
108Dictionary EditorPropertyFontOTObject::get_dict() {
109 return dict;
110}
111
112void EditorPropertyFontOTObject::set_defaults(const Dictionary &p_dict) {
113 defaults_dict = p_dict;
114}
115
116Dictionary EditorPropertyFontOTObject::get_defaults() {
117 return defaults_dict;
118}
119
120bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name) const {
121 String name = p_name;
122
123 if (name.begins_with("keys")) {
124 int key = name.get_slicec('/', 1).to_int();
125 if (defaults_dict.has(key) && dict.has(key)) {
126 int value = dict[key];
127 Vector3i range = defaults_dict[key];
128 return range.z != value;
129 }
130 }
131
132 return false;
133}
134
135bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name, Variant &r_property) const {
136 String name = p_name;
137
138 if (name.begins_with("keys")) {
139 int key = name.get_slicec('/', 1).to_int();
140 if (defaults_dict.has(key)) {
141 Vector3i range = defaults_dict[key];
142 r_property = range.z;
143 return true;
144 }
145 }
146
147 return false;
148}
149
150/*************************************************************************/
151/* EditorPropertyFontMetaOverride */
152/*************************************************************************/
153
154void EditorPropertyFontMetaOverride::_notification(int p_what) {
155 switch (p_what) {
156 case NOTIFICATION_ENTER_TREE:
157 case NOTIFICATION_THEME_CHANGED: {
158 if (button_add) {
159 button_add->set_icon(get_editor_theme_icon(SNAME("Add")));
160 }
161 } break;
162 }
163}
164
165void EditorPropertyFontMetaOverride::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
166 if (p_property.begins_with("keys")) {
167 Dictionary dict = object->get_dict();
168 String key = p_property.get_slice("/", 1);
169 dict[key] = (bool)p_value;
170
171 emit_changed(get_edited_property(), dict, "", true);
172
173 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
174 object->set_dict(dict);
175 }
176}
177
178void EditorPropertyFontMetaOverride::_remove(Object *p_button, const String &p_key) {
179 Dictionary dict = object->get_dict();
180
181 dict.erase(p_key);
182
183 emit_changed(get_edited_property(), dict, "", false);
184
185 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
186 object->set_dict(dict);
187 update_property();
188}
189
190void EditorPropertyFontMetaOverride::_add_menu() {
191 if (script_editor) {
192 Size2 size = get_size();
193 menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
194 menu->reset_size();
195 menu->popup();
196 } else {
197 locale_select->popup_locale_dialog();
198 }
199}
200
201void EditorPropertyFontMetaOverride::_add_script(int p_option) {
202 Dictionary dict = object->get_dict();
203
204 dict[script_codes[p_option]] = true;
205
206 emit_changed(get_edited_property(), dict, "", false);
207
208 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
209 object->set_dict(dict);
210 update_property();
211}
212
213void EditorPropertyFontMetaOverride::_add_lang(const String &p_locale) {
214 Dictionary dict = object->get_dict();
215
216 dict[p_locale] = true;
217
218 emit_changed(get_edited_property(), dict, "", false);
219
220 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
221 object->set_dict(dict);
222 update_property();
223}
224
225void EditorPropertyFontMetaOverride::_object_id_selected(const StringName &p_property, ObjectID p_id) {
226 emit_signal(SNAME("object_id_selected"), p_property, p_id);
227}
228
229void EditorPropertyFontMetaOverride::update_property() {
230 Variant updated_val = get_edited_property_value();
231
232 Dictionary dict = updated_val;
233
234 edit->set_text(vformat(TTR("Overrides (%d)"), dict.size()));
235
236 bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
237 if (edit->is_pressed() != unfolded) {
238 edit->set_pressed(unfolded);
239 }
240
241 if (unfolded) {
242 updating = true;
243
244 if (!container) {
245 container = memnew(MarginContainer);
246 container->set_theme_type_variation("MarginContainer4px");
247 add_child(container);
248 set_bottom_editor(container);
249
250 VBoxContainer *vbox = memnew(VBoxContainer);
251 vbox->set_v_size_flags(SIZE_EXPAND_FILL);
252 container->add_child(vbox);
253
254 property_vbox = memnew(VBoxContainer);
255 property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
256 vbox->add_child(property_vbox);
257
258 paginator = memnew(EditorPaginator);
259 paginator->connect("page_changed", callable_mp(this, &EditorPropertyFontMetaOverride::_page_changed));
260 vbox->add_child(paginator);
261 } else {
262 // Queue children for deletion, deleting immediately might cause errors.
263 for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
264 property_vbox->get_child(i)->queue_free();
265 }
266 button_add = nullptr;
267 }
268
269 int size = dict.size();
270
271 int max_page = MAX(0, size - 1) / page_length;
272 page_index = MIN(page_index, max_page);
273
274 paginator->update(page_index, max_page);
275 paginator->set_visible(max_page > 0);
276
277 int offset = page_index * page_length;
278
279 int amount = MIN(size - offset, page_length);
280
281 dict = dict.duplicate();
282 object->set_dict(dict);
283
284 for (int i = 0; i < amount; i++) {
285 String name = dict.get_key_at_index(i);
286 EditorProperty *prop = memnew(EditorPropertyCheck);
287 prop->set_object_and_property(object.ptr(), "keys/" + name);
288
289 if (script_editor) {
290 prop->set_label(TranslationServer::get_singleton()->get_script_name(name));
291 } else {
292 prop->set_label(TranslationServer::get_singleton()->get_locale_name(name));
293 }
294 prop->set_tooltip_text(name);
295 prop->set_selectable(false);
296
297 prop->connect("property_changed", callable_mp(this, &EditorPropertyFontMetaOverride::_property_changed));
298 prop->connect("object_id_selected", callable_mp(this, &EditorPropertyFontMetaOverride::_object_id_selected));
299
300 HBoxContainer *hbox = memnew(HBoxContainer);
301 property_vbox->add_child(hbox);
302 hbox->add_child(prop);
303 prop->set_h_size_flags(SIZE_EXPAND_FILL);
304 Button *remove = memnew(Button);
305 remove->set_icon(get_editor_theme_icon(SNAME("Remove")));
306 hbox->add_child(remove);
307 remove->connect("pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_remove).bind(remove, name));
308
309 prop->update_property();
310 }
311
312 if (script_editor) {
313 // TRANSLATORS: Script refers to a writing system.
314 button_add = EditorInspector::create_inspector_action_button(TTR("Add Script", "Locale"));
315 } else {
316 button_add = EditorInspector::create_inspector_action_button(TTR("Add Locale"));
317 }
318 button_add->connect("pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_add_menu));
319 property_vbox->add_child(button_add);
320
321 updating = false;
322 } else {
323 if (container) {
324 set_bottom_editor(nullptr);
325 memdelete(container);
326 button_add = nullptr;
327 container = nullptr;
328 }
329 }
330}
331
332void EditorPropertyFontMetaOverride::_edit_pressed() {
333 Variant prop_val = get_edited_property_value();
334 if (prop_val.get_type() == Variant::NIL) {
335 Callable::CallError ce;
336 Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);
337 get_edited_object()->set(get_edited_property(), prop_val);
338 }
339
340 get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
341 update_property();
342}
343
344void EditorPropertyFontMetaOverride::_page_changed(int p_page) {
345 if (updating) {
346 return;
347 }
348 page_index = p_page;
349 update_property();
350}
351
352EditorPropertyFontMetaOverride::EditorPropertyFontMetaOverride(bool p_script) {
353 script_editor = p_script;
354
355 object.instantiate();
356 page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
357
358 edit = memnew(Button);
359 edit->set_h_size_flags(SIZE_EXPAND_FILL);
360 edit->set_clip_text(true);
361 edit->connect("pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_edit_pressed));
362 edit->set_toggle_mode(true);
363 add_child(edit);
364 add_focusable(edit);
365
366 menu = memnew(PopupMenu);
367 if (script_editor) {
368 script_codes = TranslationServer::get_singleton()->get_all_scripts();
369 for (int i = 0; i < script_codes.size(); i++) {
370 menu->add_item(TranslationServer::get_singleton()->get_script_name(script_codes[i]) + " (" + script_codes[i] + ")", i);
371 }
372 }
373 add_child(menu);
374 menu->connect("id_pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_add_script));
375
376 locale_select = memnew(EditorLocaleDialog);
377 locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyFontMetaOverride::_add_lang));
378 add_child(locale_select);
379}
380
381/*************************************************************************/
382/* EditorPropertyOTVariation */
383/*************************************************************************/
384
385void EditorPropertyOTVariation::_notification(int p_what) {
386 switch (p_what) {
387 case NOTIFICATION_ENTER_TREE:
388 case NOTIFICATION_THEME_CHANGED: {
389 } break;
390 }
391}
392
393void EditorPropertyOTVariation::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
394 if (p_property.begins_with("keys")) {
395 Dictionary dict = object->get_dict();
396 Dictionary defaults_dict = object->get_defaults();
397 int key = p_property.get_slice("/", 1).to_int();
398 dict[key] = (int)p_value;
399 if (defaults_dict.has(key)) {
400 Vector3i range = defaults_dict[key];
401 if (range.z == (int)p_value) {
402 dict.erase(key);
403 }
404 }
405
406 emit_changed(get_edited_property(), dict, "", true);
407
408 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
409 object->set_dict(dict);
410 }
411}
412
413void EditorPropertyOTVariation::_object_id_selected(const StringName &p_property, ObjectID p_id) {
414 emit_signal(SNAME("object_id_selected"), p_property, p_id);
415}
416
417void EditorPropertyOTVariation::update_property() {
418 Variant updated_val = get_edited_property_value();
419
420 Dictionary dict = updated_val;
421
422 Ref<Font> fd;
423 if (Object::cast_to<Font>(get_edited_object()) != nullptr) {
424 fd = get_edited_object();
425 } else if (Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()) != nullptr) {
426 Ref<DynamicFontImportSettingsData> imp = Object::cast_to<DynamicFontImportSettingsData>(get_edited_object());
427 fd = imp->get_font();
428 }
429
430 Dictionary supported = (fd.is_valid()) ? fd->get_supported_variation_list() : Dictionary();
431
432 edit->set_text(vformat(TTR("Variation Coordinates (%d)"), supported.size()));
433
434 bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
435 if (edit->is_pressed() != unfolded) {
436 edit->set_pressed(unfolded);
437 }
438
439 if (unfolded) {
440 updating = true;
441
442 if (!container) {
443 container = memnew(MarginContainer);
444 container->set_theme_type_variation("MarginContainer4px");
445 add_child(container);
446 set_bottom_editor(container);
447
448 VBoxContainer *vbox = memnew(VBoxContainer);
449 vbox->set_v_size_flags(SIZE_EXPAND_FILL);
450 container->add_child(vbox);
451
452 property_vbox = memnew(VBoxContainer);
453 property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
454 vbox->add_child(property_vbox);
455
456 paginator = memnew(EditorPaginator);
457 paginator->connect("page_changed", callable_mp(this, &EditorPropertyOTVariation::_page_changed));
458 vbox->add_child(paginator);
459 } else {
460 // Queue children for deletion, deleting immediately might cause errors.
461 for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
462 property_vbox->get_child(i)->queue_free();
463 }
464 }
465
466 int size = supported.size();
467
468 int max_page = MAX(0, size - 1) / page_length;
469 page_index = MIN(page_index, max_page);
470
471 paginator->update(page_index, max_page);
472 paginator->set_visible(max_page > 0);
473
474 int offset = page_index * page_length;
475
476 int amount = MIN(size - offset, page_length);
477
478 dict = dict.duplicate();
479 object->set_dict(dict);
480 object->set_defaults(supported);
481
482 for (int i = 0; i < amount; i++) {
483 int name_tag = supported.get_key_at_index(i);
484 Vector3i range = supported.get_value_at_index(i);
485
486 EditorPropertyInteger *prop = memnew(EditorPropertyInteger);
487 prop->setup(range.x, range.y, false, 1, false, false);
488 prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));
489
490 String name = TS->tag_to_name(name_tag);
491 prop->set_label(name.capitalize());
492 prop->set_tooltip_text(name);
493 prop->set_selectable(false);
494
495 prop->connect("property_changed", callable_mp(this, &EditorPropertyOTVariation::_property_changed));
496 prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTVariation::_object_id_selected));
497
498 property_vbox->add_child(prop);
499
500 prop->update_property();
501 }
502
503 updating = false;
504 } else {
505 if (container) {
506 set_bottom_editor(nullptr);
507 memdelete(container);
508 container = nullptr;
509 }
510 }
511}
512
513void EditorPropertyOTVariation::_edit_pressed() {
514 Variant prop_val = get_edited_property_value();
515 if (prop_val.get_type() == Variant::NIL) {
516 Callable::CallError ce;
517 Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);
518 get_edited_object()->set(get_edited_property(), prop_val);
519 }
520
521 get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
522 update_property();
523}
524
525void EditorPropertyOTVariation::_page_changed(int p_page) {
526 if (updating) {
527 return;
528 }
529 page_index = p_page;
530 update_property();
531}
532
533EditorPropertyOTVariation::EditorPropertyOTVariation() {
534 object.instantiate();
535 page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
536
537 edit = memnew(Button);
538 edit->set_h_size_flags(SIZE_EXPAND_FILL);
539 edit->set_clip_text(true);
540 edit->connect("pressed", callable_mp(this, &EditorPropertyOTVariation::_edit_pressed));
541 edit->set_toggle_mode(true);
542 add_child(edit);
543 add_focusable(edit);
544}
545
546/*************************************************************************/
547/* EditorPropertyOTFeatures */
548/*************************************************************************/
549
550void EditorPropertyOTFeatures::_notification(int p_what) {
551 switch (p_what) {
552 case NOTIFICATION_ENTER_TREE:
553 case NOTIFICATION_THEME_CHANGED: {
554 if (button_add) {
555 button_add->set_icon(get_editor_theme_icon(SNAME("Add")));
556 }
557 } break;
558 }
559}
560
561void EditorPropertyOTFeatures::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
562 if (p_property.begins_with("keys")) {
563 Dictionary dict = object->get_dict();
564 int key = p_property.get_slice("/", 1).to_int();
565 dict[key] = (int)p_value;
566
567 emit_changed(get_edited_property(), dict, "", true);
568
569 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
570 object->set_dict(dict);
571 }
572}
573
574void EditorPropertyOTFeatures::_remove(Object *p_button, int p_key) {
575 Dictionary dict = object->get_dict();
576
577 dict.erase(p_key);
578
579 emit_changed(get_edited_property(), dict, "", false);
580
581 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
582 object->set_dict(dict);
583 update_property();
584}
585
586void EditorPropertyOTFeatures::_add_menu() {
587 Size2 size = get_size();
588 menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
589 menu->reset_size();
590 menu->popup();
591}
592
593void EditorPropertyOTFeatures::_add_feature(int p_option) {
594 Dictionary dict = object->get_dict();
595
596 dict[p_option] = 1;
597
598 emit_changed(get_edited_property(), dict, "", false);
599
600 dict = dict.duplicate(); // Duplicate, so undo/redo works better.
601 object->set_dict(dict);
602 update_property();
603}
604
605void EditorPropertyOTFeatures::_object_id_selected(const StringName &p_property, ObjectID p_id) {
606 emit_signal(SNAME("object_id_selected"), p_property, p_id);
607}
608
609void EditorPropertyOTFeatures::update_property() {
610 Variant updated_val = get_edited_property_value();
611
612 Dictionary dict = updated_val;
613
614 Ref<Font> fd;
615 if (Object::cast_to<FontVariation>(get_edited_object()) != nullptr) {
616 fd = get_edited_object();
617 } else if (Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()) != nullptr) {
618 Ref<DynamicFontImportSettingsData> imp = Object::cast_to<DynamicFontImportSettingsData>(get_edited_object());
619 fd = imp->get_font();
620 }
621
622 Dictionary supported;
623 if (fd.is_valid()) {
624 supported = fd->get_supported_feature_list();
625 }
626
627 if (supported.is_empty()) {
628 edit->set_text(vformat(TTR("No supported features")));
629 if (container) {
630 set_bottom_editor(nullptr);
631 memdelete(container);
632 button_add = nullptr;
633 container = nullptr;
634 }
635 return;
636 }
637 edit->set_text(vformat(TTR("Features (%d of %d set)"), dict.size(), supported.size()));
638
639 bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
640 if (edit->is_pressed() != unfolded) {
641 edit->set_pressed(unfolded);
642 }
643
644 if (unfolded) {
645 updating = true;
646
647 if (!container) {
648 container = memnew(MarginContainer);
649 container->set_theme_type_variation("MarginContainer4px");
650 add_child(container);
651 set_bottom_editor(container);
652
653 VBoxContainer *vbox = memnew(VBoxContainer);
654 vbox->set_v_size_flags(SIZE_EXPAND_FILL);
655 container->add_child(vbox);
656
657 property_vbox = memnew(VBoxContainer);
658 property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
659 vbox->add_child(property_vbox);
660
661 paginator = memnew(EditorPaginator);
662 paginator->connect("page_changed", callable_mp(this, &EditorPropertyOTFeatures::_page_changed));
663 vbox->add_child(paginator);
664 } else {
665 // Queue children for deletion, deleting immediately might cause errors.
666 for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
667 property_vbox->get_child(i)->queue_free();
668 }
669 button_add = nullptr;
670 }
671
672 // Update add menu items.
673 menu->clear();
674 bool have_sub[FGRP_MAX];
675 for (int i = 0; i < FGRP_MAX; i++) {
676 menu_sub[i]->clear();
677 have_sub[i] = false;
678 }
679
680 bool show_hidden = EDITOR_GET("interface/inspector/show_low_level_opentype_features");
681
682 for (int i = 0; i < supported.size(); i++) {
683 int name_tag = supported.get_key_at_index(i);
684 Dictionary info = supported.get_value_at_index(i);
685 bool hidden = info["hidden"].operator bool();
686 String name = TS->tag_to_name(name_tag);
687 FeatureGroups grp = FGRP_MAX;
688
689 if (hidden && !show_hidden) {
690 continue;
691 }
692
693 if (name.begins_with("stylistic_set_")) {
694 grp = FGRP_STYLISTIC_SET;
695 } else if (name.begins_with("character_variant_")) {
696 grp = FGRP_CHARACTER_VARIANT;
697 } else if (name.ends_with("_capitals")) {
698 grp = FGRP_CAPITLS;
699 } else if (name.ends_with("_ligatures")) {
700 grp = FGRP_LIGATURES;
701 } else if (name.ends_with("_alternates")) {
702 grp = FGRP_ALTERNATES;
703 } else if (name.ends_with("_kanji_forms") || name.begins_with("jis") || name == "simplified_forms" || name == "traditional_name_forms" || name == "traditional_forms") {
704 grp = FGRP_EAL;
705 } else if (name.ends_with("_widths")) {
706 grp = FGRP_EAW;
707 } else if (name == "tabular_figures" || name == "proportional_figures") {
708 grp = FGRP_NUMAL;
709 } else if (name.begins_with("custom_")) {
710 grp = FGRP_CUSTOM;
711 }
712 String disp_name = name.capitalize();
713 if (info.has("label")) {
714 disp_name = vformat("%s (%s)", disp_name, info["label"].operator String());
715 }
716
717 if (grp == FGRP_MAX) {
718 menu->add_item(disp_name, name_tag);
719 } else {
720 menu_sub[grp]->add_item(disp_name, name_tag);
721 have_sub[grp] = true;
722 }
723 }
724 for (int i = 0; i < FGRP_MAX; i++) {
725 if (have_sub[i]) {
726 menu->add_submenu_item(RTR(group_names[i]), "FTRMenu_" + itos(i));
727 }
728 }
729
730 int size = dict.size();
731
732 int max_page = MAX(0, size - 1) / page_length;
733 page_index = MIN(page_index, max_page);
734
735 paginator->update(page_index, max_page);
736 paginator->set_visible(max_page > 0);
737
738 int offset = page_index * page_length;
739
740 int amount = MIN(size - offset, page_length);
741
742 dict = dict.duplicate();
743 object->set_dict(dict);
744
745 for (int i = 0; i < amount; i++) {
746 int name_tag = dict.get_key_at_index(i);
747
748 if (supported.has(name_tag)) {
749 Dictionary info = supported[name_tag];
750 Variant::Type vtype = Variant::Type(info["type"].operator int());
751 bool hidden = info["hidden"].operator bool();
752 if (hidden && !show_hidden) {
753 continue;
754 }
755
756 EditorProperty *prop = nullptr;
757 switch (vtype) {
758 case Variant::NIL: {
759 prop = memnew(EditorPropertyNil);
760 } break;
761 case Variant::BOOL: {
762 prop = memnew(EditorPropertyCheck);
763 } break;
764 case Variant::INT: {
765 EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
766 editor->setup(0, 255, 1, false, false, false);
767 prop = editor;
768 } break;
769 default: {
770 ERR_CONTINUE_MSG(true, vformat("Unsupported OT feature data type %s", Variant::get_type_name(vtype)));
771 }
772 }
773 prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));
774
775 String name = TS->tag_to_name(name_tag);
776 String disp_name = name.capitalize();
777 if (info.has("label")) {
778 disp_name = vformat("%s (%s)", disp_name, info["label"].operator String());
779 }
780 prop->set_label(disp_name);
781 prop->set_tooltip_text(name);
782 prop->set_selectable(false);
783
784 prop->connect("property_changed", callable_mp(this, &EditorPropertyOTFeatures::_property_changed));
785 prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTFeatures::_object_id_selected));
786
787 HBoxContainer *hbox = memnew(HBoxContainer);
788 property_vbox->add_child(hbox);
789 hbox->add_child(prop);
790 prop->set_h_size_flags(SIZE_EXPAND_FILL);
791 Button *remove = memnew(Button);
792 remove->set_icon(get_editor_theme_icon(SNAME("Remove")));
793 hbox->add_child(remove);
794 remove->connect("pressed", callable_mp(this, &EditorPropertyOTFeatures::_remove).bind(remove, name_tag));
795
796 prop->update_property();
797 }
798 }
799
800 button_add = EditorInspector::create_inspector_action_button(TTR("Add Feature"));
801 button_add->set_icon(get_editor_theme_icon(SNAME("Add")));
802 button_add->connect("pressed", callable_mp(this, &EditorPropertyOTFeatures::_add_menu));
803 property_vbox->add_child(button_add);
804
805 updating = false;
806 } else {
807 if (container) {
808 set_bottom_editor(nullptr);
809 memdelete(container);
810 button_add = nullptr;
811 container = nullptr;
812 }
813 }
814}
815
816void EditorPropertyOTFeatures::_edit_pressed() {
817 Variant prop_val = get_edited_property_value();
818 if (prop_val.get_type() == Variant::NIL) {
819 Callable::CallError ce;
820 Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);
821 get_edited_object()->set(get_edited_property(), prop_val);
822 }
823
824 get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
825 update_property();
826}
827
828void EditorPropertyOTFeatures::_page_changed(int p_page) {
829 if (updating) {
830 return;
831 }
832 page_index = p_page;
833 update_property();
834}
835
836EditorPropertyOTFeatures::EditorPropertyOTFeatures() {
837 object.instantiate();
838 page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
839
840 edit = memnew(Button);
841 edit->set_h_size_flags(SIZE_EXPAND_FILL);
842 edit->set_clip_text(true);
843 edit->connect("pressed", callable_mp(this, &EditorPropertyOTFeatures::_edit_pressed));
844 edit->set_toggle_mode(true);
845 add_child(edit);
846 add_focusable(edit);
847
848 menu = memnew(PopupMenu);
849 add_child(menu);
850 menu->connect("id_pressed", callable_mp(this, &EditorPropertyOTFeatures::_add_feature));
851
852 for (int i = 0; i < FGRP_MAX; i++) {
853 menu_sub[i] = memnew(PopupMenu);
854 menu_sub[i]->set_name("FTRMenu_" + itos(i));
855 menu->add_child(menu_sub[i]);
856 menu_sub[i]->connect("id_pressed", callable_mp(this, &EditorPropertyOTFeatures::_add_feature));
857 }
858
859 group_names[FGRP_STYLISTIC_SET] = "Stylistic Sets";
860 group_names[FGRP_CHARACTER_VARIANT] = "Character Variants";
861 group_names[FGRP_CAPITLS] = "Capitals";
862 group_names[FGRP_LIGATURES] = "Ligatures";
863 group_names[FGRP_ALTERNATES] = "Alternates";
864 group_names[FGRP_EAL] = "East Asian Language";
865 group_names[FGRP_EAW] = "East Asian Widths";
866 group_names[FGRP_NUMAL] = "Numeral Alignment";
867 group_names[FGRP_CUSTOM] = "Custom";
868}
869
870/*************************************************************************/
871/* EditorInspectorPluginFontVariation */
872/*************************************************************************/
873
874bool EditorInspectorPluginFontVariation::can_handle(Object *p_object) {
875 return (Object::cast_to<FontVariation>(p_object) != nullptr) || (Object::cast_to<DynamicFontImportSettingsData>(p_object) != nullptr);
876}
877
878bool EditorInspectorPluginFontVariation::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
879 if (p_path == "variation_opentype") {
880 add_property_editor(p_path, memnew(EditorPropertyOTVariation));
881 return true;
882 } else if (p_path == "opentype_features") {
883 add_property_editor(p_path, memnew(EditorPropertyOTFeatures));
884 return true;
885 } else if (p_path == "language_support") {
886 add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(false)));
887 return true;
888 } else if (p_path == "script_support") {
889 add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(true)));
890 return true;
891 }
892 return false;
893}
894
895/*************************************************************************/
896/* FontPreview */
897/*************************************************************************/
898
899void FontPreview::_notification(int p_what) {
900 switch (p_what) {
901 case NOTIFICATION_DRAW: {
902 // Draw font name (style).
903 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
904 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
905 Color text_color = get_theme_color(SNAME("font_color"), SNAME("Label"));
906
907 // Draw font preview.
908 bool prev_ok = true;
909 if (prev_font.is_valid()) {
910 if (prev_font->get_font_name().is_empty()) {
911 prev_ok = false;
912 } else {
913 String name;
914 if (prev_font->get_font_style_name().is_empty()) {
915 name = prev_font->get_font_name();
916 } else {
917 name = vformat("%s (%s)", prev_font->get_font_name(), prev_font->get_font_style_name());
918 }
919 if (prev_font->is_class("FontVariation")) {
920 name += " " + TTR(" - Variation");
921 }
922 font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), name, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color);
923
924 String sample;
925 static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";
926 for (int i = 0; i < sample_base.length(); i++) {
927 if (prev_font->has_char(sample_base[i])) {
928 sample += sample_base[i];
929 }
930 }
931 if (sample.is_empty()) {
932 sample = prev_font->get_supported_chars().substr(0, 6);
933 }
934 if (sample.is_empty()) {
935 prev_ok = false;
936 } else {
937 prev_font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + prev_font->get_height(25 * EDSCALE)), sample, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, 25 * EDSCALE, text_color);
938 }
939 }
940 }
941 if (!prev_ok) {
942 text_color.a *= 0.5;
943 font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), TTR("Unable to preview font"), HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color);
944 }
945 } break;
946 }
947}
948
949void FontPreview::_bind_methods() {}
950
951Size2 FontPreview::get_minimum_size() const {
952 return Vector2(64, 64) * EDSCALE;
953}
954
955void FontPreview::set_data(const Ref<Font> &p_f) {
956 prev_font = p_f;
957 queue_redraw();
958}
959
960FontPreview::FontPreview() {
961}
962
963/*************************************************************************/
964/* EditorInspectorPluginFontPreview */
965/*************************************************************************/
966
967bool EditorInspectorPluginFontPreview::can_handle(Object *p_object) {
968 return Object::cast_to<Font>(p_object) != nullptr;
969}
970
971void EditorInspectorPluginFontPreview::parse_begin(Object *p_object) {
972 Font *fd = Object::cast_to<Font>(p_object);
973 ERR_FAIL_NULL(fd);
974
975 FontPreview *editor = memnew(FontPreview);
976 editor->set_data(fd);
977 add_custom_control(editor);
978}
979
980bool EditorInspectorPluginFontPreview::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
981 return false;
982}
983
984/*************************************************************************/
985/* EditorPropertyFontNamesArray */
986/*************************************************************************/
987
988void EditorPropertyFontNamesArray::_add_element() {
989 Size2 size = get_size();
990 menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
991 menu->reset_size();
992 menu->popup();
993}
994
995void EditorPropertyFontNamesArray::_add_font(int p_option) {
996 if (updating) {
997 return;
998 }
999
1000 Variant array = object->get_array();
1001 int previous_size = array.call("size");
1002
1003 array.call("resize", previous_size + 1);
1004 array.set(previous_size, menu->get_item_text(p_option));
1005
1006 emit_changed(get_edited_property(), array, "", false);
1007 object->set_array(array);
1008 update_property();
1009}
1010
1011EditorPropertyFontNamesArray::EditorPropertyFontNamesArray() {
1012 menu = memnew(PopupMenu);
1013 menu->add_item("Sans-Serif", 0);
1014 menu->add_item("Serif", 1);
1015 menu->add_item("Monospace", 2);
1016 menu->add_item("Fantasy", 3);
1017 menu->add_item("Cursive", 4);
1018
1019 menu->add_separator();
1020
1021 if (OS::get_singleton()) {
1022 Vector<String> fonts = OS::get_singleton()->get_system_fonts();
1023 fonts.sort();
1024 for (int i = 0; i < fonts.size(); i++) {
1025 menu->add_item(fonts[i], i + 6);
1026 }
1027 }
1028 add_child(menu);
1029 menu->connect("id_pressed", callable_mp(this, &EditorPropertyFontNamesArray::_add_font));
1030}
1031
1032/*************************************************************************/
1033/* EditorInspectorPluginSystemFont */
1034/*************************************************************************/
1035
1036bool EditorInspectorPluginSystemFont::can_handle(Object *p_object) {
1037 return Object::cast_to<SystemFont>(p_object) != nullptr;
1038}
1039
1040bool EditorInspectorPluginSystemFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
1041 if (p_path == "font_names") {
1042 EditorPropertyFontNamesArray *editor = memnew(EditorPropertyFontNamesArray);
1043 editor->setup(p_type, p_hint_text);
1044 add_property_editor(p_path, editor);
1045 return true;
1046 }
1047 return false;
1048}
1049
1050/*************************************************************************/
1051/* FontEditorPlugin */
1052/*************************************************************************/
1053
1054FontEditorPlugin::FontEditorPlugin() {
1055 Ref<EditorInspectorPluginFontVariation> fc_plugin;
1056 fc_plugin.instantiate();
1057 EditorInspector::add_inspector_plugin(fc_plugin);
1058
1059 Ref<EditorInspectorPluginSystemFont> fs_plugin;
1060 fs_plugin.instantiate();
1061 EditorInspector::add_inspector_plugin(fs_plugin);
1062
1063 Ref<EditorInspectorPluginFontPreview> fp_plugin;
1064 fp_plugin.instantiate();
1065 EditorInspector::add_inspector_plugin(fp_plugin);
1066}
1067