Example #1
0
        /// <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);
            }
        }
Example #6
0
        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();
        }