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