using UnityEngine;
using System.Linq;
using System;
using System.Collections.Generic;
using UnityEngine.UI;
using HMUI;
using VRUI;
using SongBrowser.DataAccess;
using TMPro;
using Logger = SongBrowser.Logging.Logger;
using SongBrowser.DataAccess.BeatSaverApi;
using System.Collections;
using SongCore.Utilities;
using SongBrowser.Internals;
using CustomUI.BeatSaber;
using SongDataCore.ScoreSaber;
namespace SongBrowser.UI
{
public enum UIState
{
Disabled,
Main,
SortBy,
FilterBy
}
///
/// Hijack the flow coordinator. Have access to all StandardLevel easily.
///
public class SongBrowserUI : MonoBehaviour
{
// Logging
public const String Name = "SongBrowserUI";
private const float SEGMENT_PERCENT = 0.1f;
private const int LIST_ITEMS_VISIBLE_AT_ONCE = 6;
// BeatSaber Internal UI structures
DataAccess.BeatSaberUIController _beatUi;
// New UI Elements
private List _sortButtonGroup;
private List _filterButtonGroup;
private Button _sortByButton;
private Button _sortByDisplay;
private Button _filterByButton;
private Button _filterByDisplay;
private Button _randomButton;
private Button _clearSortFilterButton;
private Button _addFavoriteButton;
private SimpleDialogPromptViewController _deleteDialog;
private Button _deleteButton;
private Button _pageUpFastButton;
private Button _pageDownFastButton;
private SearchKeyboardViewController _searchViewController;
private PlaylistFlowCoordinator _playListFlowCoordinator;
private RectTransform _ppStatButton;
private RectTransform _starStatButton;
private RectTransform _njsStatButton;
private Sprite _currentAddFavoriteButtonSprite;
private IBeatmapLevelPack _lastLevelPack;
// Model
private SongBrowserModel _model;
public SongBrowserModel Model
{
set
{
_model = value;
}
}
// UI Created
private bool _uiCreated = false;
private UIState _currentUiState = UIState.Disabled;
///
/// Builds the UI for this plugin.
///
public void CreateUI(MainMenuViewController.MenuButton mode)
{
Logger.Trace("CreateUI()");
// Determine the flow controller to use
FlowCoordinator flowCoordinator = null;
if (mode == MainMenuViewController.MenuButton.SoloFreePlay)
{
Logger.Debug("Entering SOLO mode...");
flowCoordinator = Resources.FindObjectsOfTypeAll().First();
}
else if (mode == MainMenuViewController.MenuButton.Party)
{
Logger.Debug("Entering PARTY mode...");
flowCoordinator = Resources.FindObjectsOfTypeAll().First();
}
else
{
Logger.Debug("Entering SOLO CAMPAIGN mode...");
flowCoordinator = Resources.FindObjectsOfTypeAll().First();
return;
}
_beatUi = new DataAccess.BeatSaberUIController(flowCoordinator);
// returning to the menu and switching modes could trigger this.
if (_uiCreated)
{
return;
}
try
{
if (_playListFlowCoordinator == null)
{
_playListFlowCoordinator = UIBuilder.CreateFlowCoordinator("PlaylistFlowCoordinator");
_playListFlowCoordinator.didFinishEvent += HandleDidSelectPlaylist;
}
// delete dialog
this._deleteDialog = UnityEngine.Object.Instantiate(_beatUi.SimpleDialogPromptViewControllerPrefab);
this._deleteDialog.name = "DeleteDialogPromptViewController";
this._deleteDialog.gameObject.SetActive(false);
// create song browser main ui
CreateOuterUi();
CreateSortButtons();
CreateFilterButtons();
CreateAddFavoritesButton();
CreateDeleteButton();
CreateFastPageButtons();
this.InstallHandlers();
this.ModifySongStatsPanel();
this.ResizeSongUI();
_uiCreated = true;
RefreshSortButtonUI();
Logger.Debug("Done Creating UI...");
}
catch (Exception e)
{
Logger.Exception("Exception during CreateUI: ", e);
}
}
///
/// Create the outer ui.
///
private void CreateOuterUi()
{
Logger.Debug("Creating outer UI...");
float clearButtonX = -32.5f;
float clearButtonY = 34.5f;
float buttonY = 37f;
float buttonHeight = 5.0f;
float sortByButtonX = -22.5f + buttonHeight;
float outerButtonFontSize = 3.0f;
float displayButtonFontSize = 2.5f;
float outerButtonWidth = 24.0f;
float randomButtonWidth = 8.0f;
// clear button
_clearSortFilterButton = CreateClearButton(clearButtonX, clearButtonY, buttonHeight, () =>
{
if (_currentUiState == UIState.FilterBy || _currentUiState == UIState.SortBy)
{
RefreshOuterUIState(UIState.Main);
}
else
{
OnClearButtonClickEvent();
}
});
// create SortBy button and its display
float curX = sortByButtonX;
_sortByButton = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
{
RefreshOuterUIState(UIState.SortBy);
}, "Sort By");
_sortByButton.SetButtonTextSize(outerButtonFontSize);
_sortByButton.ToggleWordWrapping(false);
curX += outerButtonWidth;
_sortByDisplay = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
{
OnSortButtonClickEvent(_model.Settings.sortMode);
}, "");
_sortByDisplay.SetButtonTextSize(displayButtonFontSize);
_sortByDisplay.ToggleWordWrapping(false);
curX += outerButtonWidth;
// create FilterBy button and its display
_filterByButton = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
{
RefreshOuterUIState(UIState.FilterBy);
}, "Filter By");
_filterByButton.SetButtonTextSize(outerButtonFontSize);
_filterByButton.ToggleWordWrapping(false);
curX += outerButtonWidth;
_filterByDisplay = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
{
_model.Settings.filterMode = SongFilterMode.None;
CancelFilter();
RefreshSongUI();
}, "");
_filterByDisplay.SetButtonTextSize(displayButtonFontSize);
_filterByDisplay.ToggleWordWrapping(false);
// random button
_randomButton = _beatUi.LevelPackLevelsViewController.CreateUIButton("HowToPlayButton", new Vector2(curX + (outerButtonWidth / 2.0f) + (randomButtonWidth / 2.0f), clearButtonY), new Vector2(randomButtonWidth, buttonHeight), () =>
{
OnSortButtonClickEvent(SongSortMode.Random);
}, "",
Base64Sprites.RandomIcon);
_randomButton.GetComponentsInChildren().First(btn => btn.name == "Content").padding = new RectOffset(0, 0, 0, 0);
var textRect = _randomButton.GetComponentsInChildren(true).FirstOrDefault(c => c.name == "Text");
if (textRect != null)
{
UnityEngine.Object.Destroy(textRect.gameObject);
}
UIBuilder.SetButtonBorderActive(_randomButton, false);
}
///
/// Create the back button
///
///
private Button CreateClearButton(float x, float y, float h, UnityEngine.Events.UnityAction callback)
{
Button b = _beatUi.LevelPackLevelsViewController.CreateUIButton("HowToPlayButton", new Vector2(x, y), new Vector2(h, h), callback, "", Base64Sprites.XIcon);
b.GetComponentsInChildren().First(btn => btn.name == "Content").padding = new RectOffset(1, 1, 0, 0);
RectTransform textRect = b.GetComponentsInChildren(true).FirstOrDefault(c => c.name == "Text");
if (textRect != null)
{
UnityEngine.Object.Destroy(textRect.gameObject);
}
UIBuilder.SetButtonBorderActive(b, false);
return b;
}
///
/// Create the sort button ribbon
///
private void CreateSortButtons()
{
Logger.Debug("Create sort buttons...");
float sortButtonFontSize = 2.15f;
float sortButtonX = -23.0f;
float sortButtonWidth = 12.0f;
float buttonSpacing = 0.25f;
float buttonY = 37f;
float buttonHeight = 5.0f;
string[] sortButtonNames = new string[]
{
"Title", "Author", "Newest", "YourPlays", "PP", "Stars", "UpVotes", "PlayCount", "Rating", "Heat"
};
SongSortMode[] sortModes = new SongSortMode[]
{
SongSortMode.Default, SongSortMode.Author, SongSortMode.Newest, SongSortMode.YourPlayCount, SongSortMode.PP, SongSortMode.Stars, SongSortMode.UpVotes, SongSortMode.PlayCount, SongSortMode.Rating, SongSortMode.Heat
};
_sortButtonGroup = new List();
for (int i = 0; i < sortButtonNames.Length; i++)
{
float curButtonX = sortButtonX + (sortButtonWidth * i) + (buttonSpacing * i);
SongSortButton sortButton = new SongSortButton();
sortButton.SortMode = sortModes[i];
sortButton.Button = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton",
new Vector2(curButtonX, buttonY), new Vector2(sortButtonWidth, buttonHeight),
() =>
{
OnSortButtonClickEvent(sortButton.SortMode);
RefreshOuterUIState(UIState.Main);
},
sortButtonNames[i]);
sortButton.Button.SetButtonTextSize(sortButtonFontSize);
sortButton.Button.GetComponentsInChildren().First(btn => btn.name == "Content").padding = new RectOffset(4, 4, 2, 2);
sortButton.Button.ToggleWordWrapping(false);
sortButton.Button.name = "Sort" + sortModes[i].ToString() + "Button";
_sortButtonGroup.Add(sortButton);
}
}
///
/// Create the filter by buttons
///
private void CreateFilterButtons()
{
Logger.Debug("Creating filter buttons...");
float filterButtonFontSize = 2.25f;
float filterButtonX = -23.0f;
float filterButtonWidth = 12.25f;
float buttonSpacing = 0.5f;
float buttonY = 37f;
float buttonHeight = 5.0f;
string[] filterButtonNames = new string[]
{
"Favorites", "Playlist", "Search", "Ranked", "Unranked"
};
SongFilterMode[] filterModes = new SongFilterMode[]
{
SongFilterMode.Favorites, SongFilterMode.Playlist, SongFilterMode.Search, SongFilterMode.Ranked, SongFilterMode.Unranked
};
_filterButtonGroup = new List();
for (int i = 0; i < filterButtonNames.Length; i++)
{
float curButtonX = filterButtonX + (filterButtonWidth * i) + (buttonSpacing * i);
SongFilterButton filterButton = new SongFilterButton();
filterButton.FilterMode = filterModes[i];
filterButton.Button = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton",
new Vector2(curButtonX, buttonY), new Vector2(filterButtonWidth, buttonHeight),
() =>
{
OnFilterButtonClickEvent(filterButton.FilterMode);
RefreshOuterUIState(UIState.Main);
},
filterButtonNames[i]);
filterButton.Button.SetButtonTextSize(filterButtonFontSize);
filterButton.Button.GetComponentsInChildren().First(btn => btn.name == "Content").padding = new RectOffset(4, 4, 2, 2);
filterButton.Button.ToggleWordWrapping(false);
filterButton.Button.name = "Filter" + filterButtonNames[i] + "Button";
_filterButtonGroup.Add(filterButton);
}
}
///
/// Create the fast page up and down buttons
///
private void CreateFastPageButtons()
{
Logger.Debug("Creating fast scroll button...");
_pageUpFastButton = Instantiate(_beatUi.TableViewPageUpButton, _beatUi.LevelPackLevelsTableViewRectTransform, false);
(_pageUpFastButton.transform as RectTransform).anchorMin = new Vector2(0.5f, 1f);
(_pageUpFastButton.transform as RectTransform).anchorMax = new Vector2(0.5f, 1f);
(_pageUpFastButton.transform as RectTransform).anchoredPosition = new Vector2(-26f, 0.25f);
(_pageUpFastButton.transform as RectTransform).sizeDelta = new Vector2(8f, 6f);
_pageUpFastButton.GetComponentsInChildren().First(x => x.name == "BG").sizeDelta = new Vector2(8f, 6f);
_pageUpFastButton.GetComponentsInChildren().First(x => x.name == "Arrow").sprite = Base64Sprites.DoubleArrow;
_pageUpFastButton.onClick.AddListener(delegate ()
{
this.JumpSongList(-1, SEGMENT_PERCENT);
});
_pageDownFastButton = Instantiate(_beatUi.TableViewPageDownButton, _beatUi.LevelPackLevelsTableViewRectTransform, false);
(_pageDownFastButton.transform as RectTransform).anchorMin = new Vector2(0.5f, 0f);
(_pageDownFastButton.transform as RectTransform).anchorMax = new Vector2(0.5f, 0f);
(_pageDownFastButton.transform as RectTransform).anchoredPosition = new Vector2(-26f, -1f);
(_pageDownFastButton.transform as RectTransform).sizeDelta = new Vector2(8f, 6f);
_pageDownFastButton.GetComponentsInChildren().First(x => x.name == "BG").sizeDelta = new Vector2(8f, 6f);
_pageDownFastButton.GetComponentsInChildren().First(x => x.name == "Arrow").sprite = Base64Sprites.DoubleArrow;
_pageDownFastButton.onClick.AddListener(delegate ()
{
this.JumpSongList(1, SEGMENT_PERCENT);
});
}
///
/// Create the +/- favorite button in the play button container.
///
private void CreateAddFavoritesButton()
{
// Create add favorite button
Logger.Debug("Creating Add to favorites button...");
_addFavoriteButton = UIBuilder.CreateIconButton(_beatUi.PlayButtons, _beatUi.PracticeButton, Base64Sprites.AddToFavoritesIcon);
_addFavoriteButton.onClick.AddListener(delegate () {
ToggleSongInPlaylist();
});
}
///
/// Create the delete button in the play button container
///
private void CreateDeleteButton()
{
// Create delete button
Logger.Debug("Creating delete button...");
_deleteButton = UIBuilder.CreateIconButton(_beatUi.PlayButtons, _beatUi.PracticeButton, Base64Sprites.DeleteIcon);
_deleteButton.onClick.AddListener(delegate () {
HandleDeleteSelectedLevel();
});
}
///
/// Resize the stats panel to fit more stats.
///
private void ModifySongStatsPanel()
{
// modify details view
Logger.Debug("Resizing Stats Panel...");
var statsPanel = _beatUi.StandardLevelDetailView.GetPrivateField("_levelParamsPanel");
var statTransforms = statsPanel.GetComponentsInChildren();
var valueTexts = statsPanel.GetComponentsInChildren().Where(x => x.name == "ValueText").ToList();
RectTransform panelRect = (statsPanel.transform as RectTransform);
panelRect.sizeDelta = new Vector2(panelRect.sizeDelta.x * 1.2f, panelRect.sizeDelta.y * 1.2f);
for (int i = 0; i < statTransforms.Length; i++)
{
var r = statTransforms[i];
if (r.name == "Separator")
{
continue;
}
r.sizeDelta = new Vector2(r.sizeDelta.x * 0.75f, r.sizeDelta.y * 0.75f);
}
for (int i = 0; i < valueTexts.Count; i++)
{
var text = valueTexts[i];
text.fontSize = 3.25f;
}
// inject our components
_ppStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
UIBuilder.SetStatButtonIcon(_ppStatButton, Base64Sprites.GraphIcon);
_starStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
UIBuilder.SetStatButtonIcon(_starStatButton, Base64Sprites.StarFullIcon);
_njsStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
UIBuilder.SetStatButtonIcon(_njsStatButton, Base64Sprites.SpeedIcon);
// shrink title
var titleText = _beatUi.LevelDetailViewController.GetComponentsInChildren(true).First(x => x.name == "SongNameText");
titleText.fontSize = 5.0f;
}
///
/// Resize some of the song table elements.
///
public void ResizeSongUI()
{
// Reposition the table view a bit
_beatUi.LevelPackLevelsTableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
// Move the page up/down buttons a bit
TableView tableView = ReflectionUtil.GetPrivateField(_beatUi.LevelPackLevelsTableView, "_tableView");
RectTransform pageUpButton = _beatUi.TableViewPageUpButton.transform as RectTransform;
RectTransform pageDownButton = _beatUi.TableViewPageDownButton.transform as RectTransform;
pageUpButton.anchoredPosition = new Vector2(pageUpButton.anchoredPosition.x, pageUpButton.anchoredPosition.y - 1f);
pageDownButton.anchoredPosition = new Vector2(pageDownButton.anchoredPosition.x, pageDownButton.anchoredPosition.y + 1f);
// shrink play button container
RectTransform playContainerRect = _beatUi.StandardLevelDetailView.GetComponentsInChildren().First(x => x.name == "PlayContainer");
RectTransform playButtonsRect = playContainerRect.GetComponentsInChildren().First(x => x.name == "PlayButtons");
playButtonsRect.localScale = new Vector3(0.825f, 0.825f, 0.825f);
}
///
/// Add our handlers into BeatSaber.
///
private void InstallHandlers()
{
// level pack, level, difficulty handlers, characteristics
TableView tableView = ReflectionUtil.GetPrivateField(_beatUi.LevelPackLevelsTableView, "_tableView");
_beatUi.LevelPackLevelsViewController.didSelectLevelEvent -= OnDidSelectLevelEvent;
_beatUi.LevelPackLevelsViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
_beatUi.LevelDetailViewController.didPresentContentEvent -= OnDidPresentContentEvent;
_beatUi.LevelDetailViewController.didPresentContentEvent += OnDidPresentContentEvent;
_beatUi.LevelDetailViewController.didChangeDifficultyBeatmapEvent -= OnDidChangeDifficultyEvent;
_beatUi.LevelDetailViewController.didChangeDifficultyBeatmapEvent += OnDidChangeDifficultyEvent;
//_beatUi.LevelPacksTableView.didSelectPackEvent -= _levelPacksTableView_didSelectPackEvent;
//_beatUi.LevelPacksTableView.didSelectPackEvent += _levelPacksTableView_didSelectPackEvent;
_beatUi.LevelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
_beatUi.LevelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
_beatUi.BeatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent -= OnDidSelectBeatmapCharacteristic;
_beatUi.BeatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent += OnDidSelectBeatmapCharacteristic;
// make sure the quick scroll buttons don't desync with regular scrolling
_beatUi.TableViewPageDownButton.onClick.AddListener(delegate ()
{
StartCoroutine(RefreshQuickScrollButtonsAsync());
});
_beatUi.TableViewPageUpButton.onClick.AddListener(delegate ()
{
StartCoroutine(RefreshQuickScrollButtonsAsync());
});
// finished level
ResultsViewController resultsViewController = _beatUi.LevelSelectionFlowCoordinator.GetPrivateField("_resultsViewController");
resultsViewController.continueButtonPressedEvent += ResultsViewController_continueButtonPressedEvent;
}
///
/// Helper to reduce code duplication...
///
public void RefreshSongUI(bool scrollToLevel=true)
{
if (!_uiCreated)
{
return;
}
RefreshSongList(scrollToLevel);
RefreshSortButtonUI();
if (!scrollToLevel)
{
_beatUi.ScrollToLevelByRow(0);
}
RefreshQuickScrollButtons();
RefreshCurrentSelectionDisplay();
}
///
/// External helper.
///
public void ProcessSongList()
{
if (!_uiCreated)
{
return;
}
this._model.ProcessSongList(_beatUi.LevelPackLevelsViewController);
}
///
/// Helper for common filter cancellation logic.
///
public void CancelFilter()
{
Logger.Debug($"Cancelling filter, levelPack {_lastLevelPack}");
_model.Settings.filterMode = SongFilterMode.None;
_beatUi.LevelPackLevelsViewController.SetData(_lastLevelPack);
}
///
/// Handle updating the level pack selection after returning from a song.
///
///
private void ResultsViewController_continueButtonPressedEvent(ResultsViewController obj)
{
StartCoroutine(this.UpdateLevelPackSelectionEndOfFrame());
}
///
/// TODO - evaluate this sillyness...
///
///
public IEnumerator UpdateLevelPackSelectionEndOfFrame()
{
yield return new WaitForEndOfFrame();
try
{
UpdateLevelPackSelection();
_beatUi.SelectAndScrollToLevel(_beatUi.LevelPackLevelsTableView, _model.LastSelectedLevelId);
RefreshQuickScrollButtons();
}
catch (Exception e)
{
Logger.Exception("Exception:", e);
}
}
///
/// Handler for level pack selection.
///
///
///
private void _levelPacksTableView_didSelectPackEvent(LevelPacksTableView arg1, IBeatmapLevelPack arg2)
{
Logger.Trace("_levelPacksTableView_didSelectPackEvent(arg2={0})", arg2);
try
{
RefreshSortButtonUI();
RefreshQuickScrollButtons();
}
catch (Exception e)
{
Logger.Exception("Exception handling didSelectPackEvent...", e);
}
}
///
/// Handler for level pack selection, controller.
/// Sets the current level pack into the model and updates.
///
///
///
private void _levelPackViewController_didSelectPackEvent(LevelPacksViewController arg1, IBeatmapLevelPack levelPack)
{
Logger.Trace("_levelPackViewController_didSelectPackEvent(levelPack={0})", levelPack);
try
{
// store the real level pack
if (levelPack.packID != SongBrowserModel.FilteredSongsPackId)
{
_lastLevelPack = levelPack;
}
// reset level selection
_model.LastSelectedLevelId = null;
// save level packs
this._model.Settings.currentLevelPackId = levelPack.packID;
this._model.Settings.Save();
ProcessSongList();
// trickery to handle Downloader playlist level packs
// We need to avoid scrolling to a level and then select the header
bool scrollToLevel = true;
if (levelPack.packID.Contains("Playlist_"))
{
scrollToLevel = false;
}
RefreshSongUI(scrollToLevel);
}
catch (Exception e)
{
Logger.Exception("Exception handling didSelectPackEvent...", e);
}
}
///
/// Remove all filters, update song list, save.
///
private void OnClearButtonClickEvent()
{
Logger.Debug("Clearing all sorts and filters.");
_model.Settings.sortMode = SongSortMode.Original;
_model.Settings.invertSortResults = false;
_model.Settings.filterMode = SongFilterMode.None;
_model.Settings.Save();
CancelFilter();
ProcessSongList();
RefreshSongUI();
}
///
/// Sort button clicked.
///
private void OnSortButtonClickEvent(SongSortMode sortMode)
{
Logger.Debug("Sort button - {0} - pressed.", sortMode.ToString());
if ((sortMode.NeedsScoreSaberData() && !SongDataCore.Plugin.ScoreSaber.IsDataAvailable()) ||
(sortMode.NeedsBeatSaverData() && !SongDataCore.Plugin.BeatSaver.IsDataAvailable()))
{
Logger.Info("Data for sort type is not available.");
return;
}
// Clear current selected level id so our song list jumps to the start
_model.LastSelectedLevelId = null;
if (_model.Settings.sortMode == sortMode)
{
_model.ToggleInverting();
}
_model.Settings.sortMode = sortMode;
// update the seed
if (_model.Settings.sortMode == SongSortMode.Random)
{
_model.Settings.randomSongSeed = Guid.NewGuid().GetHashCode();
}
_model.Settings.Save();
ProcessSongList();
RefreshSongUI();
}
///
/// Handle filter button logic. Some filters have sub menus that need special logic.
///
///
private void OnFilterButtonClickEvent(SongFilterMode mode)
{
Logger.Debug($"FilterButton {mode} clicked.");
if (_lastLevelPack == null || _beatUi.LevelPackLevelsViewController.levelPack.packID != SongBrowserModel.FilteredSongsPackId)
{
_lastLevelPack = _beatUi.LevelPackLevelsViewController.levelPack;
}
if (mode == SongFilterMode.Favorites || mode == SongFilterMode.Playlist)
{
_beatUi.SelectLevelPack(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID);
}
else
{
_beatUi.LevelPackLevelsViewController.SetData(_lastLevelPack);
}
// If selecting the same filter, cancel
if (_model.Settings.filterMode == mode)
{
_model.Settings.filterMode = SongFilterMode.None;
}
else
{
_model.Settings.filterMode = mode;
}
switch (mode)
{
case SongFilterMode.Playlist:
OnPlaylistButtonClickEvent();
break;
case SongFilterMode.Search:
OnSearchButtonClickEvent();
break;
default:
_model.Settings.Save();
ProcessSongList();
RefreshSongUI();
break;
}
}
///
/// Display the keyboard.
///
///
private void OnSearchButtonClickEvent()
{
Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
this.ShowSearchKeyboard();
}
///
/// Display the playlist selector.
///
///
private void OnPlaylistButtonClickEvent()
{
Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
_model.LastSelectedLevelId = null;
_playListFlowCoordinator.parentFlowCoordinator = _beatUi.LevelSelectionFlowCoordinator;
_beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentFlowCoordinator", new object[] { _playListFlowCoordinator, null, false, false });
}
///
/// Adjust UI based on level selected.
/// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
///
private void OnDidSelectLevelEvent(LevelPackLevelsViewController view, IPreviewBeatmapLevel level)
{
try
{
Logger.Trace("OnDidSelectLevelEvent()");
if (level == null)
{
Logger.Debug("No level selected?");
return;
}
if (_model.Settings == null)
{
Logger.Debug("Settings not instantiated yet?");
return;
}
_model.LastSelectedLevelId = level.levelID;
HandleDidSelectLevelRow(level);
}
catch (Exception e)
{
Logger.Exception("Exception selecting song:", e);
}
}
///
/// Switching one-saber modes for example.
///
///
///
private void OnDidSelectBeatmapCharacteristic(BeatmapCharacteristicSegmentedControlController view, BeatmapCharacteristicSO bc)
{
Logger.Trace("OnDidSelectBeatmapCharacteristic({0}", bc.name);
_model.CurrentBeatmapCharacteristicSO = bc;
_model.UpdateLevelRecords();
this.RefreshSongList();
}
///
/// Handle difficulty level selection.
///
private void OnDidChangeDifficultyEvent(StandardLevelDetailViewController view, IDifficultyBeatmap beatmap)
{
Logger.Trace("OnDidChangeDifficultyEvent({0})", beatmap);
if (view.selectedDifficultyBeatmap == null)
{
return;
}
_deleteButton.interactable = (view.selectedDifficultyBeatmap.level.levelID.Length >= 32);
RefreshScoreSaberData(view.selectedDifficultyBeatmap.level, beatmap.difficulty);
RefreshNoteJumpSpeed(beatmap.difficulty);
}
///
/// BeatSaber finished loading content. This is when the difficulty is finally updated.
///
///
///
private void OnDidPresentContentEvent(StandardLevelDetailViewController view, StandardLevelDetailViewController.ContentType type)
{
Logger.Trace("OnDidPresentContentEvent()");
if (view.selectedDifficultyBeatmap == null)
{
return;
}
_deleteButton.interactable = (_beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID.Length >= 32);
RefreshScoreSaberData(view.selectedDifficultyBeatmap.level, view.selectedDifficultyBeatmap.difficulty);
RefreshNoteJumpSpeed(view.selectedDifficultyBeatmap.difficulty);
}
///
/// Refresh stats panel.
///
///
private void HandleDidSelectLevelRow(IPreviewBeatmapLevel level)
{
Logger.Trace("HandleDidSelectLevelRow({0})", level);
_deleteButton.interactable = (level.levelID.Length >= 32);
RefreshQuickScrollButtons();
RefreshAddFavoriteButton(level.levelID);
}
///
/// Call Downloader delete.
///
private void CallDownloaderDelete()
{
BeatSaverDownloader.UI.SongListTweaks.Instance.DeletePressed();
}
///
/// Pop up a delete dialog.
///
private void HandleDeleteSelectedLevel()
{
bool DownloaderInstalled = CustomHelpers.IsModInstalled("BeatSaverDownloader");
if (DownloaderInstalled)
{
CallDownloaderDelete();
return;
}
IBeatmapLevel level = _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level;
_deleteDialog.Init("Delete song", $"Do you really want to delete \"{ level.songName} {level.songSubName}\"?", "Delete", "Cancel",
(selectedButton) =>
{
_beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _deleteDialog, null, false });
if (selectedButton == 0)
{
try
{
// determine the index we are deleting so we can keep the cursor near the same spot after
// the header counts as an index, so if the index came from the level array we have to add 1.
var levelsTableView = _beatUi.LevelPackLevelsViewController.GetPrivateField("_levelPackLevelsTableView");
List levels = levelsTableView.GetPrivateField("_pack").beatmapLevelCollection.beatmapLevels.ToList();
int selectedIndex = levels.FindIndex(x => x.levelID == _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level.levelID);
if (selectedIndex > -1)
{
var song = new Song(SongCore.Loader.CustomLevels.First(x => x.Value.levelID == _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID).Value);
SongCore.Loader.Instance.DeleteSong(song.path);
this._model.RemoveSongFromLevelPack(_beatUi.GetCurrentSelectedLevelPack(), _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID);
this.UpdateLevelDataModel();
this.RefreshSongList();
int removedLevels = levels.RemoveAll(x => x.levelID == _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level.levelID);
Logger.Info("Removed " + removedLevels + " level(s) from song list!");
TableView listTableView = levelsTableView.GetPrivateField("_tableView");
listTableView.ScrollToCellWithIdx(selectedIndex, TableView.ScrollPositionType.Beginning, false);
levelsTableView.SetPrivateField("_selectedRow", selectedIndex);
listTableView.SelectCellWithIdx(selectedIndex, true);
}
}
catch (Exception e)
{
Logger.Error("Unable to delete song! Exception: " + e);
}
}
});
_beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { _deleteDialog, null, false });
}
///
/// Handle selection of a playlist. Show just the songs in the playlist.
///
///
private void HandleDidSelectPlaylist(Playlist p)
{
if (p != null)
{
Logger.Debug("Showing songs for playlist: {0}", p.playlistTitle);
_model.Settings.filterMode = SongFilterMode.Playlist;
_model.CurrentPlaylist = p;
_model.Settings.Save();
ProcessSongList();
RefreshSongUI();
}
else
{
Logger.Debug("No playlist selected");
}
}
///
/// Display the search keyboard
///
void ShowSearchKeyboard()
{
if (_searchViewController == null)
{
_searchViewController = UIBuilder.CreateViewController("SearchKeyboardViewController");
_searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
_searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
}
Logger.Debug("Presenting search keyboard");
_beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { _searchViewController, null, false });
}
///
/// Handle back button event from search keyboard.
///
private void SearchViewControllerbackButtonPressed()
{
_beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
this._model.Settings.filterMode = SongFilterMode.None;
this._model.Settings.Save();
RefreshSongUI();
}
///
/// Handle search.
///
///
private void SearchViewControllerSearchButtonPressed(string searchFor)
{
_beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
Logger.Debug("Searching for \"{0}\"...", searchFor);
_model.Settings.filterMode = SongFilterMode.Search;
_model.Settings.searchTerms.Insert(0, searchFor);
_model.Settings.Save();
_model.LastSelectedLevelId = null;
ProcessSongList();
RefreshSongUI();
}
///
/// Make big jumps in the song list.
///
///
private void JumpSongList(int numJumps, float segmentPercent)
{
var levels = _beatUi.GetCurrentLevelPackLevels();
if (levels == null)
{
return;
}
int totalSize = _beatUi.GetLevelPackLevelCount();
int segmentSize = (int)(totalSize * segmentPercent);
// Jump at least one scree size.
if (segmentSize < LIST_ITEMS_VISIBLE_AT_ONCE)
{
segmentSize = LIST_ITEMS_VISIBLE_AT_ONCE;
}
TableView tableView = ReflectionUtil.GetPrivateField(_beatUi.LevelPackLevelsTableView, "_tableView");
int currentRow = _beatUi.LevelPackLevelsTableView.GetPrivateField("_selectedRow");
int jumpDirection = Math.Sign(numJumps);
int newRow = currentRow + (jumpDirection * segmentSize);
if (newRow <= 0)
{
newRow = 0;
}
else if (newRow >= totalSize)
{
newRow = totalSize - 1;
}
Logger.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
_beatUi.SelectAndScrollToLevel(_beatUi.LevelPackLevelsTableView, levels[newRow].levelID);
RefreshQuickScrollButtons();
}
///
/// Add/Remove song from favorites depending on if it already exists.
///
private void ToggleSongInPlaylist()
{
IBeatmapLevel songInfo = _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level;
if (_model.CurrentEditingPlaylist != null)
{
if (_model.CurrentEditingPlaylistLevelIds.Contains(songInfo.levelID))
{
Logger.Info("Remove {0} from editing playlist", songInfo.songName);
_model.RemoveSongFromEditingPlaylist(songInfo);
if (_model.Settings.filterMode == SongFilterMode.Favorites)
{
ProcessSongList();
this.RefreshSongList();
}
}
else
{
Logger.Info("Add {0} to editing playlist", songInfo.songName);
_model.AddSongToEditingPlaylist(songInfo);
}
}
RefreshAddFavoriteButton(songInfo.levelID);
_model.Settings.Save();
}
///
/// Update GUI elements that show score saber data.
///
public void RefreshScoreSaberData(IPreviewBeatmapLevel level, BeatmapDifficulty vdifficulty)
{
Logger.Trace("RefreshScoreSaberData({0})", level.levelID);
if (!SongDataCore.Plugin.ScoreSaber.IsDataAvailable())
{
return;
}
BeatmapDifficulty difficulty = _beatUi.LevelDifficultyViewController.selectedDifficulty;
string difficultyString = difficulty.ToString();
if (difficultyString.Equals("ExpertPlus"))
{
difficultyString = "Expert+";
}
Logger.Debug(difficultyString);
// Check if we have data for this song
Logger.Debug("Checking if have info for song {0}", level.songName);
var hash = CustomHelpers.GetSongHash(level.levelID);
if (SongDataCore.Plugin.ScoreSaber.Data.Songs.ContainsKey(hash))
{
Logger.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
ScoreSaberSong scoreSaberSong = SongDataCore.Plugin.ScoreSaber.Data.Songs[hash];
ScoreSaberSongDifficultyStats scoreSaberSongDifficulty = scoreSaberSong.diffs.FirstOrDefault(x => String.Equals(x.diff, difficultyString));
if (scoreSaberSongDifficulty != null)
{
Logger.Debug("Display pp for song.");
double pp = scoreSaberSongDifficulty.pp;
double star = scoreSaberSongDifficulty.star;
UIBuilder.SetStatButtonText(_ppStatButton, String.Format("{0:0.#}", pp));
UIBuilder.SetStatButtonText(_starStatButton, String.Format("{0:0.#}", star));
}
else
{
UIBuilder.SetStatButtonText(_ppStatButton, "NA");
UIBuilder.SetStatButtonText(_starStatButton, "NA");
}
}
else
{
UIBuilder.SetStatButtonText(_ppStatButton, "NA");
UIBuilder.SetStatButtonText(_starStatButton, "NA");
}
Logger.Debug("Done refreshing score saber stats.");
}
///
/// Helper to refresh the NJS widget.
///
///
private void RefreshNoteJumpSpeed(BeatmapDifficulty beatmap)
{
UIBuilder.SetStatButtonText(_njsStatButton, String.Format("{0}", beatmap.NoteJumpMovementSpeed()));
}
///
/// Update interactive state of the quick scroll buttons.
///
private void RefreshQuickScrollButtons()
{
if (!_uiCreated)
{
return;
}
_pageUpFastButton.interactable = _beatUi.TableViewPageUpButton.interactable;
_pageUpFastButton.gameObject.SetActive(_beatUi.TableViewPageUpButton.IsActive());
_pageDownFastButton.interactable = _beatUi.TableViewPageDownButton.interactable;
_pageDownFastButton.gameObject.SetActive(_beatUi.TableViewPageDownButton.IsActive());
}
///
/// TODO - evaluate this sillyness...
///
///
public IEnumerator RefreshQuickScrollButtonsAsync()
{
yield return new WaitForEndOfFrame();
RefreshQuickScrollButtons();
}
///
/// Show the UI.
///
public void Show()
{
Logger.Trace("Show SongBrowserUI()");
this.SetVisibility(true);
}
///
/// Hide the UI.
///
public void Hide()
{
Logger.Trace("Hide SongBrowserUI()");
this.SetVisibility(false);
}
///
/// Handle showing or hiding UI logic.
///
///
private void SetVisibility(bool visible)
{
// UI not created, nothing visible to hide...
if (!_uiCreated)
{
return;
}
_ppStatButton.gameObject.SetActive(visible);
_starStatButton.gameObject.SetActive(visible);
_njsStatButton.gameObject.SetActive(visible);
RefreshOuterUIState(visible == true ? UIState.Main : UIState.Disabled);
_addFavoriteButton.gameObject.SetActive(visible);
_deleteButton.gameObject.SetActive(visible);
_pageUpFastButton.gameObject.SetActive(visible);
_pageDownFastButton.gameObject.SetActive(visible);
}
///
/// Update the top UI state.
/// Hides the outer ui, sort, and filter buttons depending on the state.
///
private void RefreshOuterUIState(UIState state)
{
bool sortButtons = false;
bool filterButtons = false;
bool outerButtons = false;
bool clearButton = true;
if (state == UIState.SortBy)
{
sortButtons = true;
}
else if (state == UIState.FilterBy)
{
filterButtons = true;
}
else if (state == UIState.Main)
{
outerButtons = true;
}
else
{
clearButton = false;
}
_sortButtonGroup.ForEach(x => x.Button.gameObject.SetActive(sortButtons));
_filterButtonGroup.ForEach(x => x.Button.gameObject.SetActive(filterButtons));
_sortByButton.gameObject.SetActive(outerButtons);
_sortByDisplay.gameObject.SetActive(outerButtons);
_filterByButton.gameObject.SetActive(outerButtons);
_filterByDisplay.gameObject.SetActive(outerButtons);
_clearSortFilterButton.gameObject.SetActive(clearButton);
_randomButton.gameObject.SetActive(outerButtons);
RefreshCurrentSelectionDisplay();
_currentUiState = state;
}
///
/// Adjust the text field of the sort by and filter by displays.
///
private void RefreshCurrentSelectionDisplay()
{
string sortByDisplay = null;
if (_model.Settings.sortMode == SongSortMode.Default)
{
sortByDisplay = "Title";
}
else
{
sortByDisplay = _model.Settings.sortMode.ToString();
}
_sortByDisplay.SetButtonText(sortByDisplay);
if (_model.Settings.filterMode != SongFilterMode.Custom)
{
// Custom SongFilterMod implies that another mod has modified the text of this button (do not overwrite)
_filterByDisplay.SetButtonText(_model.Settings.filterMode.ToString());
}
}
///
/// Helper to quickly refresh add to favorites button
///
///
private void RefreshAddFavoriteButton(String levelId)
{
if (levelId == null)
{
_currentAddFavoriteButtonSprite = null;
}
else
{
if (_model.CurrentEditingPlaylistLevelIds.Contains(levelId))
{
_currentAddFavoriteButtonSprite = Base64Sprites.RemoveFromFavoritesIcon;
}
else
{
_currentAddFavoriteButtonSprite = Base64Sprites.AddToFavoritesIcon;
}
}
_addFavoriteButton.SetButtonIcon(_currentAddFavoriteButtonSprite);
}
///
/// Adjust the UI colors.
///
public void RefreshSortButtonUI()
{
if (!_uiCreated)
{
return;
}
// So far all we need to refresh is the sort buttons.
foreach (SongSortButton sortButton in _sortButtonGroup)
{
if (sortButton.SortMode.NeedsBeatSaverData() && !SongDataCore.Plugin.BeatSaver.IsDataAvailable())
{
UIBuilder.SetButtonBorder(sortButton.Button, Color.gray);
}
else if (sortButton.SortMode.NeedsScoreSaberData() && !SongDataCore.Plugin.ScoreSaber.IsDataAvailable())
{
UIBuilder.SetButtonBorder(sortButton.Button, Color.gray);
}
else
{
UIBuilder.SetButtonBorder(sortButton.Button, Color.white);
}
if (sortButton.SortMode == _model.Settings.sortMode)
{
if (this._model.Settings.invertSortResults)
{
UIBuilder.SetButtonBorder(sortButton.Button, Color.red);
}
else
{
UIBuilder.SetButtonBorder(sortButton.Button, Color.green);
}
}
}
// refresh filter buttons
foreach (SongFilterButton filterButton in _filterButtonGroup)
{
UIBuilder.SetButtonBorder(filterButton.Button, Color.white);
if (filterButton.FilterMode == _model.Settings.filterMode)
{
UIBuilder.SetButtonBorder(filterButton.Button, Color.green);
}
}
if (this._model.Settings.invertSortResults)
{
UIBuilder.SetButtonBorder(_sortByDisplay, Color.red);
}
else
{
UIBuilder.SetButtonBorder(_sortByDisplay, Color.green);
}
if (this._model.Settings.filterMode != SongFilterMode.None)
{
UIBuilder.SetButtonBorder(_filterByDisplay, Color.green);
}
else
{
UIBuilder.SetButtonBorder(_filterByDisplay, Color.white);
}
}
///
///
///
public void RefreshSongList(bool scrollToLevel = true)
{
if (!_uiCreated)
{
return;
}
_beatUi.RefreshSongList(_model.LastSelectedLevelId);
}
///
/// Helper for updating the model (which updates the song list)
///
public void UpdateLevelDataModel()
{
try
{
Logger.Trace("UpdateLevelDataModel()");
// get a current beatmap characteristic...
if (_model.CurrentBeatmapCharacteristicSO == null && _uiCreated)
{
_model.CurrentBeatmapCharacteristicSO = _beatUi.BeatmapCharacteristicSelectionViewController.GetPrivateField("_selectedBeatmapCharacteristic");
}
_model.UpdateLevelRecords();
}
catch (Exception e)
{
Logger.Exception("SongBrowser UI crashed trying to update the internal song lists: ", e);
}
}
///
/// Logic for fixing BeatSaber's level pack selection bugs.
///
public bool UpdateLevelPackSelection()
{
if (_uiCreated)
{
IBeatmapLevelPack currentSelected = _beatUi.GetCurrentSelectedLevelPack();
Logger.Debug("Current selected level pack: {0}", currentSelected);
if (String.IsNullOrEmpty(_model.Settings.currentLevelPackId))
{
if (currentSelected == null)
{
Logger.Debug("No level pack selected, acquiring the first available...");
var levelPackCollection = _beatUi.LevelPackViewController.GetPrivateField("_levelPackCollection");
currentSelected = levelPackCollection.beatmapLevelPacks[0];
}
}
else if (currentSelected == null || (currentSelected.packID != _model.Settings.currentLevelPackId))
{
Logger.Debug("Automatically selecting level pack: {0}", _model.Settings.currentLevelPackId);
_beatUi.LevelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
_lastLevelPack = _beatUi.GetLevelPackByPackId(_model.Settings.currentLevelPackId);
_beatUi.SelectLevelPack(_model.Settings.currentLevelPackId);
_beatUi.LevelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
ProcessSongList();
return true;
}
}
return false;
}
}
}