Path: blob/master/editor/scene/gui/font_config_plugin.cpp
21026 views
/**************************************************************************/1/* font_config_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "font_config_plugin.h"3132#include "core/string/translation_server.h"33#include "editor/import/dynamic_font_import_settings.h"34#include "editor/settings/editor_settings.h"35#include "editor/themes/editor_scale.h"36#include "scene/gui/margin_container.h"3738/*************************************************************************/39/* EditorPropertyFontMetaObject */40/*************************************************************************/4142bool EditorPropertyFontMetaObject::_set(const StringName &p_name, const Variant &p_value) {43String name = p_name;4445if (name.begins_with("keys")) {46String key = name.get_slicec('/', 1);47dict[key] = p_value;48return true;49}5051return false;52}5354bool EditorPropertyFontMetaObject::_get(const StringName &p_name, Variant &r_ret) const {55String name = p_name;5657if (name.begins_with("keys")) {58String key = name.get_slicec('/', 1);59r_ret = dict[key];60return true;61}6263return false;64}6566void EditorPropertyFontMetaObject::set_dict(const Dictionary &p_dict) {67dict = p_dict;68}6970Dictionary EditorPropertyFontMetaObject::get_dict() {71return dict;72}7374/*************************************************************************/75/* EditorPropertyFontOTObject */76/*************************************************************************/7778bool EditorPropertyFontOTObject::_set(const StringName &p_name, const Variant &p_value) {79String name = p_name;8081if (name.begins_with("keys")) {82int key = name.get_slicec('/', 1).to_int();83dict[key] = p_value;84return true;85}8687return false;88}8990bool EditorPropertyFontOTObject::_get(const StringName &p_name, Variant &r_ret) const {91String name = p_name;9293if (name.begins_with("keys")) {94int key = name.get_slicec('/', 1).to_int();95r_ret = dict[key];96return true;97}9899return false;100}101102void EditorPropertyFontOTObject::set_dict(const Dictionary &p_dict) {103dict = p_dict;104}105106Dictionary EditorPropertyFontOTObject::get_dict() {107return dict;108}109110void EditorPropertyFontOTObject::set_defaults(const Dictionary &p_dict) {111defaults_dict = p_dict;112}113114Dictionary EditorPropertyFontOTObject::get_defaults() {115return defaults_dict;116}117118bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name) const {119String name = p_name;120121if (name.begins_with("keys")) {122int key = name.get_slicec('/', 1).to_int();123return defaults_dict.has(key) && dict.has(key);124}125return false;126}127128bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name, Variant &r_property) const {129String name = p_name;130131if (name.begins_with("keys")) {132int key = name.get_slicec('/', 1).to_int();133if (defaults_dict.has(key)) {134Vector3i range = defaults_dict[key];135r_property = range.z;136return true;137}138}139return false;140}141142/*************************************************************************/143/* EditorPropertyFontMetaOverride */144/*************************************************************************/145146void EditorPropertyFontMetaOverride::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {147if (p_property.begins_with("keys")) {148Dictionary dict = object->get_dict();149String key = p_property.get_slicec('/', 1);150dict[key] = (bool)p_value;151152emit_changed(get_edited_property(), dict, "", true);153154dict = dict.duplicate(); // Duplicate, so undo/redo works better.155object->set_dict(dict);156}157}158159void EditorPropertyFontMetaOverride::_remove(Object *p_button, const String &p_key) {160Dictionary dict = object->get_dict();161162dict.erase(p_key);163164emit_changed(get_edited_property(), dict, "", false);165166dict = dict.duplicate(); // Duplicate, so undo/redo works better.167object->set_dict(dict);168update_property();169}170171void EditorPropertyFontMetaOverride::_add_menu() {172if (script_editor) {173Size2 size = get_size();174menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));175menu->reset_size();176menu->popup();177} else {178locale_select->popup_locale_dialog();179}180}181182void EditorPropertyFontMetaOverride::_add_script(int p_option) {183Dictionary dict = object->get_dict();184185dict[script_codes[p_option]] = true;186187emit_changed(get_edited_property(), dict, "", false);188189dict = dict.duplicate(); // Duplicate, so undo/redo works better.190object->set_dict(dict);191update_property();192}193194void EditorPropertyFontMetaOverride::_add_lang(const String &p_locale) {195Dictionary dict = object->get_dict();196197dict[p_locale] = true;198199emit_changed(get_edited_property(), dict, "", false);200201dict = dict.duplicate(); // Duplicate, so undo/redo works better.202object->set_dict(dict);203update_property();204}205206void EditorPropertyFontMetaOverride::_object_id_selected(const StringName &p_property, ObjectID p_id) {207emit_signal(SNAME("object_id_selected"), p_property, p_id);208}209210void EditorPropertyFontMetaOverride::update_property() {211Variant updated_val = get_edited_property_value();212213Dictionary dict = updated_val;214215edit->set_text(vformat(TTR("Overrides (%d)"), dict.size()));216217bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());218if (edit->is_pressed() != unfolded) {219edit->set_pressed(unfolded);220}221222if (unfolded) {223updating = true;224225if (!container) {226container = memnew(MarginContainer);227container->set_theme_type_variation("MarginContainer4px");228add_child(container);229set_bottom_editor(container);230231VBoxContainer *vbox = memnew(VBoxContainer);232vbox->set_v_size_flags(SIZE_EXPAND_FILL);233container->add_child(vbox);234235property_vbox = memnew(VBoxContainer);236property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);237vbox->add_child(property_vbox);238239paginator = memnew(EditorPaginator);240paginator->connect("page_changed", callable_mp(this, &EditorPropertyFontMetaOverride::_page_changed));241vbox->add_child(paginator);242} else {243// Queue children for deletion, deleting immediately might cause errors.244for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {245property_vbox->get_child(i)->queue_free();246}247}248249int size = dict.size();250251int max_page = MAX(0, size - 1) / page_length;252page_index = MIN(page_index, max_page);253254paginator->update(page_index, max_page);255paginator->set_visible(max_page > 0);256257int offset = page_index * page_length;258259int amount = MIN(size - offset, page_length);260261dict = dict.duplicate();262object->set_dict(dict);263264for (int i = 0; i < amount; i++) {265String name = dict.get_key_at_index(i);266EditorProperty *prop = memnew(EditorPropertyCheck);267prop->set_object_and_property(object.ptr(), "keys/" + name);268269if (script_editor) {270prop->set_label(TranslationServer::get_singleton()->get_script_name(name));271} else {272prop->set_label(TranslationServer::get_singleton()->get_locale_name(name));273}274prop->set_tooltip_text(name);275prop->set_selectable(false);276277prop->connect("property_changed", callable_mp(this, &EditorPropertyFontMetaOverride::_property_changed));278prop->connect("object_id_selected", callable_mp(this, &EditorPropertyFontMetaOverride::_object_id_selected));279280HBoxContainer *hbox = memnew(HBoxContainer);281property_vbox->add_child(hbox);282hbox->add_child(prop);283prop->set_h_size_flags(SIZE_EXPAND_FILL);284Button *remove = memnew(Button);285remove->set_accessibility_name(TTRC("Remove"));286remove->set_button_icon(get_editor_theme_icon(SNAME("Remove")));287hbox->add_child(remove);288remove->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyFontMetaOverride::_remove).bind(remove, name));289290prop->update_property();291}292293EditorInspectorActionButton *button_add;294if (script_editor) {295// This property editor is currently only used inside the font import settings dialog.296// Usually, the dialog needs to be closed in order to change the editor's language.297// So we can ignore the auto-translation here.298299// TRANSLATORS: Script refers to a writing system.300button_add = memnew(EditorInspectorActionButton(TTR("Add Script", "Locale"), SNAME("Add")));301button_add->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);302} else {303button_add = memnew(EditorInspectorActionButton(TTRC("Add Locale"), SNAME("Add")));304}305button_add->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyFontMetaOverride::_add_menu));306property_vbox->add_child(button_add);307308updating = false;309} else {310if (container) {311set_bottom_editor(nullptr);312memdelete(container);313container = nullptr;314}315}316}317318void EditorPropertyFontMetaOverride::_edit_pressed() {319Variant prop_val = get_edited_property_value();320if (prop_val.get_type() == Variant::NIL) {321Callable::CallError ce;322Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);323get_edited_object()->set(get_edited_property(), prop_val);324}325326get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());327update_property();328}329330void EditorPropertyFontMetaOverride::_page_changed(int p_page) {331if (updating) {332return;333}334page_index = p_page;335update_property();336}337338EditorPropertyFontMetaOverride::EditorPropertyFontMetaOverride(bool p_script) {339script_editor = p_script;340341object.instantiate();342page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));343344edit = memnew(Button);345edit->set_accessibility_name(TTRC("Edit"));346edit->set_h_size_flags(SIZE_EXPAND_FILL);347edit->set_clip_text(true);348edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyFontMetaOverride::_edit_pressed));349edit->set_toggle_mode(true);350add_child(edit);351add_focusable(edit);352353menu = memnew(PopupMenu);354if (script_editor) {355script_codes = TranslationServer::get_singleton()->get_all_scripts();356for (int i = 0; i < script_codes.size(); i++) {357menu->add_item(TranslationServer::get_singleton()->get_script_name(script_codes[i]) + " (" + script_codes[i] + ")", i);358}359}360add_child(menu);361menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyFontMetaOverride::_add_script));362363locale_select = memnew(EditorLocaleDialog);364locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyFontMetaOverride::_add_lang));365add_child(locale_select);366}367368/*************************************************************************/369/* EditorPropertyOTVariation */370/*************************************************************************/371372void EditorPropertyOTVariation::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {373if (p_property.begins_with("keys")) {374Dictionary dict = object->get_dict();375int key = p_property.get_slicec('/', 1).to_int();376dict[key] = (int)p_value;377378emit_changed(get_edited_property(), dict, "", true);379380dict = dict.duplicate(); // Duplicate, so undo/redo works better.381object->set_dict(dict);382}383}384385void EditorPropertyOTVariation::_object_id_selected(const StringName &p_property, ObjectID p_id) {386emit_signal(SNAME("object_id_selected"), p_property, p_id);387}388389void EditorPropertyOTVariation::update_property() {390Variant updated_val = get_edited_property_value();391392Dictionary dict = updated_val;393394Ref<Font> fd;395if (Object::cast_to<Font>(get_edited_object()) != nullptr) {396fd = get_edited_object();397} else if (Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()) != nullptr) {398Ref<DynamicFontImportSettingsData> imp = Object::cast_to<DynamicFontImportSettingsData>(get_edited_object());399fd = imp->get_font();400}401402Dictionary supported = (fd.is_valid()) ? fd->get_supported_variation_list() : Dictionary();403404for (const KeyValue<Variant, Variant> &kv : supported) {405const int &name_tag = kv.key;406const Vector3i &range = kv.value;407if ((dict.has(name_tag) && dict[name_tag].get_type() == Variant::NIL) || !dict.has(name_tag)) {408dict[name_tag] = range.z;409}410}411412edit->set_text(vformat(TTR("Variation Coordinates (%d)"), supported.size()));413414bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());415if (edit->is_pressed() != unfolded) {416edit->set_pressed(unfolded);417}418419if (unfolded) {420updating = true;421422if (!container) {423container = memnew(MarginContainer);424container->set_theme_type_variation("MarginContainer4px");425add_child(container);426set_bottom_editor(container);427428VBoxContainer *vbox = memnew(VBoxContainer);429vbox->set_v_size_flags(SIZE_EXPAND_FILL);430container->add_child(vbox);431432property_vbox = memnew(VBoxContainer);433property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);434vbox->add_child(property_vbox);435436paginator = memnew(EditorPaginator);437paginator->connect("page_changed", callable_mp(this, &EditorPropertyOTVariation::_page_changed));438vbox->add_child(paginator);439} else {440// Queue children for deletion, deleting immediately might cause errors.441for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {442property_vbox->get_child(i)->queue_free();443}444}445446int size = supported.size();447448int max_page = MAX(0, size - 1) / page_length;449page_index = MIN(page_index, max_page);450451paginator->update(page_index, max_page);452paginator->set_visible(max_page > 0);453454int offset = page_index * page_length;455456int amount = MIN(size - offset, page_length);457458dict = dict.duplicate();459object->set_dict(dict);460object->set_defaults(supported);461462for (int i = 0; i < amount; i++) {463int name_tag = supported.get_key_at_index(i);464Vector3i range = supported.get_value_at_index(i);465466EditorPropertyInteger *prop = memnew(EditorPropertyInteger);467EditorPropertyRangeHint hint;468hint.min = range.x;469hint.max = range.y;470hint.or_greater = false;471hint.or_less = false;472prop->setup(hint);473prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));474475String name = TS->tag_to_name(name_tag);476String name_cap;477{478String aux = name.replace_char('_', ' ').strip_edges();479for (int j = 0; j < aux.get_slice_count(" "); j++) {480String slice = aux.get_slicec(' ', j);481if (slice.length() > 0) {482slice[0] = String::char_uppercase(slice[0]);483if (i > 0) {484name_cap += " ";485}486name_cap += slice;487}488}489}490prop->set_label(name_cap);491prop->set_tooltip_text(name);492prop->set_selectable(false);493494prop->connect("property_changed", callable_mp(this, &EditorPropertyOTVariation::_property_changed));495prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTVariation::_object_id_selected));496497property_vbox->add_child(prop);498499prop->update_property();500}501502updating = false;503} else {504if (container) {505set_bottom_editor(nullptr);506memdelete(container);507container = nullptr;508}509}510}511512void EditorPropertyOTVariation::_edit_pressed() {513Variant prop_val = get_edited_property_value();514if (prop_val.get_type() == Variant::NIL) {515Callable::CallError ce;516Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);517get_edited_object()->set(get_edited_property(), prop_val);518}519520get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());521update_property();522}523524void EditorPropertyOTVariation::_page_changed(int p_page) {525if (updating) {526return;527}528page_index = p_page;529update_property();530}531532EditorPropertyOTVariation::EditorPropertyOTVariation() {533object.instantiate();534page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));535536edit = memnew(Button);537edit->set_accessibility_name(TTRC("Edit"));538edit->set_h_size_flags(SIZE_EXPAND_FILL);539edit->set_clip_text(true);540edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTVariation::_edit_pressed));541edit->set_toggle_mode(true);542add_child(edit);543add_focusable(edit);544}545546/*************************************************************************/547/* EditorPropertyOTFeatures */548/*************************************************************************/549550void EditorPropertyOTFeatures::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {551if (p_property.begins_with("keys")) {552Dictionary dict = object->get_dict();553int key = p_property.get_slicec('/', 1).to_int();554dict[key] = (int)p_value;555556emit_changed(get_edited_property(), dict, "", true);557558dict = dict.duplicate(); // Duplicate, so undo/redo works better.559object->set_dict(dict);560}561}562563void EditorPropertyOTFeatures::_remove(Object *p_button, int p_key) {564Dictionary dict = object->get_dict();565566dict.erase(p_key);567568emit_changed(get_edited_property(), dict, "", false);569570dict = dict.duplicate(); // Duplicate, so undo/redo works better.571object->set_dict(dict);572update_property();573}574575void EditorPropertyOTFeatures::_add_menu() {576Size2 size = get_size();577menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));578menu->reset_size();579menu->popup();580}581582void EditorPropertyOTFeatures::_add_feature(int p_option) {583Dictionary dict = object->get_dict();584585dict[p_option] = 1;586587emit_changed(get_edited_property(), dict, "", false);588589dict = dict.duplicate(); // Duplicate, so undo/redo works better.590object->set_dict(dict);591update_property();592}593594void EditorPropertyOTFeatures::_object_id_selected(const StringName &p_property, ObjectID p_id) {595emit_signal(SNAME("object_id_selected"), p_property, p_id);596}597598void EditorPropertyOTFeatures::update_property() {599Variant updated_val = get_edited_property_value();600601Dictionary dict = updated_val;602603Ref<Font> fd;604if (Object::cast_to<FontVariation>(get_edited_object()) != nullptr) {605fd = get_edited_object();606} else if (Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()) != nullptr) {607Ref<DynamicFontImportSettingsData> imp = Object::cast_to<DynamicFontImportSettingsData>(get_edited_object());608fd = imp->get_font();609}610611Dictionary supported;612if (fd.is_valid()) {613supported = fd->get_supported_feature_list();614}615616if (supported.is_empty()) {617edit->set_text(vformat(TTR("No supported features")));618if (container) {619set_bottom_editor(nullptr);620memdelete(container);621container = nullptr;622}623return;624}625edit->set_text(vformat(TTR("Features (%d of %d set)"), dict.size(), supported.size()));626627bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());628if (edit->is_pressed() != unfolded) {629edit->set_pressed(unfolded);630}631632if (unfolded) {633updating = true;634635if (!container) {636container = memnew(MarginContainer);637container->set_theme_type_variation("MarginContainer4px");638add_child(container);639set_bottom_editor(container);640641VBoxContainer *vbox = memnew(VBoxContainer);642vbox->set_v_size_flags(SIZE_EXPAND_FILL);643container->add_child(vbox);644645property_vbox = memnew(VBoxContainer);646property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);647vbox->add_child(property_vbox);648649paginator = memnew(EditorPaginator);650paginator->connect("page_changed", callable_mp(this, &EditorPropertyOTFeatures::_page_changed));651vbox->add_child(paginator);652} else {653// Queue children for deletion, deleting immediately might cause errors.654for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {655property_vbox->get_child(i)->queue_free();656}657}658659// Update add menu items.660menu->clear(false);661bool have_sub[FGRP_MAX];662for (int i = 0; i < FGRP_MAX; i++) {663menu_sub[i]->clear();664have_sub[i] = false;665}666667bool show_hidden = EDITOR_GET("interface/inspector/show_low_level_opentype_features");668669for (int i = 0; i < supported.size(); i++) {670int name_tag = supported.get_key_at_index(i);671Dictionary info = supported.get_value_at_index(i);672bool hidden = info["hidden"].operator bool();673String name = TS->tag_to_name(name_tag);674FeatureGroups grp = FGRP_MAX;675676if (hidden && !show_hidden) {677continue;678}679680if (name.begins_with("stylistic_set_")) {681grp = FGRP_STYLISTIC_SET;682} else if (name.begins_with("character_variant_")) {683grp = FGRP_CHARACTER_VARIANT;684} else if (name.ends_with("_capitals")) {685grp = FGRP_CAPITLS;686} else if (name.ends_with("_ligatures")) {687grp = FGRP_LIGATURES;688} else if (name.ends_with("_alternates")) {689grp = FGRP_ALTERNATES;690} else if (name.ends_with("_kanji_forms") || name.begins_with("jis") || name == "simplified_forms" || name == "traditional_name_forms" || name == "traditional_forms") {691grp = FGRP_EAL;692} else if (name.ends_with("_widths")) {693grp = FGRP_EAW;694} else if (name == "tabular_figures" || name == "proportional_figures") {695grp = FGRP_NUMAL;696} else if (name.begins_with("custom_")) {697grp = FGRP_CUSTOM;698}699String disp_name = name.capitalize();700if (info.has("label")) {701disp_name = vformat("%s (%s)", disp_name, info["label"].operator String());702}703704if (grp == FGRP_MAX) {705menu->add_item(disp_name, name_tag);706} else {707menu_sub[grp]->add_item(disp_name, name_tag);708have_sub[grp] = true;709}710}711for (int i = 0; i < FGRP_MAX; i++) {712if (have_sub[i]) {713menu->add_submenu_node_item(TTRGET(group_names[i]), menu_sub[i]);714}715}716717int size = dict.size();718719int max_page = MAX(0, size - 1) / page_length;720page_index = MIN(page_index, max_page);721722paginator->update(page_index, max_page);723paginator->set_visible(max_page > 0);724725int offset = page_index * page_length;726727int amount = MIN(size - offset, page_length);728729dict = dict.duplicate();730object->set_dict(dict);731732for (int i = 0; i < amount; i++) {733int name_tag = dict.get_key_at_index(i);734735if (supported.has(name_tag)) {736Dictionary info = supported[name_tag];737Variant::Type vtype = Variant::Type(info["type"].operator int());738bool hidden = info["hidden"].operator bool();739if (hidden && !show_hidden) {740continue;741}742743EditorProperty *prop = nullptr;744switch (vtype) {745case Variant::NIL: {746prop = memnew(EditorPropertyNil);747} break;748case Variant::BOOL: {749prop = memnew(EditorPropertyCheck);750} break;751case Variant::INT: {752EditorPropertyInteger *editor = memnew(EditorPropertyInteger);753EditorPropertyRangeHint hint;754hint.min = 0;755hint.max = 255;756hint.hide_control = false;757hint.or_greater = false;758hint.or_less = false;759editor->setup(hint);760prop = editor;761} break;762default: {763ERR_CONTINUE_MSG(true, vformat("Unsupported OT feature data type %s", Variant::get_type_name(vtype)));764}765}766prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));767768String name = TS->tag_to_name(name_tag);769String disp_name = name.capitalize();770if (info.has("label")) {771disp_name = vformat("%s (%s)", disp_name, info["label"].operator String());772}773prop->set_label(disp_name);774prop->set_tooltip_text(name);775prop->set_selectable(false);776777prop->connect("property_changed", callable_mp(this, &EditorPropertyOTFeatures::_property_changed));778prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTFeatures::_object_id_selected));779780HBoxContainer *hbox = memnew(HBoxContainer);781property_vbox->add_child(hbox);782hbox->add_child(prop);783prop->set_h_size_flags(SIZE_EXPAND_FILL);784Button *remove = memnew(Button);785remove->set_accessibility_name(TTRC("Remove"));786remove->set_button_icon(get_editor_theme_icon(SNAME("Remove")));787hbox->add_child(remove);788remove->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_remove).bind(remove, name_tag));789790prop->update_property();791}792}793794EditorInspectorActionButton *button_add = memnew(EditorInspectorActionButton(TTRC("Add Feature"), SNAME("Add")));795button_add->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_add_menu));796property_vbox->add_child(button_add);797798updating = false;799} else {800if (container) {801set_bottom_editor(nullptr);802memdelete(container);803container = nullptr;804}805}806}807808void EditorPropertyOTFeatures::_edit_pressed() {809Variant prop_val = get_edited_property_value();810if (prop_val.get_type() == Variant::NIL) {811Callable::CallError ce;812Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);813get_edited_object()->set(get_edited_property(), prop_val);814}815816get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());817update_property();818}819820void EditorPropertyOTFeatures::_page_changed(int p_page) {821if (updating) {822return;823}824page_index = p_page;825update_property();826}827828EditorPropertyOTFeatures::EditorPropertyOTFeatures() {829object.instantiate();830page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));831832edit = memnew(Button);833edit->set_accessibility_name(TTRC("Edit"));834edit->set_h_size_flags(SIZE_EXPAND_FILL);835edit->set_clip_text(true);836edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_edit_pressed));837edit->set_toggle_mode(true);838add_child(edit);839add_focusable(edit);840841menu = memnew(PopupMenu);842add_child(menu);843menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyOTFeatures::_add_feature));844845for (int i = 0; i < FGRP_MAX; i++) {846menu_sub[i] = memnew(PopupMenu);847menu->add_child(menu_sub[i]);848menu_sub[i]->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyOTFeatures::_add_feature));849}850851group_names[FGRP_STYLISTIC_SET] = TTRC("Stylistic Sets");852group_names[FGRP_CHARACTER_VARIANT] = TTRC("Character Variants");853group_names[FGRP_CAPITLS] = TTRC("Capitals");854group_names[FGRP_LIGATURES] = TTRC("Ligatures");855group_names[FGRP_ALTERNATES] = TTRC("Alternates");856group_names[FGRP_EAL] = TTRC("East Asian Language");857group_names[FGRP_EAW] = TTRC("East Asian Widths");858group_names[FGRP_NUMAL] = TTRC("Numeral Alignment");859group_names[FGRP_CUSTOM] = TTRC("Custom");860}861862/*************************************************************************/863/* EditorInspectorPluginFontVariation */864/*************************************************************************/865866bool EditorInspectorPluginFontVariation::can_handle(Object *p_object) {867return (Object::cast_to<FontVariation>(p_object) != nullptr) || (Object::cast_to<DynamicFontImportSettingsData>(p_object) != nullptr);868}869870bool 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) {871if (p_path == "variation_opentype") {872add_property_editor(p_path, memnew(EditorPropertyOTVariation));873return true;874} else if (p_path == "opentype_features") {875add_property_editor(p_path, memnew(EditorPropertyOTFeatures));876return true;877} else if (p_path == "language_support") {878add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(false)));879return true;880} else if (p_path == "script_support") {881add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(true)));882return true;883}884return false;885}886887/*************************************************************************/888/* FontPreview */889/*************************************************************************/890891void FontPreview::_notification(int p_what) {892switch (p_what) {893case NOTIFICATION_DRAW: {894// Draw font name (style).895Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));896int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));897Color text_color = get_theme_color(SceneStringName(font_color), SNAME("Label"));898899// Draw font preview.900bool prev_ok = true;901if (prev_font.is_valid()) {902if (prev_font->get_font_name().is_empty()) {903prev_ok = false;904} else {905String name;906if (prev_font->get_font_style_name().is_empty()) {907name = prev_font->get_font_name();908} else {909name = vformat("%s (%s)", prev_font->get_font_name(), prev_font->get_font_style_name());910}911if (prev_font->is_class("FontVariation")) {912// TRANSLATORS: This refers to variable font config, appended to the font name.913name += " - " + TTR("Variation");914}915font->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);916917String sample;918static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";919for (int i = 0; i < sample_base.length(); i++) {920if (prev_font->has_char(sample_base[i])) {921sample += sample_base[i];922}923}924if (sample.is_empty()) {925sample = prev_font->get_supported_chars().substr(0, 6);926}927if (sample.is_empty()) {928prev_ok = false;929} else {930prev_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);931}932}933}934if (!prev_ok) {935text_color.a *= 0.5;936font->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);937}938} break;939940case NOTIFICATION_EXIT_TREE: {941if (prev_font.is_valid()) {942prev_font->disconnect_changed(callable_mp(this, &FontPreview::_preview_changed));943}944} break;945}946}947948void FontPreview::_bind_methods() {}949950Size2 FontPreview::get_minimum_size() const {951return Vector2(64, 64) * EDSCALE;952}953954void FontPreview::set_data(const Ref<Font> &p_f) {955if (prev_font.is_valid()) {956prev_font->disconnect_changed(callable_mp(this, &FontPreview::_preview_changed));957}958prev_font = p_f;959if (prev_font.is_valid()) {960prev_font->connect_changed(callable_mp(this, &FontPreview::_preview_changed));961}962queue_redraw();963}964965void FontPreview::_preview_changed() {966queue_redraw();967}968969/*************************************************************************/970/* EditorInspectorPluginFontPreview */971/*************************************************************************/972973bool EditorInspectorPluginFontPreview::can_handle(Object *p_object) {974return Object::cast_to<Font>(p_object) != nullptr;975}976977void EditorInspectorPluginFontPreview::parse_begin(Object *p_object) {978Font *fd = Object::cast_to<Font>(p_object);979ERR_FAIL_NULL(fd);980981FontPreview *editor = memnew(FontPreview);982editor->set_data(fd);983add_custom_control(editor);984}985986bool 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) {987return false;988}989990/*************************************************************************/991/* EditorPropertyFontNamesArray */992/*************************************************************************/993994void EditorPropertyFontNamesArray::_add_element() {995Size2 size = get_size();996menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));997menu->reset_size();998menu->popup();999}10001001void EditorPropertyFontNamesArray::_add_font(int p_option) {1002if (updating) {1003return;1004}10051006Variant array = object->get_array();1007int previous_size = array.call("size");10081009array.call("resize", previous_size + 1);1010array.set(previous_size, menu->get_item_text(p_option));10111012emit_changed(get_edited_property(), array, "", false);1013object->set_array(array);1014update_property();1015}10161017EditorPropertyFontNamesArray::EditorPropertyFontNamesArray() {1018menu = memnew(PopupMenu);1019menu->add_item("Sans-Serif", 0);1020menu->add_item("Serif", 1);1021menu->add_item("Monospace", 2);1022menu->add_item("Fantasy", 3);1023menu->add_item("Cursive", 4);10241025menu->add_separator();10261027if (OS::get_singleton()) {1028Vector<String> fonts = OS::get_singleton()->get_system_fonts();1029fonts.sort();1030for (int i = 0; i < fonts.size(); i++) {1031menu->add_item(fonts[i], i + 6);1032}1033}1034add_child(menu);1035menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyFontNamesArray::_add_font));1036}10371038/*************************************************************************/1039/* EditorInspectorPluginSystemFont */1040/*************************************************************************/10411042bool EditorInspectorPluginSystemFont::can_handle(Object *p_object) {1043return Object::cast_to<SystemFont>(p_object) != nullptr;1044}10451046bool 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) {1047if (p_path == "font_names") {1048EditorPropertyFontNamesArray *editor = memnew(EditorPropertyFontNamesArray);1049editor->setup(p_type, p_hint_text);1050add_property_editor(p_path, editor);1051return true;1052}1053return false;1054}10551056/*************************************************************************/1057/* FontEditorPlugin */1058/*************************************************************************/10591060FontEditorPlugin::FontEditorPlugin() {1061Ref<EditorInspectorPluginFontVariation> fc_plugin;1062fc_plugin.instantiate();1063EditorInspector::add_inspector_plugin(fc_plugin);10641065Ref<EditorInspectorPluginSystemFont> fs_plugin;1066fs_plugin.instantiate();1067EditorInspector::add_inspector_plugin(fs_plugin);10681069Ref<EditorInspectorPluginFontPreview> fp_plugin;1070fp_plugin.instantiate();1071EditorInspector::add_inspector_plugin(fp_plugin);1072}107310741075