private void ResetButtonPressed() { _maxResultsShownStagingValue = PluginConfig.MaxSearchResults; _stripSymbolsStagingValue = PluginConfig.StripSymbols; _splitQueryStagingValue = PluginConfig.SplitQueryByWords; _songFieldsStagingValue = PluginConfig.SongFieldsToSearch; _compactModeStagingValue = PluginConfig.CompactSearchMode; RefreshUI(); _resetButton.interactable = false; _applyButton.interactable = false; }
private IEnumerator _SearchSongs(string searchQuery) { int index = 0; string[] queryWords; bool stripSymbols = PluginConfig.StripSymbols; bool splitWords = PluginConfig.SplitQueryByWords; SearchableSongFields songFields = PluginConfig.SongFieldsToSearch; if (stripSymbols) { searchQuery = RemoveSymbolsRegex.Replace(searchQuery.ToLower(), string.Empty); } else { searchQuery = searchQuery.ToLower(); } if (splitWords) { queryWords = searchQuery.Split(SplitCharacters, StringSplitOptions.RemoveEmptyEntries); } else { queryWords = new string[] { searchQuery } }; while (index < _searchSpace.Count) { // limit the number of songs to evaluate so users with large song libraries don't pause on every key press for (int count = 0; count < MaxSearchInOneFrame && index < _searchSpace.Count; ++count) { if (!CheckSong(_searchSpace[index], stripSymbols, splitWords, songFields, queryWords)) { _searchSpace.RemoveAt(index); } else { ++index; } } yield return(null); } _searchCompletedAction?.Invoke(_searchSpace.ToArray()); _searchCoroutine = null; }
/// <summary> /// Search through all the songs provided and return a result immediately, rather than using a coroutine and a callback. /// This may cause the game to freeze momentarily for large song libraries. /// </summary> /// <param name="searchSpace">A list of songs to search through.</param> /// <param name="searchQuery">The search term that all returned songs should contain.</param> /// <param name="overrideStripSymbols">Strip symbols from the query and search space fields. Set as null to use config settings.</param> /// <param name="overrideSplitWords">Split a query into separate words. Set as null to use config settings.</param> /// <param name="overrideSongFields">Fields of a beatmap to search for the query. Set as null to use config settings.</param> /// <returns>A list of songs that contain the provided search term.</returns> public List <IPreviewBeatmapLevel> StartInstantSearch(IEnumerable <IPreviewBeatmapLevel> searchSpace, string searchQuery, bool?overrideStripSymbols = null, bool?overrideSplitWords = null, SearchableSongFields?overrideSongFields = null) { if (searchSpace == null || string.IsNullOrEmpty(searchQuery)) { return(searchSpace?.ToList() ?? new List <IPreviewBeatmapLevel>(0)); } string[] queryWords; bool stripSymbols = overrideStripSymbols.HasValue ? overrideStripSymbols.Value : PluginConfig.StripSymbols; bool splitWords = overrideSplitWords.HasValue ? overrideSplitWords.Value : PluginConfig.SplitQueryByWords; SearchableSongFields songFields = overrideSongFields.HasValue ? overrideSongFields.Value : PluginConfig.SongFieldsToSearch; if (stripSymbols) { searchQuery = RemoveSymbolsRegex.Replace(searchQuery.ToLower(), string.Empty); } else { searchQuery = searchQuery.ToLower(); } if (splitWords) { queryWords = searchQuery.Split(SplitCharacters, StringSplitOptions.RemoveEmptyEntries); } else { queryWords = new string[] { searchQuery } }; return(searchSpace .AsParallel() .Where(level => CheckSong(level, stripSymbols, splitWords, songFields, queryWords)) .AsSequential() .ToList()); }
protected override void DidActivate(bool firstActivation, ActivationType activationType) { _maxResultsShownStagingValue = PluginConfig.MaxSearchResults; _stripSymbolsStagingValue = PluginConfig.StripSymbols; _splitQueryStagingValue = PluginConfig.SplitQueryByWords; _songFieldsStagingValue = PluginConfig.SongFieldsToSearch; _compactModeStagingValue = PluginConfig.CompactSearchMode; if (firstActivation) { var headerRectTransform = Instantiate(Resources.FindObjectsOfTypeAll <RectTransform>() .First(x => x.name == "HeaderPanel" && x.parent.name == "PlayerSettingsViewController"), this.rectTransform); headerRectTransform.gameObject.SetActive(true); TextMeshProUGUI titleText = headerRectTransform.GetComponentInChildren <TextMeshProUGUI>(); titleText.text = "Options"; _submenu = new SubMenu(this.transform); // have to use the ListViewController because IntViewController doesn't seem to work outside of the settings menu float[] maxResultsShownValues = Enumerable.Range(PluginConfig.MaxSearchResultsMinValue, PluginConfig.MaxSearchResultsMaxValue + 1) .Select((x) => (float)x).ToArray(); _maxResultsShownSetting = _submenu.AddList("Maximum # of Results Shown", maxResultsShownValues, "The maximum number of songs found before a search result is shown.\n" + "<color=#11FF11>A lower number is less distracting and only displays results when most irrelevant songs are removed.</color>\n" + "<color=#FFFF11>You can force a search result to be shown using the button on the center screen.</color>"); _maxResultsShownSetting.GetTextForValue += x => ((int)x).ToString(); _maxResultsShownSetting.GetValue += () => _maxResultsShownStagingValue; _maxResultsShownSetting.SetValue += delegate(float value) { _maxResultsShownStagingValue = (int)value; _resetButton.interactable = true; _applyButton.interactable = true; }; SetSettingElementPosition(_maxResultsShownSetting.transform as RectTransform); _maxResultsShownSetting.Init(); _maxResultsShownSetting.applyImmediately = true; // applyImmediately is after Init(), otherwise it calls SetValue once _splitQuerySetting = _submenu.AddBool("Search Each Word Individually", "Split up the search query into words and searches the song details for those words individually. " + "A song will only appear in the results if it contains all the words typed.\n" + "<color=#11FF11>'ON' - For when you know some words or names in the song details, but not the specific order.</color>\n" + "<color=#11FF11>'OFF' - Useful if you want to search for a particular phrase.</color>"); _splitQuerySetting.GetValue += () => _splitQueryStagingValue; _splitQuerySetting.SetValue += delegate(bool value) { _splitQueryStagingValue = value; _resetButton.interactable = true; _applyButton.interactable = true; }; SetSettingElementPosition(_splitQuerySetting.transform as RectTransform); _splitQuerySetting.Init(); _splitQuerySetting.applyImmediately = true; float[] songFieldsValues = new float[3] { (float)SearchableSongFields.All, (float)SearchableSongFields.TitleAndAuthor, (float)SearchableSongFields.TitleOnly }; _songFieldsSetting = _submenu.AddList("Song Fields to Search", songFieldsValues, "A query will only search in these particular details of a song.\n" + "<color=#11FF11>Can get relevant results quicker if you never search for song artist or beatmap creator.</color>\n" + "Options - 'All', 'Title and Author', 'Title Only'"); _songFieldsSetting.GetTextForValue += delegate(float value) { switch (value) { case (float)SearchableSongFields.All: return("All"); case (float)SearchableSongFields.TitleAndAuthor: return("<size=80%>Title and Author</size>"); //case (float)SearchableSongFields.TitleOnly: default: return("Title Only"); } }; _songFieldsSetting.GetValue += () => (float)_songFieldsStagingValue; _songFieldsSetting.SetValue += delegate(float value) { _songFieldsStagingValue = (SearchableSongFields)value; _resetButton.interactable = true; _applyButton.interactable = true; }; SetSettingElementPosition(_songFieldsSetting.transform as RectTransform); _songFieldsSetting.Init(); _songFieldsSetting.applyImmediately = true; _stripSymbolsSetting = _submenu.AddBool("Strip Symbols from Song Details", "Removes symbols from song title, subtitle, artist, etc. fields when performing search.\n" + "<color=#11FF11>Can be useful when searching for song remixes and titles with apostrophes, quotations, or hyphens.</color>"); _stripSymbolsSetting.GetValue += () => _stripSymbolsStagingValue; _stripSymbolsSetting.SetValue += delegate(bool value) { _stripSymbolsStagingValue = value; _resetButton.interactable = true; _applyButton.interactable = true; }; SetSettingElementPosition(_stripSymbolsSetting.transform as RectTransform); _stripSymbolsSetting.Init(); _stripSymbolsSetting.applyImmediately = true; _compactModeSetting = _submenu.AddBool("Use Compact Mode", "Removes the keyboard on the right screen, replacing it with a smaller keyboard on the center screen."); _compactModeSetting.GetValue += () => _compactModeStagingValue; _compactModeSetting.SetValue += delegate(bool value) { _compactModeStagingValue = value; _resetButton.interactable = true; _applyButton.interactable = true; }; SetSettingElementPosition(_compactModeSetting.transform as RectTransform); _compactModeSetting.Init(); _compactModeSetting.applyImmediately = true; _defaultButton = BeatSaberUI.CreateUIButton(this.rectTransform, "CancelButton", new Vector2(-37f, -32f), new Vector2(36f, 10f), DefaultButtonPressed, "Use Defaults"); _defaultButton.ToggleWordWrapping(false); _resetButton = BeatSaberUI.CreateUIButton(this.rectTransform, "CancelButton", new Vector2(16f, -32f), new Vector2(24f, 9f), ResetButtonPressed, "Reset"); _resetButton.ToggleWordWrapping(false); _applyButton = BeatSaberUI.CreateUIButton(this.rectTransform, "CancelButton", new Vector2(43f, -32f), new Vector2(24f, 9f), ApplyButtonPressed, "Apply"); _applyButton.ToggleWordWrapping(false); } else { // force show current value in config RefreshUI(); } _resetButton.interactable = false; _applyButton.interactable = false; }
/// <summary> /// Check whether a song contains the all the words in the query. /// </summary> /// <param name="level">Song to check.</param> /// <param name="stripSymbols">Whether to strip the symbols of each word before checking.</param> /// <param name="combineSingleLetterSequences">Whether to combine single letter 'word' sequences.</param> /// <param name="songFields">Song fields to check.</param> /// <param name="queryWords">List of words (or a phrase) to query for.</param> /// <returns>True if the song contains all the words in the query, otherwise false.</returns> private bool CheckSong(IPreviewBeatmapLevel level, bool stripSymbols, bool combineSingleLetterSequences, SearchableSongFields songFields, IEnumerable <string> queryWords) { string songName; if (combineSingleLetterSequences) { // combine contiguous single letter 'word' sequences in the title each into one word // should only done when Split Words option is enabled StringBuilder songNameSB = new StringBuilder(level.songName.Length); StringBuilder constructedWordSB = new StringBuilder(level.songName.Length); foreach (string word in level.songName.Split(SplitCharacters, StringSplitOptions.RemoveEmptyEntries)) { if (word.Length > 1 || !char.IsLetterOrDigit(word[0])) { // multi-letter word or special character if (constructedWordSB.Length > 0) { if (songNameSB.Length > 0) { songNameSB.Append(' '); } songNameSB.Append(constructedWordSB.ToString()); constructedWordSB.Clear(); } if (songNameSB.Length > 0) { songNameSB.Append(' '); } songNameSB.Append(word); } else { // single letter 'word' constructedWordSB.Append(word); } } // add last constructed word if it exists if (constructedWordSB.Length > 0) { if (songNameSB.Length > 0) { songNameSB.Append(' '); } songNameSB.Append(constructedWordSB.ToString()); } songName = songNameSB.ToString(); } else { songName = level.songName; } string fields; if (songFields == SearchableSongFields.All) { fields = $"{songName} {level.songSubName} {level.levelAuthorName} {level.songAuthorName}".ToLower(); } else if (songFields == SearchableSongFields.TitleOnly) { fields = $"{songName} {level.songSubName}".ToLower(); } //else if (songFields == SearchableSongFields.TitleAndAuthor) else { fields = $"{songName} {level.songSubName} {level.songAuthorName}".ToLower(); } if (stripSymbols) { fields = RemoveSymbolsRegex.Replace(fields, string.Empty); } foreach (var word in queryWords) { if (!fields.Contains(word)) { return(false); } } return(true); }
private IEnumerator _SearchSongs(string searchQuery) { int index = 0; string[] queryWords; bool stripSymbols = PluginConfig.StripSymbols; bool splitWords = PluginConfig.SplitQueryByWords; SearchableSongFields songFields = PluginConfig.SongFieldsToSearch; if (stripSymbols) { searchQuery = RemoveSymbolsRegex.Replace(searchQuery.ToLower(), string.Empty); } else { searchQuery = searchQuery.ToLower(); } if (splitWords) { queryWords = searchQuery.Split(SplitCharacters, StringSplitOptions.RemoveEmptyEntries); } else { queryWords = new string[] { searchQuery } }; while (index < _searchSpace.Count) { // limit the number of songs to evaluate so users with large song libraries don't pause on every key press for (int count = 0; count < MaxSearchInOneFrame && index < _searchSpace.Count; ++count) { IPreviewBeatmapLevel level = _searchSpace[index]; string songName; if (splitWords) { // combine contiguous single letter 'word' sequences in the title each into one word // only done when Split Words option is enabled StringBuilder songNameSB = new StringBuilder(level.songName.Length); StringBuilder constructedWordSB = new StringBuilder(level.songName.Length); foreach (string word in level.songName.Split(SplitCharacters, StringSplitOptions.RemoveEmptyEntries)) { if (word.Length > 1 || !char.IsLetterOrDigit(word[0])) { // multi-letter word or special character if (constructedWordSB.Length > 0) { if (songNameSB.Length > 0) { songNameSB.Append(' '); } songNameSB.Append(constructedWordSB.ToString()); constructedWordSB.Clear(); } if (songNameSB.Length > 0) { songNameSB.Append(' '); } songNameSB.Append(word); } else { // single letter 'word' constructedWordSB.Append(word); } } // add last constructed word if it exists if (constructedWordSB.Length > 0) { if (songNameSB.Length > 0) { songNameSB.Append(' '); } songNameSB.Append(constructedWordSB.ToString()); } songName = songNameSB.ToString(); } else { songName = level.songName; } string fields; if (songFields == SearchableSongFields.All) { fields = $"{songName} {level.songSubName} {level.levelAuthorName} {level.songAuthorName}".ToLower(); } else if (songFields == SearchableSongFields.TitleOnly) { fields = $"{songName} {level.songSubName}".ToLower(); } //else if (songFields == SearchableSongFields.TitleAndAuthor) else { fields = $"{songName} {level.songSubName} {level.songAuthorName}".ToLower(); } if (stripSymbols) { fields = RemoveSymbolsRegex.Replace(fields, string.Empty); } bool remove = false; foreach (var word in queryWords) { if (!fields.Contains(word)) { remove = true; break; } } if (remove) { _searchSpace.RemoveAt(index); } else { ++index; } } yield return(null); } _searchCompletedAction?.Invoke(_searchSpace.ToArray()); _searchCoroutine = null; } }
private IEnumerator _SearchSongs(string searchQuery) { int index = 0; string[] queryWords; bool stripSymbols = PluginConfig.StripSymbols; bool splitWords = PluginConfig.SplitQueryByWords; SearchableSongFields songFields = PluginConfig.SongFieldsToSearch; if (stripSymbols) { searchQuery = RemoveSymbolsRegex.Replace(searchQuery.ToLower(), string.Empty); } else { searchQuery = searchQuery.ToLower(); } if (splitWords) { queryWords = searchQuery.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); } else { queryWords = new string[] { searchQuery } }; while (index < _searchSpace.Count) { // limit the number of songs to evaluate so users with large song libraries don't pause on every key press for (int count = 0; count < MaxSearchInOneFrame && index < _searchSpace.Count; ++count) { IPreviewBeatmapLevel level = _searchSpace[index]; string fields; if (songFields == SearchableSongFields.All) { fields = $"{level.songName} {level.songSubName} {level.levelAuthorName} {level.songAuthorName}".ToLower(); } else if (songFields == SearchableSongFields.TitleOnly) { fields = $"{level.songName} {level.songSubName}".ToLower(); } //else if (songFields == SearchableSongFields.TitleAndAuthor) else { fields = $"{level.songName} {level.songSubName} {level.songAuthorName}".ToLower(); } if (stripSymbols) { fields = RemoveSymbolsRegex.Replace(fields, string.Empty); } bool remove = false; foreach (var word in queryWords) { if (!fields.Contains(word)) { remove = true; break; } } if (remove) { _searchSpace.RemoveAt(index); } else { ++index; } } yield return(null); } _searchCompletedAction?.Invoke(_searchSpace.ToArray()); _searchCoroutine = null; } }