SongBrowserUI.cs 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238
  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. using System.Security.Cryptography;
  12. using System.Text;
  13. using System.Text.RegularExpressions;
  14. using TMPro;
  15. namespace SongBrowserPlugin.UI
  16. {
  17. /// <summary>
  18. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  19. /// </summary>
  20. public class SongBrowserUI : MonoBehaviour
  21. {
  22. // Logging
  23. public const String Name = "SongBrowserUI";
  24. private const float SEGMENT_PERCENT = 0.1f;
  25. private const int LIST_ITEMS_VISIBLE_AT_ONCE = 6;
  26. private Logger _log = new Logger(Name);
  27. // Beat Saber UI Elements
  28. private StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
  29. private StandardLevelListViewController _levelListViewController;
  30. private StandardLevelDetailViewController _levelDetailViewController;
  31. private StandardLevelDifficultyViewController _levelDifficultyViewController;
  32. private StandardLevelSelectionNavigationController _levelSelectionNavigationController;
  33. private StandardLevelListTableView _levelListTableView;
  34. private RectTransform _tableViewRectTransform;
  35. private Button _tableViewPageUpButton;
  36. private Button _tableViewPageDownButton;
  37. private Button _playButton;
  38. private RectTransform _bmpValueTextRect;
  39. // New UI Elements
  40. private List<SongSortButton> _sortButtonGroup;
  41. private List<SongFilterButton> _filterButtonGroup;
  42. private Button _addFavoriteButton;
  43. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  44. private SimpleDialogPromptViewController _deleteDialog;
  45. private Button _deleteButton;
  46. private Button _pageUpFastButton;
  47. private Button _pageDownFastButton;
  48. private Button _enterFolderButton;
  49. private Button _upFolderButton;
  50. private SearchKeyboardViewController _searchViewController;
  51. private PlaylistFlowCoordinator _playListFlowCoordinator;
  52. private TextMeshProUGUI _ppText;
  53. private TextMeshProUGUI _starText;
  54. // Cached items
  55. private Sprite _addFavoriteSprite;
  56. private Sprite _removeFavoriteSprite;
  57. private Sprite _currentAddFavoriteButtonSprite;
  58. // Plugin Compat checks
  59. private bool _detectedTwitchPluginQueue = false;
  60. private bool _checkedForTwitchPlugin = false;
  61. // Debug
  62. private int _sortButtonLastPushedIndex = 0;
  63. private int _lastRow = 0;
  64. // Model
  65. private SongBrowserModel _model;
  66. public SongBrowserModel Model
  67. {
  68. get
  69. {
  70. return _model;
  71. }
  72. }
  73. /// <summary>
  74. /// Constructor
  75. /// </summary>
  76. public SongBrowserUI() : base()
  77. {
  78. if (_model == null)
  79. {
  80. _model = new SongBrowserModel();
  81. }
  82. _model.Init();
  83. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  84. }
  85. /// <summary>
  86. /// Builds the UI for this plugin.
  87. /// </summary>
  88. public void CreateUI()
  89. {
  90. _log.Trace("CreateUI()");
  91. try
  92. {
  93. if (_levelSelectionFlowCoordinator == null)
  94. {
  95. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<StandardLevelSelectionFlowCoordinator>().First();
  96. }
  97. if (_levelListViewController == null)
  98. {
  99. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  100. }
  101. if (_levelDetailViewController == null)
  102. {
  103. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  104. }
  105. if (_levelSelectionNavigationController == null)
  106. {
  107. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelSelectionNavigationController>("_levelSelectionNavigationController");
  108. }
  109. if (_levelDifficultyViewController == null)
  110. {
  111. _levelDifficultyViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDifficultyViewController>("_levelDifficultyViewController");
  112. }
  113. if (_levelListTableView == null)
  114. {
  115. _levelListTableView = this._levelListViewController.GetComponentInChildren<StandardLevelListTableView>();
  116. }
  117. _bmpValueTextRect = Resources.FindObjectsOfTypeAll<RectTransform>().First(x => x.name == "BPMValueText");
  118. _playButton = _levelDetailViewController.GetComponentsInChildren<Button>().FirstOrDefault(x => x.name == "PlayButton");
  119. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  120. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  121. this._deleteDialog.gameObject.SetActive(false);
  122. this._addFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.AddToFavoritesIcon);
  123. this._removeFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.RemoveFromFavoritesIcon);
  124. this.CreateUIElements();
  125. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  126. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  127. tableView.didSelectRowEvent += HandleDidSelectTableViewRow;
  128. _levelDifficultyViewController.didSelectDifficultyEvent += OnDidSelectDifficultyEvent;
  129. }
  130. catch (Exception e)
  131. {
  132. _log.Exception("Exception during CreateUI: ", e);
  133. }
  134. }
  135. /// <summary>
  136. /// Builds the SongBrowser UI
  137. /// </summary>
  138. private void CreateUIElements()
  139. {
  140. _log.Trace("CreateUIElements");
  141. try
  142. {
  143. // Gather some transforms and templates to use.
  144. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  145. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  146. Button sortButtonTemplate = _playButton;
  147. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "QuitButton"));
  148. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  149. // Resize some of the UI
  150. _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
  151. _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
  152. _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  153. // Create Sorting Songs By-Buttons
  154. _log.Debug("Creating sort by buttons...");
  155. float fontSize = 2.35f;
  156. float buttonWidth = 13.50f;
  157. float buttonHeight = 5.5f;
  158. float buttonX = -61;
  159. float buttonY = 74.0f;
  160. string[] sortButtonNames = new string[]
  161. {
  162. "Song", "Author", "Original", "Newest", "Plays", "PP", "Difficult", "Random"
  163. };
  164. SongSortMode[] sortModes = new SongSortMode[]
  165. {
  166. SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.PP, SongSortMode.Difficulty, SongSortMode.Random
  167. };
  168. _sortButtonGroup = new List<SongSortButton>();
  169. for (int i = 0; i < sortButtonNames.Length; i++)
  170. {
  171. _sortButtonGroup.Add(UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  172. sortButtonNames[i],
  173. fontSize,
  174. buttonX + (buttonWidth * i),
  175. buttonY,
  176. buttonWidth,
  177. buttonHeight,
  178. sortModes[i],
  179. OnSortButtonClickEvent));
  180. }
  181. // Create filter buttons
  182. float filterButtonX = buttonX + (buttonWidth * (sortButtonNames.Length - 1)) + (buttonWidth / 2.0f) + 2.5f;
  183. Vector2 iconButtonSize = new Vector2(5.5f, buttonHeight);
  184. Sprite playlistSprite = Base64Sprites.Base64ToSprite(Base64Sprites.PlaylistIcon);
  185. Sprite searchSprite = Base64Sprites.Base64ToSprite(Base64Sprites.SearchIcon);
  186. List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>> filterButtonSetup = new List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>>()
  187. {
  188. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Favorites, OnFavoriteFilterButtonClickEvent, _addFavoriteSprite),
  189. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Playlist, OnPlaylistButtonClickEvent, playlistSprite),
  190. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Search, OnSearchButtonClickEvent, searchSprite),
  191. };
  192. _filterButtonGroup = new List<SongFilterButton>();
  193. for (int i = 0; i < filterButtonSetup.Count; i++)
  194. {
  195. Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite> t = filterButtonSetup[i];
  196. Button b = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate,
  197. t.Item3,
  198. new Vector2(filterButtonX + (iconButtonSize.x * i), buttonY),
  199. new Vector2(iconButtonSize.x, iconButtonSize.y),
  200. new Vector2(0, 0),
  201. new Vector2(3.5f, 3.5f),
  202. new Vector2(1.0f, 1.0f),
  203. 0);
  204. SongFilterButton filterButton = new SongFilterButton
  205. {
  206. Button = b,
  207. FilterMode = t.Item1
  208. };
  209. b.onClick.AddListener(t.Item2);
  210. _filterButtonGroup.Add(filterButton);
  211. }
  212. // Create Add to Favorites Button
  213. Vector2 addFavoritePos = new Vector2(40f, (sortButtonTemplate.transform as RectTransform).anchoredPosition.y);
  214. _addFavoriteButton = UIBuilder.CreateIconButton(otherButtonTransform, otherButtonTemplate, null,
  215. new Vector2(addFavoritePos.x, addFavoritePos.y),
  216. new Vector2(10.0f, 10.0f),
  217. new Vector2(2f, -1.5f),
  218. new Vector2(7.0f, 7.0f),
  219. new Vector2(1.0f, 1.0f),
  220. 0.0f);
  221. _addFavoriteButton.onClick.AddListener(delegate () {
  222. ToggleSongInFavorites();
  223. });
  224. if (_currentAddFavoriteButtonSprite == null)
  225. {
  226. IStandardLevel level = this._levelListViewController.selectedLevel;
  227. if (level != null)
  228. {
  229. RefreshAddFavoriteButton(level.levelID);
  230. }
  231. }
  232. // Create delete button
  233. _deleteButton = UIBuilder.CreateButton(otherButtonTransform, otherButtonTemplate, "Delete", fontSize, 46f, 0f, 15f, 5f);
  234. _deleteButton.onClick.AddListener(delegate () {
  235. HandleDeleteSelectedLevel();
  236. });
  237. // Create fast scroll buttons
  238. _pageUpFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  239. new Vector2(15, 67.5f),
  240. new Vector2(6.0f, 5.5f),
  241. new Vector2(0f, 0f),
  242. new Vector2(1.5f, 1.5f),
  243. new Vector2(2.0f, 2.0f),
  244. 180);
  245. _pageUpFastButton.onClick.AddListener(delegate () {
  246. this.JumpSongList(-1, SEGMENT_PERCENT);
  247. });
  248. _pageDownFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  249. new Vector2(15, 0.5f),
  250. new Vector2(6.0f, 5.5f),
  251. new Vector2(0f, 0f),
  252. new Vector2(1.5f, 1.5f),
  253. new Vector2(2.0f, 2.0f),
  254. 0);
  255. _pageDownFastButton.onClick.AddListener(delegate () {
  256. this.JumpSongList(1, SEGMENT_PERCENT);
  257. });
  258. // Create enter folder button
  259. if (_model.Settings.folderSupportEnabled)
  260. {
  261. _enterFolderButton = UIBuilder.CreateUIButton(otherButtonTransform, _playButton);
  262. _enterFolderButton.onClick.AddListener(delegate ()
  263. {
  264. _model.PushDirectory(_levelListViewController.selectedLevel);
  265. this.RefreshSongList();
  266. this.RefreshDirectoryButtons();
  267. });
  268. UIBuilder.SetButtonText(ref _enterFolderButton, "Enter");
  269. // Create up folder button
  270. _upFolderButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  271. new Vector2(filterButtonX + (iconButtonSize.x* filterButtonSetup.Count), buttonY),
  272. new Vector2(iconButtonSize.x, iconButtonSize.y),
  273. new Vector2(0f, 0f),
  274. new Vector2(0.85f, 0.85f),
  275. new Vector2(2.0f, 2.0f),
  276. 180);
  277. _upFolderButton.onClick.RemoveAllListeners();
  278. _upFolderButton.onClick.AddListener(delegate ()
  279. {
  280. _model.PopDirectory();
  281. this.RefreshSongList();
  282. this.RefreshDirectoryButtons();
  283. });
  284. }
  285. RefreshSortButtonUI();
  286. RefreshDirectoryButtons();
  287. }
  288. catch (Exception e)
  289. {
  290. _log.Exception("Exception CreateUIElements:", e);
  291. }
  292. }
  293. /// <summary>
  294. /// Sort button clicked.
  295. /// </summary>
  296. private void OnSortButtonClickEvent(SongSortMode sortMode)
  297. {
  298. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  299. _model.LastSelectedLevelId = null;
  300. if (_model.Settings.sortMode == sortMode)
  301. {
  302. _model.ToggleInverting();
  303. }
  304. _model.Settings.sortMode = sortMode;
  305. _model.Settings.Save();
  306. //this._model.ProcessSongList();
  307. UpdateSongList();
  308. RefreshSongList();
  309. // Handle instant queue logic, avoid picking a folder.
  310. if (_model.Settings.sortMode == SongSortMode.Random && _model.Settings.randomInstantQueue)
  311. {
  312. for (int i = 0; i < _model.SortedSongList.Count; i++)
  313. {
  314. if (!_model.SortedSongList[i].levelID.StartsWith("Folder_"))
  315. {
  316. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[i].levelID);
  317. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, _model.SortedSongList[i].difficultyBeatmaps.Length-1);
  318. _playButton.onClick.Invoke();
  319. break;
  320. }
  321. }
  322. }
  323. }
  324. /// <summary>
  325. /// Filter by favorites.
  326. /// </summary>
  327. private void OnFavoriteFilterButtonClickEvent()
  328. {
  329. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Favorites.ToString());
  330. if (_model.Settings.filterMode != SongFilterMode.Favorites)
  331. {
  332. _model.Settings.filterMode = SongFilterMode.Favorites;
  333. }
  334. else
  335. {
  336. _model.Settings.filterMode = SongFilterMode.None;
  337. }
  338. _model.Settings.Save();
  339. UpdateSongList();
  340. RefreshSongList();
  341. }
  342. /// <summary>
  343. /// Filter button clicked.
  344. /// </summary>
  345. /// <param name="sortMode"></param>
  346. private void OnSearchButtonClickEvent()
  347. {
  348. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
  349. if (_model.Settings.filterMode != SongFilterMode.Search)
  350. {
  351. _model.Settings.filterMode = SongFilterMode.Search;
  352. this.ShowSearchKeyboard();
  353. }
  354. else
  355. {
  356. _model.Settings.filterMode = SongFilterMode.None;
  357. UpdateSongList();
  358. RefreshSongList();
  359. }
  360. _model.Settings.Save();
  361. }
  362. /// <summary>
  363. /// Display the playlist selector.
  364. /// </summary>
  365. /// <param name="sortMode"></param>
  366. private void OnPlaylistButtonClickEvent()
  367. {
  368. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
  369. _model.LastSelectedLevelId = null;
  370. if (_model.Settings.filterMode != SongFilterMode.Playlist)
  371. {
  372. if (_playListFlowCoordinator == null || !_playListFlowCoordinator.isActiveAndEnabled)
  373. {
  374. _playListFlowCoordinator = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  375. _playListFlowCoordinator.didSelectPlaylist += HandleDidSelectPlaylist;
  376. _playListFlowCoordinator.Present(_levelSelectionNavigationController);
  377. }
  378. }
  379. else
  380. {
  381. _model.Settings.filterMode = SongFilterMode.None;
  382. _model.Settings.Save();
  383. UpdateSongList();
  384. RefreshSongList();
  385. }
  386. }
  387. /// <summary>
  388. /// Adjust UI based on level selected.
  389. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  390. /// </summary>
  391. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  392. {
  393. try
  394. {
  395. _log.Trace("OnDidSelectLevelEvent()");
  396. if (level == null)
  397. {
  398. _log.Debug("No level selected?");
  399. return;
  400. }
  401. if (_model.Settings == null)
  402. {
  403. _log.Debug("Settings not instantiated yet?");
  404. return;
  405. }
  406. _model.LastSelectedLevelId = level.levelID;
  407. RefreshAddFavoriteButton(level.levelID);
  408. RefreshQuickScrollButtons();
  409. if (level.levelID.StartsWith("Folder_"))
  410. {
  411. _log.Debug("Folder selected! Adjust PlayButton logic...");
  412. HandleDidSelectFolderRow(level);
  413. }
  414. else
  415. {
  416. HandleDidSelectLevelRow(level);
  417. }
  418. }
  419. catch (Exception e)
  420. {
  421. _log.Exception("Exception selecting song:", e);
  422. }
  423. }
  424. /// <summary>
  425. /// Handle difficulty level selection.
  426. /// </summary>
  427. private void OnDidSelectDifficultyEvent(StandardLevelDifficultyViewController view, IStandardLevelDifficultyBeatmap beatmap)
  428. {
  429. this.RefreshScoreSaberData(_levelListViewController.selectedLevel);
  430. }
  431. /// <summary>
  432. /// Turn play button into enter folder button.
  433. /// </summary>
  434. private void HandleDidSelectFolderRow(IStandardLevel level)
  435. {
  436. _enterFolderButton.gameObject.SetActive(true);
  437. _playButton.gameObject.SetActive(false);
  438. }
  439. /// <summary>
  440. /// Turn enter folder button into play button.
  441. /// </summary>
  442. /// <param name="level"></param>
  443. private void HandleDidSelectLevelRow(IStandardLevel level)
  444. {
  445. // deal with enter folder button
  446. if (_enterFolderButton != null)
  447. {
  448. _enterFolderButton.gameObject.SetActive(false);
  449. }
  450. _playButton.gameObject.SetActive(true);
  451. this.RefreshScoreSaberData(level);
  452. }
  453. /// <summary>
  454. /// Track the current row.
  455. /// </summary>
  456. /// <param name="tableView"></param>
  457. /// <param name="row"></param>
  458. private void HandleDidSelectTableViewRow(TableView tableView, int row)
  459. {
  460. _log.Trace("HandleDidSelectTableViewRow({0})", row);
  461. _lastRow = row;
  462. }
  463. /// <summary>
  464. /// Pop up a delete dialog.
  465. /// </summary>
  466. private void HandleDeleteSelectedLevel()
  467. {
  468. IStandardLevel level = this._levelListViewController.selectedLevel;
  469. if (level == null)
  470. {
  471. _log.Info("No level selected, cannot delete nothing...");
  472. return;
  473. }
  474. if (level.levelID.StartsWith("Level"))
  475. {
  476. _log.Debug("Cannot delete non-custom levels.");
  477. return;
  478. }
  479. if (level.levelID.StartsWith("Folder"))
  480. {
  481. _log.Debug("Cannot delete folders.");
  482. return;
  483. }
  484. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  485. 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");
  486. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  487. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  488. }
  489. /// <summary>
  490. /// Handle delete dialog resolution.
  491. /// </summary>
  492. /// <param name="viewController"></param>
  493. /// <param name="ok"></param>
  494. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  495. {
  496. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  497. if (!ok)
  498. {
  499. viewController.DismissModalViewController(null, false);
  500. }
  501. else
  502. {
  503. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  504. IStandardLevel level = this._levelListViewController.selectedLevel;
  505. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  506. string songPath = customLevel.customSongInfo.path;
  507. bool isZippedSong = false;
  508. viewController.DismissModalViewController(null, false);
  509. _log.Debug("Deleting: {0}", songPath);
  510. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  511. {
  512. isZippedSong = true;
  513. }
  514. if (isZippedSong)
  515. {
  516. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  517. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  518. Directory.Delete(songHashDir.FullName, true);
  519. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  520. {
  521. string hash = CreateMD5FromFile(file);
  522. if (hash != null)
  523. {
  524. if (hash == songHashDir.Name)
  525. {
  526. _log.Debug("Deleting zipped song: {0}", file);
  527. File.Delete(file);
  528. break;
  529. }
  530. }
  531. }
  532. }
  533. else
  534. {
  535. // Just delete the song we know about.
  536. FileAttributes attr = File.GetAttributes(songPath);
  537. if (attr.HasFlag(FileAttributes.Directory))
  538. {
  539. _log.Debug("Deleting song: {0}", songPath);
  540. Directory.Delete(songPath, true);
  541. }
  542. // check if this is in the BeatSaberDownloader format
  543. if (_model.Settings.deleteNumberedSongFolder)
  544. {
  545. String[] splitPath = songPath.Split('/');
  546. if (splitPath.Length > 2)
  547. {
  548. String numberedDir = splitPath[splitPath.Length - 2];
  549. Regex r = new Regex(@"^\d{1,}-\d{1,}");
  550. if (r.Match(numberedDir).Success)
  551. {
  552. DirectoryInfo songNumberedDirPath = Directory.GetParent(songPath);
  553. _log.Debug("Deleting song numbered folder: {0}", songNumberedDirPath.FullName);
  554. Directory.Delete(songNumberedDirPath.FullName, true);
  555. }
  556. }
  557. }
  558. }
  559. int newRow = _model.SortedSongList.FindIndex(x => x.levelID == level.levelID) - 1;
  560. if (newRow > 0 && newRow < _model.SortedSongList.Count)
  561. {
  562. _model.LastSelectedLevelId = _model.SortedSongList[newRow].levelID;
  563. }
  564. else
  565. {
  566. _model.LastSelectedLevelId = null;
  567. }
  568. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  569. this.UpdateSongList();
  570. this.RefreshSongList();
  571. }
  572. }
  573. /// <summary>
  574. /// Create MD5 of a file.
  575. /// </summary>
  576. /// <param name="path"></param>
  577. /// <returns></returns>
  578. public static string CreateMD5FromFile(string path)
  579. {
  580. string hash = "";
  581. if (!File.Exists(path)) return null;
  582. using (MD5 md5 = MD5.Create())
  583. {
  584. using (var stream = File.OpenRead(path))
  585. {
  586. byte[] hashBytes = md5.ComputeHash(stream);
  587. StringBuilder sb = new StringBuilder();
  588. foreach (byte hashByte in hashBytes)
  589. {
  590. sb.Append(hashByte.ToString("X2"));
  591. }
  592. hash = sb.ToString();
  593. return hash;
  594. }
  595. }
  596. }
  597. /// <summary>
  598. /// Handle selection of a playlist. Show just the songs in the playlist.
  599. /// </summary>
  600. /// <param name="p"></param>
  601. private void HandleDidSelectPlaylist(Playlist p)
  602. {
  603. if (_playListFlowCoordinator != null)
  604. {
  605. _levelSelectionNavigationController.PopViewControllerImmediately();
  606. _playListFlowCoordinator.gameObject.SetActive(false);
  607. UnityEngine.Object.DestroyImmediate(_playListFlowCoordinator);
  608. }
  609. if (p != null)
  610. {
  611. _log.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  612. _model.Settings.filterMode = SongFilterMode.Playlist;
  613. _model.CurrentPlaylist = p;
  614. _model.Settings.Save();
  615. }
  616. else
  617. {
  618. _log.Debug("No playlist selected");
  619. }
  620. this.UpdateSongList();
  621. this.RefreshSongList();
  622. }
  623. /// <summary>
  624. /// Display the search keyboard
  625. /// </summary>
  626. void ShowSearchKeyboard()
  627. {
  628. if (_searchViewController == null)
  629. {
  630. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  631. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  632. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  633. }
  634. _log.Debug("Presenting keyboard");
  635. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  636. }
  637. /// <summary>
  638. /// Handle back button event from search keyboard.
  639. /// </summary>
  640. private void SearchViewControllerbackButtonPressed()
  641. {
  642. }
  643. /// <summary>
  644. /// Handle search.
  645. /// </summary>
  646. /// <param name="searchFor"></param>
  647. private void SearchViewControllerSearchButtonPressed(string searchFor)
  648. {
  649. _log.Debug("Searching for \"{0}\"...", searchFor);
  650. _model.Settings.searchTerms.Insert(0, searchFor);
  651. _model.Settings.Save();
  652. _model.LastSelectedLevelId = null;
  653. this.UpdateSongList();
  654. this.RefreshSongList();
  655. }
  656. /// <summary>
  657. /// Make big jumps in the song list.
  658. /// </summary>
  659. /// <param name="numJumps"></param>
  660. private void JumpSongList(int numJumps, float segmentPercent)
  661. {
  662. int totalSize = _model.SortedSongList.Count;
  663. int segmentSize = (int)(totalSize * segmentPercent);
  664. // Jump at least one scree size.
  665. if (segmentSize < LIST_ITEMS_VISIBLE_AT_ONCE)
  666. {
  667. segmentSize = LIST_ITEMS_VISIBLE_AT_ONCE;
  668. }
  669. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  670. int jumpDirection = Math.Sign(numJumps);
  671. int newRow = _lastRow + (jumpDirection * segmentSize);
  672. if (newRow <= 0)
  673. {
  674. newRow = 0;
  675. }
  676. else if (newRow >= totalSize)
  677. {
  678. newRow = totalSize - 1;
  679. }
  680. _log.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
  681. _lastRow = newRow;
  682. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  683. }
  684. /// <summary>
  685. /// Add/Remove song from favorites depending on if it already exists.
  686. /// </summary>
  687. private void ToggleSongInFavorites()
  688. {
  689. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  690. if (_model.Settings.Favorites.Contains(songInfo.levelID))
  691. {
  692. _log.Info("Remove {0} from favorites", songInfo.songName);
  693. _model.Settings.Favorites.Remove(songInfo.levelID);
  694. }
  695. else
  696. {
  697. _log.Info("Add {0} to favorites", songInfo.songName);
  698. _model.Settings.Favorites.Add(songInfo.levelID);
  699. }
  700. RefreshAddFavoriteButton(songInfo.levelID);
  701. _model.Settings.Save();
  702. }
  703. /// <summary>
  704. /// Update GUI elements that show score saber data.
  705. /// TODO - make better
  706. /// </summary>
  707. public void RefreshScoreSaberData(IStandardLevel level)
  708. {
  709. // TODO - fix this obvious mess...
  710. // use controllers level...
  711. if (level == null)
  712. {
  713. level = _levelListViewController.selectedLevel;
  714. }
  715. // abort!
  716. if (level == null || _bmpValueTextRect == null)
  717. {
  718. _log.Debug("Aborting RefreshScoreSaberData()");
  719. return;
  720. }
  721. // display pp potentially
  722. if (this._model.LevelIdToScoreSaberData != null && this._levelDifficultyViewController.selectedDifficultyLevel != null)
  723. {
  724. if (this._ppText == null)
  725. {
  726. // Create the PP and Star rating labels
  727. //RectTransform bmpTextRect = Resources.FindObjectsOfTypeAll<RectTransform>().First(x => x.name == "BPMText");
  728. var text = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "PP", new Vector2(-5, -41), new Vector2(20f, 10f));
  729. text.fontSize = 3.5f;
  730. text.alignment = TextAlignmentOptions.Left;
  731. text = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "STAR", new Vector2(-5, -22), new Vector2(20f, 10f));
  732. text.fontSize = 3.5f;
  733. text.alignment = TextAlignmentOptions.Left;
  734. _ppText = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "?", new Vector2(_bmpValueTextRect.anchoredPosition.x, -41), new Vector2(39f, 10f));
  735. _ppText.fontSize = 3.5f;
  736. _ppText.alignment = TextAlignmentOptions.Right;
  737. _starText = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "", new Vector2(_bmpValueTextRect.anchoredPosition.x, -22), new Vector2(39f, 10f));
  738. _starText.fontSize = 3.5f;
  739. _starText.alignment = TextAlignmentOptions.Right;
  740. }
  741. LevelDifficulty difficulty = this._levelDifficultyViewController.selectedDifficultyLevel.difficulty;
  742. string difficultyString = difficulty.ToString();
  743. _log.Debug("Checking if have info for song {0}", level.songName);
  744. if (this._model.LevelIdToScoreSaberData.ContainsKey(level.levelID))
  745. {
  746. _log.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
  747. ScoreSaberData ppData = this._model.LevelIdToScoreSaberData[level.levelID];
  748. if (ppData.difficultyToSaberDifficulty.ContainsKey(difficultyString))
  749. {
  750. _log.Debug("Display pp for song.");
  751. float pp = ppData.difficultyToSaberDifficulty[difficultyString].pp;
  752. float star = ppData.difficultyToSaberDifficulty[difficultyString].star;
  753. _ppText.SetText(String.Format("{0:0.##}", pp));
  754. _starText.SetText(String.Format("{0:0.##}", star));
  755. }
  756. else
  757. {
  758. _ppText.SetText("?");
  759. _starText.SetText("?");
  760. }
  761. }
  762. else
  763. {
  764. _ppText.SetText("?");
  765. _starText.SetText("?");
  766. }
  767. }
  768. }
  769. /// <summary>
  770. /// Update interactive state of the quick scroll buttons.
  771. /// </summary>
  772. private void RefreshQuickScrollButtons()
  773. {
  774. // if you are ever viewing the song list with less than 5 songs the up/down buttons do not exist.
  775. // just try and fetch them and ignore the exception.
  776. if (_tableViewPageUpButton == null)
  777. {
  778. try
  779. {
  780. _tableViewPageUpButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageUpButton");
  781. (_tableViewPageUpButton.transform as RectTransform).anchoredPosition = new Vector2(0f, -1f);
  782. }
  783. catch (Exception)
  784. {
  785. // We don't care if this fails.
  786. return;
  787. }
  788. }
  789. if (_tableViewPageDownButton == null)
  790. {
  791. try
  792. {
  793. _tableViewPageDownButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageDownButton");
  794. (_tableViewPageDownButton.transform as RectTransform).anchoredPosition = new Vector2(0f, 1f);
  795. }
  796. catch (Exception)
  797. {
  798. // We don't care if this fails.
  799. return;
  800. }
  801. }
  802. // Refresh the fast scroll buttons
  803. if (_tableViewPageUpButton != null)
  804. {
  805. _pageUpFastButton.interactable = _tableViewPageUpButton.interactable;
  806. _pageUpFastButton.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  807. }
  808. if (_tableViewPageDownButton != null)
  809. {
  810. _pageDownFastButton.interactable = _tableViewPageDownButton.interactable;
  811. _pageDownFastButton.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  812. }
  813. }
  814. /// <summary>
  815. /// Helper to quickly refresh add to favorites button
  816. /// </summary>
  817. /// <param name="levelId"></param>
  818. private void RefreshAddFavoriteButton(String levelId)
  819. {
  820. if (levelId == null)
  821. {
  822. _currentAddFavoriteButtonSprite = null;
  823. }
  824. else
  825. {
  826. if (_model.Settings.Favorites.Contains(levelId))
  827. {
  828. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  829. }
  830. else
  831. {
  832. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  833. }
  834. }
  835. UIBuilder.SetButtonIcon(ref _addFavoriteButton, _currentAddFavoriteButtonSprite);
  836. }
  837. /// <summary>
  838. /// Adjust the UI colors.
  839. /// </summary>
  840. public void RefreshSortButtonUI()
  841. {
  842. // So far all we need to refresh is the sort buttons.
  843. foreach (SongSortButton sortButton in _sortButtonGroup)
  844. {
  845. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  846. if (sortButton.SortMode == _model.Settings.sortMode)
  847. {
  848. if (_model.InvertingResults)
  849. {
  850. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  851. }
  852. else
  853. {
  854. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  855. }
  856. }
  857. }
  858. // refresh filter buttons
  859. foreach (SongFilterButton filterButton in _filterButtonGroup)
  860. {
  861. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.clear);
  862. if (filterButton.FilterMode == _model.Settings.filterMode)
  863. {
  864. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.green);
  865. }
  866. }
  867. }
  868. /// <summary>
  869. /// Refresh the UI state of any directory buttons.
  870. /// </summary>
  871. public void RefreshDirectoryButtons()
  872. {
  873. // bail if no button, likely folder support not enabled.
  874. if (_upFolderButton == null)
  875. {
  876. return;
  877. }
  878. if (_model.DirStackSize > 1)
  879. {
  880. _upFolderButton.interactable = true;
  881. }
  882. else
  883. {
  884. _upFolderButton.interactable = false;
  885. }
  886. }
  887. /// <summary>
  888. /// Try to refresh the song list. Broken for now.
  889. /// </summary>
  890. public void RefreshSongList()
  891. {
  892. _log.Info("Refreshing the song list view.");
  893. try
  894. {
  895. if (_model.SortedSongList == null)
  896. {
  897. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  898. return;
  899. }
  900. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  901. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  902. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  903. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  904. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  905. tableView.ReloadData();
  906. String selectedLevelID = null;
  907. if (_model.LastSelectedLevelId != null)
  908. {
  909. selectedLevelID = _model.LastSelectedLevelId;
  910. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  911. }
  912. else
  913. {
  914. if (levels.Length > 0)
  915. {
  916. selectedLevelID = levels.FirstOrDefault().levelID;
  917. }
  918. }
  919. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  920. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  921. {
  922. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  923. }
  924. RefreshSortButtonUI();
  925. RefreshQuickScrollButtons();
  926. }
  927. catch (Exception e)
  928. {
  929. _log.Exception("Exception refreshing song list:", e);
  930. }
  931. }
  932. /// <summary>
  933. /// Scroll TableView to proper row, fire events.
  934. /// </summary>
  935. /// <param name="table"></param>
  936. /// <param name="levelID"></param>
  937. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  938. {
  939. // Check once per load
  940. if (!_checkedForTwitchPlugin)
  941. {
  942. _log.Info("Checking for BeatSaber Twitch Integration Plugin...");
  943. // Try to detect BeatSaber Twitch Integration Plugin
  944. _detectedTwitchPluginQueue = Resources.FindObjectsOfTypeAll<VRUIViewController>().Any(x => x.name == "RequestInfo");
  945. _log.Info("BeatSaber Twitch Integration plugin detected: " + _detectedTwitchPluginQueue);
  946. _checkedForTwitchPlugin = true;
  947. }
  948. // Skip scrolling to level if twitch plugin has queue active.
  949. if (_detectedTwitchPluginQueue)
  950. {
  951. _log.Debug("Skipping SelectAndScrollToLevel() because we detected Twitch Integrtion Plugin has a Queue active...");
  952. return;
  953. }
  954. int row = table.RowNumberForLevelID(levelID);
  955. TableView tableView = table.GetComponentInChildren<TableView>();
  956. tableView.SelectRow(row, true);
  957. tableView.ScrollToRow(row, true);
  958. _lastRow = row;
  959. }
  960. /// <summary>
  961. /// Helper for updating the model (which updates the song list)c
  962. /// </summary>
  963. public void UpdateSongList()
  964. {
  965. _log.Trace("UpdateSongList()");
  966. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  967. _model.UpdateSongLists(gameplayMode);
  968. this.RefreshDirectoryButtons();
  969. }
  970. /// <summary>
  971. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  972. /// </summary>
  973. public void LateUpdate()
  974. {
  975. CheckDebugUserInput();
  976. }
  977. /// <summary>
  978. /// Map some key presses directly to UI interactions to make testing easier.
  979. /// </summary>
  980. private void CheckDebugUserInput()
  981. {
  982. try
  983. {
  984. if (this._levelListViewController != null && this._levelListViewController.isActiveAndEnabled)
  985. {
  986. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  987. // back
  988. if (Input.GetKeyDown(KeyCode.Escape))
  989. {
  990. this._levelSelectionNavigationController.DismissButtonWasPressed();
  991. }
  992. // cycle sort modes
  993. if (Input.GetKeyDown(KeyCode.T))
  994. {
  995. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  996. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  997. }
  998. // select current sort mode again (toggle inverting)
  999. if (Input.GetKeyDown(KeyCode.Y))
  1000. {
  1001. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  1002. }
  1003. // filter playlists
  1004. if (Input.GetKeyDown(KeyCode.P))
  1005. {
  1006. OnPlaylistButtonClickEvent();
  1007. }
  1008. // filter search
  1009. if (Input.GetKeyDown(KeyCode.S))
  1010. {
  1011. OnSearchButtonClickEvent();
  1012. }
  1013. // filter favorites
  1014. if (Input.GetKeyDown(KeyCode.F))
  1015. {
  1016. OnFavoriteFilterButtonClickEvent();
  1017. }
  1018. // delete
  1019. if (Input.GetKeyDown(KeyCode.D))
  1020. {
  1021. _deleteButton.onClick.Invoke();
  1022. }
  1023. // c - select difficulty for top song
  1024. if (Input.GetKeyDown(KeyCode.C))
  1025. {
  1026. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  1027. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  1028. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  1029. }
  1030. // v start a song or enter a folder
  1031. if (Input.GetKeyDown(KeyCode.Return))
  1032. {
  1033. if (_playButton.isActiveAndEnabled)
  1034. {
  1035. _playButton.onClick.Invoke();
  1036. }
  1037. else if (_enterFolderButton.isActiveAndEnabled)
  1038. {
  1039. _enterFolderButton.onClick.Invoke();
  1040. }
  1041. }
  1042. // backspace - up a folder
  1043. if (Input.GetKeyDown(KeyCode.Backspace))
  1044. {
  1045. _upFolderButton.onClick.Invoke();
  1046. }
  1047. // change song index
  1048. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  1049. {
  1050. _pageUpFastButton.onClick.Invoke();
  1051. }
  1052. else if (Input.GetKeyDown(KeyCode.N))
  1053. {
  1054. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  1055. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  1056. }
  1057. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  1058. {
  1059. _pageDownFastButton.onClick.Invoke();
  1060. }
  1061. else if (Input.GetKeyDown(KeyCode.M))
  1062. {
  1063. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  1064. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  1065. }
  1066. // add to favorites
  1067. if (Input.GetKeyDown(KeyCode.KeypadPlus))
  1068. {
  1069. ToggleSongInFavorites();
  1070. }
  1071. }
  1072. else if (_deleteDialog != null && _deleteDialog.isInViewControllerHierarchy)
  1073. {
  1074. // accept delete
  1075. if (Input.GetKeyDown(KeyCode.Return))
  1076. {
  1077. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  1078. }
  1079. if (Input.GetKeyDown(KeyCode.Escape))
  1080. {
  1081. _deleteDialog.GetPrivateField<TextMeshProButton>("_cancelButton").button.onClick.Invoke();
  1082. }
  1083. }
  1084. }
  1085. catch (Exception e)
  1086. {
  1087. _log.Exception("Debug Input caused Exception: ", e);
  1088. }
  1089. }
  1090. }
  1091. }