/// <summary> /// Creates a menu containing items for each available language, /// and for adding new languages. /// </summary> /// <returns>The prepared menu.</returns> private GenericMenu CreateLanguageMenu() { var menu = new GenericMenu(); // Build the list of locales that are present in the currently // selected LocalizationDatabase(s). var locales = new List <string>(); foreach (SerializedProperty localization in localizationsProperty) { if (localization.objectReferenceValue == null) { continue; } var localeCode = (localization.objectReferenceValue as Localization).LocaleCode; locales.Add(localeCode); } foreach (var languageName in ProjectSettings.TextProjectLanguages) { var culture = Cultures.GetCulture(languageName); // A GUIContent for showing the display name of the culture GUIContent languageDisplayNameContent = new GUIContent(culture.DisplayName); // Does this language already exist in the selected // database(s)? if (locales.Contains(languageName)) { // Then add a disabled menu item to represent the fact // that it's a valid language, but can't be added again menu.AddDisabledItem(languageDisplayNameContent); } else { menu.AddItem( languageDisplayNameContent, false, CreateLocalizationWithLanguage, languageName); } } // If there were zero languages available to add, include a // note in this menu if (menu.GetItemCount() == 0) { menu.AddDisabledItem(new GUIContent("No languages available")); } // Finally, add a quick way to get to the screen that lets us // add new languages menu.AddSeparator(""); menu.AddItem(new GUIContent("Add More Languages..."), false, ShowYarnSpinnerProjectSettings); return(menu); }
public override void OnActivate(string searchContext, VisualElement rootElement) { // Initialize project settings variables _projectSettings = new SerializedObject(ScriptableObject.CreateInstance <ProjectSettings>()); var textLanguages = _projectSettings.FindProperty("_textProjectLanguages"); var audioLanguages = _projectSettings.FindProperty("_audioProjectLanguages"); // Initialize user settings variables (preference variables) _preferences = new SerializedObject(ScriptableObject.CreateInstance <Preferences>()); _textLanguages = ProjectSettings.TextProjectLanguages; _audioLanguage = ProjectSettings.AudioProjectLanguages; _textLanguageLastFrame = Preferences.TextLanguage; _audioLanguageLastFrame = Preferences.AudioLanguage; // Initialize visual representation of the language lists _textLanguagesReorderableList = new ReorderableList(_projectSettings, textLanguages, true, true, false, true); // Add labels to the lists // Show available text languages to the left and available audio languages to the right _textLanguagesReorderableList.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(new Rect(rect.x, rect.y, rect.width * 0.65f, EditorGUIUtility.singleLineHeight), "Languages"); EditorGUI.LabelField(new Rect(rect.width * 0.65f, rect.y, rect.width * 0.75f, EditorGUIUtility.singleLineHeight), "Has Audio"); }; // How an element of the lists should be drawn // Text languages will be drawn left as a label with their display name (-> "English") // Audio languages will be drawn right as a bool/toggle field indicating their use (-> true/false) // This communicates visually that for adding a voice over language a coresponding text language must exist already _textLanguagesReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { var languageId = _textLanguagesReorderableList.serializedProperty.GetArrayElementAtIndex(index); var displayName = Cultures.GetCultures().FirstOrDefault(c => c.Name == languageId.stringValue).DisplayName ?? "<no display name>"; rect.y += 2; EditorGUI.LabelField(new Rect(rect.x, rect.y, rect.width * 0.7f, EditorGUIUtility.singleLineHeight), displayName); var textLanguageOnAudio = ProjectSettings.AudioProjectLanguages.Contains(languageId.stringValue); var audioBool = EditorGUI.Toggle(new Rect(rect.width * 0.7f, rect.y, rect.width * 0.3f, EditorGUIUtility.singleLineHeight), textLanguageOnAudio); if (audioBool != textLanguageOnAudio) { if (audioBool) { audioLanguages.InsertArrayElementAtIndex(audioLanguages.arraySize); audioLanguages.GetArrayElementAtIndex(audioLanguages.arraySize - 1).stringValue = languageId.stringValue; } else { var audiolanguageIndex = ProjectSettings.AudioProjectLanguages.IndexOf(languageId.stringValue); audioLanguages.DeleteArrayElementAtIndex(audiolanguageIndex); } } }; }
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { // If this property is not a string, fall back to default implementation. if (property.propertyType != SerializedPropertyType.String) { EditorGUI.PropertyField(position, property, label); return; } // Display this property as a dropdown that lets you select a language. var allCultures = Cultures.GetCultures().ToList(); var indices = Enumerable.Range(0, allCultures.Count()); var culturesToIndicies = allCultures.Zip(indices, (culture, index) => new { culture, index }).ToDictionary(pair => pair.culture.Name, pair => pair.index); var value = property.stringValue; int currentCultureIndex; if (culturesToIndicies.ContainsKey(value)) { // The property doesn't contain a valid culture name. // Default it to the current locale, and also update the property so that it // value = System.Globalization.CultureInfo.CurrentCulture.Name; // property.stringValue = value; currentCultureIndex = culturesToIndicies[value]; } else { currentCultureIndex = -1; } var allCultureDisplayNames = allCultures.Select(c => c.DisplayName).Select(n => new GUIContent(n)).ToArray(); using (var changeCheck = new EditorGUI.ChangeCheckScope()) { var selectedIndex = EditorGUI.Popup(position, label, currentCultureIndex, allCultureDisplayNames); if (changeCheck.changed) { property.stringValue = allCultures[selectedIndex].Name; } } }
/// <summary> /// Draws a language selection popup used for showing a user's language preference. /// Returns the corresponding SerializedProperty of the drawn language selection popup which be used after ApplyModifiedProperties() to detect changes to the settings. /// </summary> /// <param name="languagePreference">Determines wheter to draw the Text Language preference or the Audio Language preference.</param> private SerializedProperty DrawLanguagePreference(LanguagePreference languagePreference) { // Declare and set variables depending on the type of language preference to draw List <string> languages = default; SerializedProperty preferencesProperty = default; string defaultProjectLanguage = default; string infoMessageOnEmptyProjectLanguageList = default; string languagePopupLabel = default; switch (languagePreference) { case LanguagePreference.TextLanguage: languages = _textLanguages; preferencesProperty = _preferences.FindProperty("_textLanguage"); defaultProjectLanguage = ProjectSettings.TextProjectLanguageDefault; infoMessageOnEmptyProjectLanguageList = _emptyTextLanguageMessage; languagePopupLabel = "Text Language"; _textLanguageLastFrame = preferencesProperty.stringValue; break; case LanguagePreference.AudioLanguage: languages = _audioLanguage; preferencesProperty = _preferences.FindProperty("_audioLanguage"); defaultProjectLanguage = ProjectSettings.AudioProjectLanguageDefault; infoMessageOnEmptyProjectLanguageList = _emptyAudioLanguageMessage; languagePopupLabel = "Audio Language"; _audioLanguageLastFrame = preferencesProperty.stringValue; break; } // Get currently available languages and determine which the selected language should be int selectedLanguageIndex = -1; string[] languagesNamesAvailableForSelection = languages.Count > 0 ? languages.ToArray() : System.Array.Empty <string>(); var selectedLanguage = languagesNamesAvailableForSelection .Select((name, index) => new { name, index }) .FirstOrDefault(element => element.name == preferencesProperty.stringValue); if (selectedLanguage != null) { selectedLanguageIndex = selectedLanguage.index; } else if (!string.IsNullOrEmpty(defaultProjectLanguage)) { // Assign default language in case the currently selected language has become invalid selectedLanguageIndex = 0; } string[] languagesDisplayNamesAvailableForSelection = languagesNamesAvailableForSelection.Select(name => Cultures.GetCultures().FirstOrDefault(c => c.Name == name).DisplayName).ToArray(); // Disable popup and show message box in case the project languages have been defined yet if (languagesNamesAvailableForSelection.Length == 0) { GUI.enabled = false; EditorGUILayout.HelpBox(infoMessageOnEmptyProjectLanguageList, MessageType.Info); } // Draw the actual language popup selectedLanguageIndex = EditorGUILayout.Popup(languagePopupLabel, selectedLanguageIndex, languagesDisplayNamesAvailableForSelection); // Change/set the language ID if (selectedLanguageIndex != -1) { preferencesProperty.stringValue = languagesNamesAvailableForSelection[selectedLanguageIndex]; } else { // null the language ID since the index is invalid preferencesProperty.stringValue = string.Empty; } GUI.enabled = true; return(preferencesProperty); }
public override void OnGUI(string searchContext) { if (_projectSettings == null || _projectSettings.targetObject == null) { return; } _projectSettings.Update(); GUILayout.Label("Project Languages", EditorStyles.boldLabel); // Text languages var textLanguagesProp = _projectSettings.FindProperty("_textProjectLanguages"); var textLanguages = ProjectSettings.TextProjectLanguages; var remainingTextLanguages = Cultures.GetCultures().Where(c => textLanguages.Contains(c.NativeName) == false).ToArray(); var remainingTextLanguagesDisplayNames = remainingTextLanguages.Select(c => c.DisplayName).ToArray(); // Button and Dropdown List for adding a language GUILayout.BeginHorizontal(); if (remainingTextLanguages.Length < 1) { GUI.enabled = false; GUILayout.Button("No more available Project Languages"); GUI.enabled = true; } else { if (GUILayout.Button("Add language to project")) { textLanguagesProp.InsertArrayElementAtIndex(textLanguagesProp.arraySize); textLanguagesProp.GetArrayElementAtIndex(textLanguagesProp.arraySize - 1).stringValue = remainingTextLanguages[_textLanguagesListIndex].Name; _textLanguagesListIndex = 0; } } _textLanguagesListIndex = EditorGUILayout.Popup(_textLanguagesListIndex, remainingTextLanguagesDisplayNames); GUILayout.EndHorizontal(); // Text Language List _textLanguagesReorderableList.DoLayoutList(); // Audio languages (sub-selection from available text languages) var audioLanguagesProp = _projectSettings.FindProperty("_audioProjectLanguages"); var audioLanguages = ProjectSettings.AudioProjectLanguages; // Cleanup Audio Language List from languages that have been removed from the Project Languages for (int i = audioLanguages.Count - 1; i >= 0; i--) { string language = (string)audioLanguages[i]; if (!textLanguages.Contains(language)) { audioLanguagesProp.DeleteArrayElementAtIndex(i); } } _projectSettings.ApplyModifiedProperties(); // User's language preferences if (_preferences == null || _preferences.targetObject == null) { return; } GUILayout.Label("Language Preferences", EditorStyles.boldLabel); _preferences.Update(); // Text language popup related things SerializedProperty textLanguageProp = DrawLanguagePreference(LanguagePreference.TextLanguage); // Audio language popup related things SerializedProperty audioLanguageProp = DrawLanguagePreference(LanguagePreference.AudioLanguage); #if ADDRESSABLES GUILayout.Label("Voice Over Asset Handling", EditorStyles.boldLabel); var addressableVoiceOverAudioClipsProp = _projectSettings.FindProperty("_addressableVoiceOverAudioClips"); EditorGUILayout.PropertyField(addressableVoiceOverAudioClipsProp, new GUIContent("Use Addressables")); string message = $"This project has the Addressable Assets package installed. When this option is selected, {ObjectNames.NicifyVariableName(nameof(LocalizationDatabase)).ToLowerInvariant()}s will use addressable asset references to refer to assets that belong to lines, rather than directly referencing the asset. This allows for better performance during steps that have a large amount of dependencies.\n\nFor more information, click this box to open the Yarn Spinner documentation."; EditorGUILayout.HelpBox(message, MessageType.Info); // Make the HelpBox that we just rendered have a link cursor var lastRect = GUILayoutUtility.GetLastRect(); EditorGUIUtility.AddCursorRect(lastRect, MouseCursor.Link); // And also detect clicks on it; open the documentation when // this happens if (Event.current.type == EventType.MouseUp && lastRect.Contains(Event.current.mousePosition)) { Application.OpenURL(AddressableAssetsDocumentationURL); } _projectSettings.ApplyModifiedProperties(); #endif _preferences.ApplyModifiedProperties(); // Trigger events in case the preferences have been changed if (_textLanguageLastFrame != textLanguageProp.stringValue) { Preferences.LanguagePreferencesChanged?.Invoke(this, System.EventArgs.Empty); } if (_audioLanguageLastFrame != audioLanguageProp.stringValue) { Preferences.LanguagePreferencesChanged?.Invoke(this, System.EventArgs.Empty); } }
public override void OnInspectorGUI() { // If true, at least one of the Localizations in the selected // LocalizationDatabases are null references; in this case, the // Add New Localization button will be disabled (it will be // re-enabled when the empty field is filled) bool anyLocalizationsAreNull = false; if (localizationsProperty.arraySize == 0) { EditorGUILayout.HelpBox($"This {ObjectNames.NicifyVariableName(nameof(LocalizationDatabase)).ToLowerInvariant()} has no {ObjectNames.NicifyVariableName(nameof(Localization)).ToLowerInvariant()}s. Create a new one, or add an existing one.", MessageType.Info); } foreach (SerializedProperty property in localizationsProperty) { // The locale code for this localization ("en-AU") string localeCode = null; // The human-friendly code for this localization ("English // (Australia)") string localeCodeDisplayName = null; // If true, a localization asset is present in this // property bool localizationPresent = false; // If true, the locale code for this localization asset // exists inside the Cultures class's list bool cultureIsValid = false; if (property.objectReferenceValue != null) { localeCode = (property.objectReferenceValue as Localization).LocaleCode; localizationPresent = true; cultureIsValid = Cultures.HasCulture(localeCode); if (cultureIsValid) { localeCodeDisplayName = Cultures.GetCulture(localeCode).DisplayName; } else { localeCodeDisplayName = "Invalid"; } } else { // This property is empty. We'll end up drawing an // empty object field here; record this so that we know // to disable the 'Add Existing' button later. anyLocalizationsAreNull = true; } using (new EditorGUILayout.HorizontalScope()) { // If a localization is present, show the display name // and locale code; otherwise, show null (which will // make the corresponding empty field take up the whole // width) string labelContents; if (localizationPresent) { labelContents = localeCodeDisplayName + $" ({localeCode})"; } else { labelContents = ""; } // Show the property field for this element in the // array EditorGUILayout.PropertyField(property, new GUIContent(labelContents)); // Show a button that removes this element if (GUILayout.Button("-", EditorStyles.miniButton, GUILayout.ExpandWidth(false))) { // Remove the element from the slot in the array... property.DeleteCommand(); // ... and remove the empty slot from the array. property.DeleteCommand(); } } // Is a locale code set that's invalid? if (localeCode == null) { EditorGUILayout.HelpBox($"Drag and drop a {nameof(Localization)} to this field to add it to this localization database.", MessageType.Info); } else { if (cultureIsValid == false) { // A locale code was set, but this locale code // isn't valid. Show a warning. EditorGUILayout.HelpBox($"'{localeCode}' is not a valid locale. This localization's contents won't be available in the game.", MessageType.Warning); } else if (ProjectSettings.TextProjectLanguages.Contains(localeCode) == false) { // The locale is valid, but the project settings // don't include this language. Show a warning (the // user won't be able to select this localization) const string fixButtonLabel = "Add to Language List"; EditorGUILayout.HelpBox($"{localeCodeDisplayName} is not in this project's language list. This localization's contents won't be available in the game.\n\nClick {fixButtonLabel} to fix this issue.", MessageType.Warning); if (GUILayout.Button(fixButtonLabel)) { ProjectSettings.AddNewTextLanguage(localeCode); // This will resolve the error, so we'll // immediately repaint Repaint(); } // Nice little space to visually associate the // 'add' button with the field and reduce confusion EditorGUILayout.Space(); } } } // Show the buttons for adding and creating localizations only // if we're not editing multiple databases. if (serializedObject.isEditingMultipleObjects == false) { // Disable the 'add existing' button if there's already an // empty field. (Clicking this button adds a new empty field, // so we don't want to end up creating multiples.) using (new EditorGUI.DisabledScope(anyLocalizationsAreNull)) { // Show the 'add existing' button, which adds a new // empty field for the user to drop an existing // Localization asset into. if (GUILayout.Button("Add Existing Localisation")) { localizationsProperty.InsertArrayElementAtIndex(localizationsProperty.arraySize); localizationsProperty.GetArrayElementAtIndex(localizationsProperty.arraySize - 1).objectReferenceValue = null; } } // Show the 'create new' button, which displays the menu of // available languages; selecting a language causes a new // localization asset to be created with that language, and // adds it to this database. if (GUILayout.Button("Create New Localization")) { var languageMenu = CreateLanguageMenu(); languageMenu.ShowAsContext(); } } GUILayout.Space(EditorGUIUtility.singleLineHeight); // Show information about the scripts we're pulling data from, // and offer to update the database now if (trackedProgramsProperty.arraySize == 0) { EditorGUILayout.HelpBox("No Yarn scripts currently use this database.\n\nTo make a Yarn script use this database, select one, and set its Localization Database to this file.", MessageType.Info); } else { if (serializedObject.isEditingMultipleObjects == false) { EditorGUILayout.LabelField("Uses lines from:"); EditorGUI.indentLevel += 1; // List every tracked program, but disable it (we don't // change them here, they're changed in the inspector for // the Yarn script.) using (new EditorGUI.DisabledScope(true)) { foreach (SerializedProperty trackedProgramProperty in trackedProgramsProperty) { EditorGUILayout.PropertyField(trackedProgramProperty, new GUIContent()); } } EditorGUI.indentLevel -= 1; EditorGUILayout.Space(); EditorGUILayout.HelpBox("This database will automatically update when the contents of these scripts change. If you modify the .csv files for other translations, modify any locale-specific assets, or if you need to manually update the database, click Update Database.", MessageType.Info); } if (GUILayout.Button("Update Database")) { foreach (LocalizationDatabase target in serializedObject.targetObjects) { LocalizationDatabaseUtility.UpdateContents(target); } } #if ADDRESSABLES // Give a helpful note if addressables are availalbe, but // haven't been set up. (In this circumstance, // Localizations won't be able to store references to the // assets they find.) if (AddressableAssetSettingsDefaultObject.SettingsExists == false) { EditorGUILayout.HelpBox("The Addressable Assets package has been added, but it hasn't been set up yet. Assets associated with lines won't be included in this database.\n\nTo set up Addressable Assets, choose Window > Asset Management > Addressables > Groups.", MessageType.Info); } #endif } serializedObject.ApplyModifiedProperties(); }