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);
            }
        }