Path: blob/master/editor/scene/gui/font_config_plugin.cpp
9902 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);467prop->setup(range.x, range.y, false, true, false, false);468prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));469470String name = TS->tag_to_name(name_tag);471String name_cap;472{473String aux = name.replace_char('_', ' ').strip_edges();474for (int j = 0; j < aux.get_slice_count(" "); j++) {475String slice = aux.get_slicec(' ', j);476if (slice.length() > 0) {477slice[0] = String::char_uppercase(slice[0]);478if (i > 0) {479name_cap += " ";480}481name_cap += slice;482}483}484}485prop->set_label(name_cap);486prop->set_tooltip_text(name);487prop->set_selectable(false);488489prop->connect("property_changed", callable_mp(this, &EditorPropertyOTVariation::_property_changed));490prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTVariation::_object_id_selected));491492property_vbox->add_child(prop);493494prop->update_property();495}496497updating = false;498} else {499if (container) {500set_bottom_editor(nullptr);501memdelete(container);502container = nullptr;503}504}505}506507void EditorPropertyOTVariation::_edit_pressed() {508Variant prop_val = get_edited_property_value();509if (prop_val.get_type() == Variant::NIL) {510Callable::CallError ce;511Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);512get_edited_object()->set(get_edited_property(), prop_val);513}514515get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());516update_property();517}518519void EditorPropertyOTVariation::_page_changed(int p_page) {520if (updating) {521return;522}523page_index = p_page;524update_property();525}526527EditorPropertyOTVariation::EditorPropertyOTVariation() {528object.instantiate();529page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));530531edit = memnew(Button);532edit->set_accessibility_name(TTRC("Edit"));533edit->set_h_size_flags(SIZE_EXPAND_FILL);534edit->set_clip_text(true);535edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTVariation::_edit_pressed));536edit->set_toggle_mode(true);537add_child(edit);538add_focusable(edit);539}540541/*************************************************************************/542/* EditorPropertyOTFeatures */543/*************************************************************************/544545void EditorPropertyOTFeatures::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {546if (p_property.begins_with("keys")) {547Dictionary dict = object->get_dict();548int key = p_property.get_slicec('/', 1).to_int();549dict[key] = (int)p_value;550551emit_changed(get_edited_property(), dict, "", true);552553dict = dict.duplicate(); // Duplicate, so undo/redo works better.554object->set_dict(dict);555}556}557558void EditorPropertyOTFeatures::_remove(Object *p_button, int p_key) {559Dictionary dict = object->get_dict();560561dict.erase(p_key);562563emit_changed(get_edited_property(), dict, "", false);564565dict = dict.duplicate(); // Duplicate, so undo/redo works better.566object->set_dict(dict);567update_property();568}569570void EditorPropertyOTFeatures::_add_menu() {571Size2 size = get_size();572menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));573menu->reset_size();574menu->popup();575}576577void EditorPropertyOTFeatures::_add_feature(int p_option) {578Dictionary dict = object->get_dict();579580dict[p_option] = 1;581582emit_changed(get_edited_property(), dict, "", false);583584dict = dict.duplicate(); // Duplicate, so undo/redo works better.585object->set_dict(dict);586update_property();587}588589void EditorPropertyOTFeatures::_object_id_selected(const StringName &p_property, ObjectID p_id) {590emit_signal(SNAME("object_id_selected"), p_property, p_id);591}592593void EditorPropertyOTFeatures::update_property() {594Variant updated_val = get_edited_property_value();595596Dictionary dict = updated_val;597598Ref<Font> fd;599if (Object::cast_to<FontVariation>(get_edited_object()) != nullptr) {600fd = get_edited_object();601} else if (Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()) != nullptr) {602Ref<DynamicFontImportSettingsData> imp = Object::cast_to<DynamicFontImportSettingsData>(get_edited_object());603fd = imp->get_font();604}605606Dictionary supported;607if (fd.is_valid()) {608supported = fd->get_supported_feature_list();609}610611if (supported.is_empty()) {612edit->set_text(vformat(TTR("No supported features")));613if (container) {614set_bottom_editor(nullptr);615memdelete(container);616container = nullptr;617}618return;619}620edit->set_text(vformat(TTR("Features (%d of %d set)"), dict.size(), supported.size()));621622bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());623if (edit->is_pressed() != unfolded) {624edit->set_pressed(unfolded);625}626627if (unfolded) {628updating = true;629630if (!container) {631container = memnew(MarginContainer);632container->set_theme_type_variation("MarginContainer4px");633add_child(container);634set_bottom_editor(container);635636VBoxContainer *vbox = memnew(VBoxContainer);637vbox->set_v_size_flags(SIZE_EXPAND_FILL);638container->add_child(vbox);639640property_vbox = memnew(VBoxContainer);641property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);642vbox->add_child(property_vbox);643644paginator = memnew(EditorPaginator);645paginator->connect("page_changed", callable_mp(this, &EditorPropertyOTFeatures::_page_changed));646vbox->add_child(paginator);647} else {648// Queue children for deletion, deleting immediately might cause errors.649for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {650property_vbox->get_child(i)->queue_free();651}652}653654// Update add menu items.655menu->clear(false);656bool have_sub[FGRP_MAX];657for (int i = 0; i < FGRP_MAX; i++) {658menu_sub[i]->clear();659have_sub[i] = false;660}661662bool show_hidden = EDITOR_GET("interface/inspector/show_low_level_opentype_features");663664for (int i = 0; i < supported.size(); i++) {665int name_tag = supported.get_key_at_index(i);666Dictionary info = supported.get_value_at_index(i);667bool hidden = info["hidden"].operator bool();668String name = TS->tag_to_name(name_tag);669FeatureGroups grp = FGRP_MAX;670671if (hidden && !show_hidden) {672continue;673}674675if (name.begins_with("stylistic_set_")) {676grp = FGRP_STYLISTIC_SET;677} else if (name.begins_with("character_variant_")) {678grp = FGRP_CHARACTER_VARIANT;679} else if (name.ends_with("_capitals")) {680grp = FGRP_CAPITLS;681} else if (name.ends_with("_ligatures")) {682grp = FGRP_LIGATURES;683} else if (name.ends_with("_alternates")) {684grp = FGRP_ALTERNATES;685} else if (name.ends_with("_kanji_forms") || name.begins_with("jis") || name == "simplified_forms" || name == "traditional_name_forms" || name == "traditional_forms") {686grp = FGRP_EAL;687} else if (name.ends_with("_widths")) {688grp = FGRP_EAW;689} else if (name == "tabular_figures" || name == "proportional_figures") {690grp = FGRP_NUMAL;691} else if (name.begins_with("custom_")) {692grp = FGRP_CUSTOM;693}694String disp_name = name.capitalize();695if (info.has("label")) {696disp_name = vformat("%s (%s)", disp_name, info["label"].operator String());697}698699if (grp == FGRP_MAX) {700menu->add_item(disp_name, name_tag);701} else {702menu_sub[grp]->add_item(disp_name, name_tag);703have_sub[grp] = true;704}705}706for (int i = 0; i < FGRP_MAX; i++) {707if (have_sub[i]) {708menu->add_submenu_node_item(TTRGET(group_names[i]), menu_sub[i]);709}710}711712int size = dict.size();713714int max_page = MAX(0, size - 1) / page_length;715page_index = MIN(page_index, max_page);716717paginator->update(page_index, max_page);718paginator->set_visible(max_page > 0);719720int offset = page_index * page_length;721722int amount = MIN(size - offset, page_length);723724dict = dict.duplicate();725object->set_dict(dict);726727for (int i = 0; i < amount; i++) {728int name_tag = dict.get_key_at_index(i);729730if (supported.has(name_tag)) {731Dictionary info = supported[name_tag];732Variant::Type vtype = Variant::Type(info["type"].operator int());733bool hidden = info["hidden"].operator bool();734if (hidden && !show_hidden) {735continue;736}737738EditorProperty *prop = nullptr;739switch (vtype) {740case Variant::NIL: {741prop = memnew(EditorPropertyNil);742} break;743case Variant::BOOL: {744prop = memnew(EditorPropertyCheck);745} break;746case Variant::INT: {747EditorPropertyInteger *editor = memnew(EditorPropertyInteger);748editor->setup(0, 255, 1, false, false, false);749prop = editor;750} break;751default: {752ERR_CONTINUE_MSG(true, vformat("Unsupported OT feature data type %s", Variant::get_type_name(vtype)));753}754}755prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));756757String name = TS->tag_to_name(name_tag);758String disp_name = name.capitalize();759if (info.has("label")) {760disp_name = vformat("%s (%s)", disp_name, info["label"].operator String());761}762prop->set_label(disp_name);763prop->set_tooltip_text(name);764prop->set_selectable(false);765766prop->connect("property_changed", callable_mp(this, &EditorPropertyOTFeatures::_property_changed));767prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTFeatures::_object_id_selected));768769HBoxContainer *hbox = memnew(HBoxContainer);770property_vbox->add_child(hbox);771hbox->add_child(prop);772prop->set_h_size_flags(SIZE_EXPAND_FILL);773Button *remove = memnew(Button);774remove->set_accessibility_name(TTRC("Remove"));775remove->set_button_icon(get_editor_theme_icon(SNAME("Remove")));776hbox->add_child(remove);777remove->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_remove).bind(remove, name_tag));778779prop->update_property();780}781}782783EditorInspectorActionButton *button_add = memnew(EditorInspectorActionButton(TTRC("Add Feature"), SNAME("Add")));784button_add->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_add_menu));785property_vbox->add_child(button_add);786787updating = false;788} else {789if (container) {790set_bottom_editor(nullptr);791memdelete(container);792container = nullptr;793}794}795}796797void EditorPropertyOTFeatures::_edit_pressed() {798Variant prop_val = get_edited_property_value();799if (prop_val.get_type() == Variant::NIL) {800Callable::CallError ce;801Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce);802get_edited_object()->set(get_edited_property(), prop_val);803}804805get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());806update_property();807}808809void EditorPropertyOTFeatures::_page_changed(int p_page) {810if (updating) {811return;812}813page_index = p_page;814update_property();815}816817EditorPropertyOTFeatures::EditorPropertyOTFeatures() {818object.instantiate();819page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));820821edit = memnew(Button);822edit->set_accessibility_name(TTRC("Edit"));823edit->set_h_size_flags(SIZE_EXPAND_FILL);824edit->set_clip_text(true);825edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyOTFeatures::_edit_pressed));826edit->set_toggle_mode(true);827add_child(edit);828add_focusable(edit);829830menu = memnew(PopupMenu);831add_child(menu);832menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyOTFeatures::_add_feature));833834for (int i = 0; i < FGRP_MAX; i++) {835menu_sub[i] = memnew(PopupMenu);836menu->add_child(menu_sub[i]);837menu_sub[i]->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyOTFeatures::_add_feature));838}839840group_names[FGRP_STYLISTIC_SET] = TTRC("Stylistic Sets");841group_names[FGRP_CHARACTER_VARIANT] = TTRC("Character Variants");842group_names[FGRP_CAPITLS] = TTRC("Capitals");843group_names[FGRP_LIGATURES] = TTRC("Ligatures");844group_names[FGRP_ALTERNATES] = TTRC("Alternates");845group_names[FGRP_EAL] = TTRC("East Asian Language");846group_names[FGRP_EAW] = TTRC("East Asian Widths");847group_names[FGRP_NUMAL] = TTRC("Numeral Alignment");848group_names[FGRP_CUSTOM] = TTRC("Custom");849}850851/*************************************************************************/852/* EditorInspectorPluginFontVariation */853/*************************************************************************/854855bool EditorInspectorPluginFontVariation::can_handle(Object *p_object) {856return (Object::cast_to<FontVariation>(p_object) != nullptr) || (Object::cast_to<DynamicFontImportSettingsData>(p_object) != nullptr);857}858859bool 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) {860if (p_path == "variation_opentype") {861add_property_editor(p_path, memnew(EditorPropertyOTVariation));862return true;863} else if (p_path == "opentype_features") {864add_property_editor(p_path, memnew(EditorPropertyOTFeatures));865return true;866} else if (p_path == "language_support") {867add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(false)));868return true;869} else if (p_path == "script_support") {870add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(true)));871return true;872}873return false;874}875876/*************************************************************************/877/* FontPreview */878/*************************************************************************/879880void FontPreview::_notification(int p_what) {881switch (p_what) {882case NOTIFICATION_DRAW: {883// Draw font name (style).884Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));885int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));886Color text_color = get_theme_color(SceneStringName(font_color), SNAME("Label"));887888// Draw font preview.889bool prev_ok = true;890if (prev_font.is_valid()) {891if (prev_font->get_font_name().is_empty()) {892prev_ok = false;893} else {894String name;895if (prev_font->get_font_style_name().is_empty()) {896name = prev_font->get_font_name();897} else {898name = vformat("%s (%s)", prev_font->get_font_name(), prev_font->get_font_style_name());899}900if (prev_font->is_class("FontVariation")) {901// TRANSLATORS: This refers to variable font config, appended to the font name.902name += " - " + TTR("Variation");903}904font->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);905906String sample;907static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";908for (int i = 0; i < sample_base.length(); i++) {909if (prev_font->has_char(sample_base[i])) {910sample += sample_base[i];911}912}913if (sample.is_empty()) {914sample = prev_font->get_supported_chars().substr(0, 6);915}916if (sample.is_empty()) {917prev_ok = false;918} else {919prev_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);920}921}922}923if (!prev_ok) {924text_color.a *= 0.5;925font->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);926}927} break;928929case NOTIFICATION_EXIT_TREE: {930if (prev_font.is_valid()) {931prev_font->disconnect_changed(callable_mp(this, &FontPreview::_preview_changed));932}933} break;934}935}936937void FontPreview::_bind_methods() {}938939Size2 FontPreview::get_minimum_size() const {940return Vector2(64, 64) * EDSCALE;941}942943void FontPreview::set_data(const Ref<Font> &p_f) {944if (prev_font.is_valid()) {945prev_font->disconnect_changed(callable_mp(this, &FontPreview::_preview_changed));946}947prev_font = p_f;948if (prev_font.is_valid()) {949prev_font->connect_changed(callable_mp(this, &FontPreview::_preview_changed));950}951queue_redraw();952}953954void FontPreview::_preview_changed() {955queue_redraw();956}957958/*************************************************************************/959/* EditorInspectorPluginFontPreview */960/*************************************************************************/961962bool EditorInspectorPluginFontPreview::can_handle(Object *p_object) {963return Object::cast_to<Font>(p_object) != nullptr;964}965966void EditorInspectorPluginFontPreview::parse_begin(Object *p_object) {967Font *fd = Object::cast_to<Font>(p_object);968ERR_FAIL_NULL(fd);969970FontPreview *editor = memnew(FontPreview);971editor->set_data(fd);972add_custom_control(editor);973}974975bool 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) {976return false;977}978979/*************************************************************************/980/* EditorPropertyFontNamesArray */981/*************************************************************************/982983void EditorPropertyFontNamesArray::_add_element() {984Size2 size = get_size();985menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));986menu->reset_size();987menu->popup();988}989990void EditorPropertyFontNamesArray::_add_font(int p_option) {991if (updating) {992return;993}994995Variant array = object->get_array();996int previous_size = array.call("size");997998array.call("resize", previous_size + 1);999array.set(previous_size, menu->get_item_text(p_option));10001001emit_changed(get_edited_property(), array, "", false);1002object->set_array(array);1003update_property();1004}10051006EditorPropertyFontNamesArray::EditorPropertyFontNamesArray() {1007menu = memnew(PopupMenu);1008menu->add_item("Sans-Serif", 0);1009menu->add_item("Serif", 1);1010menu->add_item("Monospace", 2);1011menu->add_item("Fantasy", 3);1012menu->add_item("Cursive", 4);10131014menu->add_separator();10151016if (OS::get_singleton()) {1017Vector<String> fonts = OS::get_singleton()->get_system_fonts();1018fonts.sort();1019for (int i = 0; i < fonts.size(); i++) {1020menu->add_item(fonts[i], i + 6);1021}1022}1023add_child(menu);1024menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyFontNamesArray::_add_font));1025}10261027/*************************************************************************/1028/* EditorInspectorPluginSystemFont */1029/*************************************************************************/10301031bool EditorInspectorPluginSystemFont::can_handle(Object *p_object) {1032return Object::cast_to<SystemFont>(p_object) != nullptr;1033}10341035bool 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) {1036if (p_path == "font_names") {1037EditorPropertyFontNamesArray *editor = memnew(EditorPropertyFontNamesArray);1038editor->setup(p_type, p_hint_text);1039add_property_editor(p_path, editor);1040return true;1041}1042return false;1043}10441045/*************************************************************************/1046/* FontEditorPlugin */1047/*************************************************************************/10481049FontEditorPlugin::FontEditorPlugin() {1050Ref<EditorInspectorPluginFontVariation> fc_plugin;1051fc_plugin.instantiate();1052EditorInspector::add_inspector_plugin(fc_plugin);10531054Ref<EditorInspectorPluginSystemFont> fs_plugin;1055fs_plugin.instantiate();1056EditorInspector::add_inspector_plugin(fs_plugin);10571058Ref<EditorInspectorPluginFontPreview> fp_plugin;1059fp_plugin.instantiate();1060EditorInspector::add_inspector_plugin(fp_plugin);1061}106210631064