SongBrowserUI.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. using UnityEngine;
  2. using System.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEngine.UI;
  6. using HMUI;
  7. using VRUI;
  8. using SongBrowserPlugin.DataAccess;
  9. using System.IO;
  10. using SongLoaderPlugin;
  11. namespace SongBrowserPlugin.UI
  12. {
  13. /// <summary>
  14. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  15. /// </summary>
  16. public class SongBrowserUI : MonoBehaviour
  17. {
  18. // Logging
  19. public const String Name = "SongBrowserUI";
  20. private Logger _log = new Logger(Name);
  21. // Beat Saber UI Elements
  22. StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
  23. StandardLevelListViewController _levelListViewController;
  24. StandardLevelDetailViewController _levelDetailViewController;
  25. StandardLevelDifficultyViewController _levelDifficultyViewController;
  26. StandardLevelSelectionNavigationController _levelSelectionNavigationController;
  27. // New UI Elements
  28. private List<SongSortButton> _sortButtonGroup;
  29. private Button _addFavoriteButton;
  30. private String _addFavoriteButtonText = null;
  31. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  32. private SimpleDialogPromptViewController _deleteDialog;
  33. private Button _deleteButton;
  34. // Debug
  35. private int _sortButtonLastPushedIndex = 0;
  36. private int _lastRow = 0;
  37. // Model
  38. private SongBrowserModel _model;
  39. /// <summary>
  40. /// Constructor
  41. /// </summary>
  42. public SongBrowserUI() : base()
  43. {
  44. if (_model == null)
  45. {
  46. _model = new SongBrowserModel();
  47. }
  48. _model.Init();
  49. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  50. }
  51. /// <summary>
  52. /// Builds the UI for this plugin.
  53. /// </summary>
  54. public void CreateUI()
  55. {
  56. _log.Trace("CreateUI()");
  57. try
  58. {
  59. if (_levelSelectionFlowCoordinator == null)
  60. {
  61. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<StandardLevelSelectionFlowCoordinator>().First();
  62. }
  63. if (_levelListViewController == null)
  64. {
  65. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  66. }
  67. if (_levelDetailViewController == null)
  68. {
  69. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  70. }
  71. if (_levelSelectionNavigationController == null)
  72. {
  73. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelSelectionNavigationController>("_levelSelectionNavigationController");
  74. }
  75. if (_levelDifficultyViewController == null)
  76. {
  77. _levelDifficultyViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDifficultyViewController>("_levelDifficultyViewController");
  78. }
  79. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  80. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  81. this._deleteDialog.gameObject.SetActive(false);
  82. this.CreateUIElements();
  83. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  84. }
  85. catch (Exception e)
  86. {
  87. _log.Exception("Exception during CreateUI: ", e);
  88. }
  89. }
  90. /// <summary>
  91. /// Builds the SongBrowser UI
  92. /// </summary>
  93. private void CreateUIElements()
  94. {
  95. _log.Trace("CreateUIElements");
  96. try
  97. {
  98. RectTransform rect = this._levelSelectionNavigationController.transform as RectTransform;
  99. // Create Sorting Songs By-Buttons
  100. _log.Debug("Creating sort by buttons...");
  101. System.Action<SongSortMode> onSortButtonClickEvent = delegate (SongSortMode sortMode) {
  102. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  103. SongBrowserModel.LastSelectedLevelId = null;
  104. _model.Settings.sortMode = sortMode;
  105. _model.Settings.Save();
  106. UpdateSongList();
  107. RefreshSongList();
  108. };
  109. _sortButtonGroup = new List<SongSortButton>
  110. {
  111. UIBuilder.CreateSortButton(rect, "PlayButton", "Favorite", 3, "AllDirectionsIcon", 66, 74.5f, 16f, 5f, SongSortMode.Favorites, onSortButtonClickEvent),
  112. UIBuilder.CreateSortButton(rect, "PlayButton", "Song", 3, "AllDirectionsIcon", 50f, 74.5f, 16f, 5f, SongSortMode.Default, onSortButtonClickEvent),
  113. UIBuilder.CreateSortButton(rect, "PlayButton", "Author", 3, "AllDirectionsIcon", 34f, 74.5f, 16f, 5f, SongSortMode.Author, onSortButtonClickEvent),
  114. UIBuilder.CreateSortButton(rect, "PlayButton", "Original", 3, "AllDirectionsIcon", 18f, 74.5f, 16f, 5f, SongSortMode.Original, onSortButtonClickEvent),
  115. UIBuilder.CreateSortButton(rect, "PlayButton", "Newest", 3, "AllDirectionsIcon", 2f, 74.5f, 16f, 5f, SongSortMode.Newest, onSortButtonClickEvent),
  116. };
  117. // Creaate Add to Favorites Button
  118. _log.Debug("Creating add to favorites button...");
  119. RectTransform transform = this._levelDetailViewController.transform as RectTransform;
  120. _addFavoriteButton = UIBuilder.CreateUIButton(transform, "QuitButton", SongBrowserApplication.Instance.ButtonTemplate);
  121. (_addFavoriteButton.transform as RectTransform).anchoredPosition = new Vector2(40f, 5.75f);
  122. (_addFavoriteButton.transform as RectTransform).sizeDelta = new Vector2(10f, 10f);
  123. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  124. UIBuilder.SetButtonTextSize(ref _addFavoriteButton, 3);
  125. UIBuilder.SetButtonIconEnabled(ref _addFavoriteButton, false);
  126. _addFavoriteButton.onClick.RemoveAllListeners();
  127. _addFavoriteButton.onClick.AddListener(delegate () {
  128. ToggleSongInFavorites();
  129. });
  130. if (_addFavoriteButtonText == null)
  131. {
  132. IStandardLevel level = this._levelListViewController.selectedLevel;
  133. if (level != null)
  134. {
  135. RefreshAddFavoriteButton(level.levelID);
  136. }
  137. }
  138. // Create delete button
  139. _log.Debug("Creating delete button...");
  140. transform = this._levelDetailViewController.transform as RectTransform;
  141. _deleteButton = UIBuilder.CreateUIButton(transform, "QuitButton", SongBrowserApplication.Instance.ButtonTemplate);
  142. (_deleteButton.transform as RectTransform).anchoredPosition = new Vector2(46f, 0f);
  143. (_deleteButton.transform as RectTransform).sizeDelta = new Vector2(15f, 5f);
  144. UIBuilder.SetButtonText(ref _deleteButton, "Delete");
  145. UIBuilder.SetButtonTextSize(ref _deleteButton, 3);
  146. UIBuilder.SetButtonIconEnabled(ref _deleteButton, false);
  147. _deleteButton.onClick.RemoveAllListeners();
  148. _deleteButton.onClick.AddListener(delegate () {
  149. HandleDeleteSelectedLevel();
  150. });
  151. RefreshUI();
  152. }
  153. catch (Exception e)
  154. {
  155. _log.Exception("Exception CreateUIElements:", e);
  156. }
  157. }
  158. /// <summary>
  159. /// Adjust UI based on level selected.
  160. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  161. /// </summary>
  162. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  163. {
  164. _log.Trace("OnDidSelectLevelEvent({0}", level.levelID);
  165. if (level == null)
  166. {
  167. _log.Debug("No level selected?");
  168. return;
  169. }
  170. if (_model.Settings == null)
  171. {
  172. _log.Debug("Settings not instantiated yet?");
  173. return;
  174. }
  175. SongBrowserModel.LastSelectedLevelId = level.levelID;
  176. RefreshAddFavoriteButton(level.levelID);
  177. }
  178. /// <summary>
  179. /// Pop up a delete dialog.
  180. /// </summary>
  181. private void HandleDeleteSelectedLevel()
  182. {
  183. IStandardLevel level = this._levelListViewController.selectedLevel;
  184. if (level == null)
  185. {
  186. _log.Info("No level selected, cannot delete nothing...");
  187. return;
  188. }
  189. if (level.levelID.StartsWith("Level"))
  190. {
  191. _log.Debug("Cannot delete non-custom levels.");
  192. return;
  193. }
  194. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  195. this._deleteDialog.Init("Delete level warning!", String.Format("<color=#00AAFF>Permanently delete level: {0}</color>\n Do you want to continue?", customLevel.songName), "YES", "NO");
  196. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  197. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  198. }
  199. /// <summary>
  200. /// Handle delete dialog resolution.
  201. /// </summary>
  202. /// <param name="viewController"></param>
  203. /// <param name="ok"></param>
  204. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  205. {
  206. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  207. if (!ok)
  208. {
  209. viewController.DismissModalViewController(null, false);
  210. }
  211. else
  212. {
  213. IStandardLevel level = this._levelListViewController.selectedLevel;
  214. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  215. viewController.DismissModalViewController(null, false);
  216. _log.Debug("Deleting: {0}", customLevel.customSongInfo.path);
  217. FileAttributes attr = File.GetAttributes(customLevel.customSongInfo.path);
  218. if (attr.HasFlag(FileAttributes.Directory))
  219. Directory.Delete(customLevel.customSongInfo.path);
  220. else
  221. File.Delete(customLevel.customSongInfo.path);
  222. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(customLevel.customSongInfo.path);
  223. }
  224. }
  225. /// <summary>
  226. /// Add/Remove song from favorites depending on if it already exists.
  227. /// </summary>
  228. private void ToggleSongInFavorites()
  229. {
  230. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  231. if (_model.Settings.favorites.Contains(songInfo.levelID))
  232. {
  233. _log.Info("Remove {0} from favorites", songInfo.songName);
  234. _model.Settings.favorites.Remove(songInfo.levelID);
  235. _addFavoriteButtonText = "+1";
  236. }
  237. else
  238. {
  239. _log.Info("Add {0} to favorites", songInfo.songName);
  240. _model.Settings.favorites.Add(songInfo.levelID);
  241. _addFavoriteButtonText = "-1";
  242. }
  243. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  244. _model.Settings.Save();
  245. }
  246. /// <summary>
  247. /// Helper to quickly refresh add to favorites button
  248. /// </summary>
  249. /// <param name="levelId"></param>
  250. private void RefreshAddFavoriteButton(String levelId)
  251. {
  252. if (levelId == null)
  253. {
  254. _addFavoriteButtonText = "0";
  255. return;
  256. }
  257. if (_model.Settings.favorites.Contains(levelId))
  258. {
  259. _addFavoriteButtonText = "-1";
  260. }
  261. else
  262. {
  263. _addFavoriteButtonText = "+1";
  264. }
  265. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  266. }
  267. /// <summary>
  268. /// Adjust the UI colors.
  269. /// </summary>
  270. public void RefreshUI()
  271. {
  272. // So far all we need to refresh is the sort buttons.
  273. foreach (SongSortButton sortButton in _sortButtonGroup)
  274. {
  275. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  276. if (sortButton.SortMode == _model.Settings.sortMode)
  277. {
  278. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  279. }
  280. }
  281. }
  282. /// <summary>
  283. /// Try to refresh the song list. Broken for now.
  284. /// </summary>
  285. public void RefreshSongList()
  286. {
  287. _log.Info("Refreshing the song list view.");
  288. try
  289. {
  290. if (_model.SortedSongList == null)
  291. {
  292. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  293. return;
  294. }
  295. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  296. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  297. StandardLevelListTableView _songListTableView = songListViewController.GetComponentInChildren<StandardLevelListTableView>();
  298. ReflectionUtil.SetPrivateField(_songListTableView, "_levels", levels);
  299. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  300. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_songListTableView, "_tableView");
  301. tableView.ReloadData();
  302. String selectedLevelID = null;
  303. if (SongBrowserModel.LastSelectedLevelId != null)
  304. {
  305. selectedLevelID = SongBrowserModel.LastSelectedLevelId;
  306. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  307. }
  308. else
  309. {
  310. selectedLevelID = levels.FirstOrDefault().levelID;
  311. }
  312. if (levels.Any(x => x.levelID == selectedLevelID))
  313. {
  314. SelectAndScrollToLevel(_songListTableView, selectedLevelID);
  315. }
  316. RefreshUI();
  317. }
  318. catch (Exception e)
  319. {
  320. _log.Exception("Exception refreshing song list:", e);
  321. }
  322. }
  323. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  324. {
  325. int row = table.RowNumberForLevelID(levelID);
  326. TableView _tableView = table.GetComponentInChildren<TableView>();
  327. _tableView.SelectRow(row, true);
  328. _tableView.ScrollToRow(row, true);
  329. }
  330. /// <summary>
  331. /// Helper for updating the model (which updates the song list)c
  332. /// </summary>
  333. public void UpdateSongList()
  334. {
  335. _log.Trace("UpdateSongList()");
  336. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  337. _model.UpdateSongLists(gameplayMode);
  338. }
  339. /// <summary>
  340. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  341. /// </summary>
  342. public void LateUpdate()
  343. {
  344. if (this._levelListViewController.isInViewControllerHierarchy)
  345. {
  346. CheckDebugUserInput();
  347. }
  348. }
  349. /// <summary>
  350. /// Map some key presses directly to UI interactions to make testing easier.
  351. /// </summary>
  352. private void CheckDebugUserInput()
  353. {
  354. try
  355. {
  356. // back
  357. if (Input.GetKeyDown(KeyCode.Escape))
  358. {
  359. this._levelSelectionNavigationController.DismissButtonWasPressed();
  360. }
  361. // cycle sort modes
  362. if (Input.GetKeyDown(KeyCode.T))
  363. {
  364. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  365. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  366. }
  367. // delete
  368. if (Input.GetKeyDown(KeyCode.D))
  369. {
  370. if (_deleteDialog.isInViewControllerHierarchy)
  371. {
  372. return;
  373. }
  374. _deleteButton.onClick.Invoke();
  375. }
  376. StandardLevelListTableView levelListTableView = this._levelListViewController.GetComponentInChildren<StandardLevelListTableView>();
  377. // z,x,c,v can be used to get into a song, b will hit continue button after song ends
  378. if (Input.GetKeyDown(KeyCode.C))
  379. {
  380. levelListTableView.SelectAndScrollToLevel(_model.SortedSongList[0].levelID);
  381. this._levelListViewController.HandleLevelListTableViewDidSelectRow(levelListTableView, 0);
  382. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  383. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  384. }
  385. if (Input.GetKeyDown(KeyCode.V))
  386. {
  387. this._levelSelectionFlowCoordinator.HandleLevelDetailViewControllerDidPressPlayButton(this._levelDetailViewController);
  388. }
  389. // change song index
  390. if (Input.GetKeyDown(KeyCode.N))
  391. {
  392. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  393. levelListTableView.SelectAndScrollToLevel(_model.SortedSongList[_lastRow].levelID);
  394. this._levelListViewController.HandleLevelListTableViewDidSelectRow(levelListTableView, _lastRow);
  395. }
  396. if (Input.GetKeyDown(KeyCode.M))
  397. {
  398. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  399. levelListTableView.SelectAndScrollToLevel(_model.SortedSongList[_lastRow].levelID);
  400. this._levelListViewController.HandleLevelListTableViewDidSelectRow(levelListTableView, _lastRow);
  401. }
  402. // add to favorites
  403. if (Input.GetKeyDown(KeyCode.F))
  404. {
  405. ToggleSongInFavorites();
  406. }
  407. }
  408. catch (Exception e)
  409. {
  410. _log.Exception("Debug Input caused Exception: ", e);
  411. }
  412. }
  413. }
  414. }