SongBrowserUI.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  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. namespace SongBrowserPlugin.UI
  14. {
  15. /// <summary>
  16. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  17. /// </summary>
  18. public class SongBrowserUI : MonoBehaviour
  19. {
  20. // Logging
  21. public const String Name = "SongBrowserUI";
  22. private const float SEGMENT_PERCENT = 0.1f;
  23. private Logger _log = new Logger(Name);
  24. // Beat Saber UI Elements
  25. private StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
  26. private StandardLevelListViewController _levelListViewController;
  27. private StandardLevelDetailViewController _levelDetailViewController;
  28. private StandardLevelDifficultyViewController _levelDifficultyViewController;
  29. private StandardLevelSelectionNavigationController _levelSelectionNavigationController;
  30. private StandardLevelListTableView _levelListTableView;
  31. private RectTransform _tableViewRectTransform;
  32. private Button _tableViewPageUpButton;
  33. private Button _tableViewPageDownButton;
  34. private Button _playButton;
  35. // New UI Elements
  36. private List<SongSortButton> _sortButtonGroup;
  37. private Button _searchButton;
  38. private Button _addFavoriteButton;
  39. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  40. private SimpleDialogPromptViewController _deleteDialog;
  41. private Button _deleteButton;
  42. private Button _pageUpTenPercent;
  43. private Button _pageDownTenPercent;
  44. private Button _enterFolderButton;
  45. private Button _upFolderButton;
  46. private SearchKeyboardViewController _searchViewController;
  47. // Cached items
  48. private Sprite _addFavoriteSprite;
  49. private Sprite _removeFavoriteSprite;
  50. private Sprite _currentAddFavoriteButtonSprite;
  51. // Debug
  52. private int _sortButtonLastPushedIndex = 0;
  53. private int _lastRow = 0;
  54. // Model
  55. private SongBrowserModel _model;
  56. /// <summary>
  57. /// Constructor
  58. /// </summary>
  59. public SongBrowserUI() : base()
  60. {
  61. if (_model == null)
  62. {
  63. _model = new SongBrowserModel();
  64. }
  65. _model.Init();
  66. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  67. }
  68. /// <summary>
  69. /// Builds the UI for this plugin.
  70. /// </summary>
  71. public void CreateUI()
  72. {
  73. _log.Trace("CreateUI()");
  74. try
  75. {
  76. if (_levelSelectionFlowCoordinator == null)
  77. {
  78. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<StandardLevelSelectionFlowCoordinator>().First();
  79. }
  80. if (_levelListViewController == null)
  81. {
  82. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  83. }
  84. if (_levelDetailViewController == null)
  85. {
  86. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  87. }
  88. if (_levelSelectionNavigationController == null)
  89. {
  90. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelSelectionNavigationController>("_levelSelectionNavigationController");
  91. }
  92. if (_levelDifficultyViewController == null)
  93. {
  94. _levelDifficultyViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDifficultyViewController>("_levelDifficultyViewController");
  95. }
  96. if (_levelListTableView == null)
  97. {
  98. _levelListTableView = this._levelListViewController.GetComponentInChildren<StandardLevelListTableView>();
  99. }
  100. _playButton = _levelDetailViewController.GetComponentsInChildren<Button>().FirstOrDefault(x => x.name == "PlayButton");
  101. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  102. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  103. this._deleteDialog.gameObject.SetActive(false);
  104. this._addFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.AddToFavoritesIcon);
  105. this._removeFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.RemoveFromFavoritesIcon);
  106. this.CreateUIElements();
  107. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  108. }
  109. catch (Exception e)
  110. {
  111. _log.Exception("Exception during CreateUI: ", e);
  112. }
  113. }
  114. /// <summary>
  115. /// Builds the SongBrowser UI
  116. /// </summary>
  117. private void CreateUIElements()
  118. {
  119. _log.Trace("CreateUIElements");
  120. try
  121. {
  122. // Gather some transforms and templates to use.
  123. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  124. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  125. Button sortButtonTemplate = _playButton;
  126. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "QuitButton"));
  127. // Resize some of the UI
  128. _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
  129. _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
  130. _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  131. _tableViewPageUpButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageUpButton");
  132. (_tableViewPageUpButton.transform as RectTransform).anchoredPosition = new Vector2(0f, -1f);
  133. _tableViewPageDownButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageDownButton");
  134. (_tableViewPageDownButton.transform as RectTransform).anchoredPosition = new Vector2(0f, 1f);
  135. // Create Sorting Songs By-Buttons
  136. _log.Debug("Creating sort by buttons...");
  137. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  138. float fontSize = 2.35f;
  139. float buttonWidth = 13.5f;
  140. float buttonHeight = 5.0f;
  141. float buttonX = -61;
  142. float buttonY = 74.5f;
  143. string[] buttonNames = new string[]
  144. {
  145. "Favorite", "Song", "Author", "Original", "Newest", "Plays", "Difficult", "Random", "Playlist"
  146. };
  147. SongSortMode[] sortModes = new SongSortMode[]
  148. {
  149. SongSortMode.Favorites, SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.Difficulty, SongSortMode.Random, SongSortMode.Playlist
  150. };
  151. System.Action<SongSortMode>[] onClickEvents = new Action<SongSortMode>[]
  152. {
  153. onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onPlaylistButtonClickEvent
  154. };
  155. _sortButtonGroup = new List<SongSortButton>();
  156. for (int i = 0; i < buttonNames.Length; i++)
  157. {
  158. _sortButtonGroup.Add(UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  159. buttonNames[i],
  160. fontSize,
  161. buttonX + (buttonWidth * i),
  162. buttonY,
  163. buttonWidth,
  164. buttonHeight,
  165. sortModes[i],
  166. onClickEvents[i]));
  167. }
  168. // Create search button
  169. Vector2 searchButtonSize = new Vector2(5.5f, buttonHeight);
  170. float searchbuttonX = buttonX + (buttonWidth * (buttonNames.Length - 1)) + (buttonWidth / 2.0f) + 2.5f;
  171. Sprite searchSprite = Base64Sprites.Base64ToSprite(Base64Sprites.SearchIcon);
  172. _searchButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, searchSprite,
  173. new Vector2(searchbuttonX, buttonY),
  174. new Vector2(searchButtonSize.x, searchButtonSize.y),
  175. new Vector2(0, 0),
  176. new Vector2(3f, 3f),
  177. new Vector2(1.0f, 1.0f),
  178. 0.0f);
  179. _searchButton.onClick.AddListener(delegate()
  180. {
  181. onSearchButtonClickEvent(SongSortMode.Search);
  182. });
  183. buttonX += searchButtonSize.x;
  184. // Create Add to Favorites Button
  185. Vector2 addFavoritePos = new Vector2(40f, (sortButtonTemplate.transform as RectTransform).anchoredPosition.y);
  186. _addFavoriteButton = UIBuilder.CreateIconButton(otherButtonTransform, otherButtonTemplate, null,
  187. new Vector2(addFavoritePos.x, addFavoritePos.y),
  188. new Vector2(10.0f, 10.0f),
  189. new Vector2(2f, -1.5f),
  190. new Vector2(7.0f, 7.0f),
  191. new Vector2(1.0f, 1.0f),
  192. 0.0f);
  193. _addFavoriteButton.onClick.AddListener(delegate () {
  194. ToggleSongInFavorites();
  195. });
  196. if (_currentAddFavoriteButtonSprite == null)
  197. {
  198. IStandardLevel level = this._levelListViewController.selectedLevel;
  199. if (level != null)
  200. {
  201. RefreshAddFavoriteButton(level.levelID);
  202. }
  203. }
  204. // Create delete button
  205. _deleteButton = UIBuilder.CreateButton(otherButtonTransform, otherButtonTemplate, "Delete", fontSize, 46f, 0f, 15f, 5f);
  206. _deleteButton.onClick.AddListener(delegate () {
  207. HandleDeleteSelectedLevel();
  208. });
  209. // Create fast scroll buttons
  210. _pageUpTenPercent = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  211. new Vector2(15, 67.5f),
  212. new Vector2(6.0f, 5.5f),
  213. new Vector2(0f, 0f),
  214. new Vector2(1.5f, 1.5f),
  215. new Vector2(2.0f, 2.0f),
  216. 180);
  217. _pageUpTenPercent.onClick.AddListener(delegate () {
  218. this.JumpSongList(-1, SEGMENT_PERCENT);
  219. });
  220. _pageDownTenPercent = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  221. new Vector2(15, 0.5f),
  222. new Vector2(6.0f, 5.5f),
  223. new Vector2(0f, 0f),
  224. new Vector2(1.5f, 1.5f),
  225. new Vector2(2.0f, 2.0f),
  226. 0);
  227. _pageDownTenPercent.onClick.AddListener(delegate () {
  228. this.JumpSongList(1, SEGMENT_PERCENT);
  229. });
  230. // Create enter folder button
  231. if (_model.Settings.folderSupportEnabled)
  232. {
  233. _enterFolderButton = UIBuilder.CreateUIButton(otherButtonTransform, _playButton);
  234. _enterFolderButton.onClick.AddListener(delegate ()
  235. {
  236. _model.PushDirectory(_levelListViewController.selectedLevel);
  237. this.RefreshSongList();
  238. this.RefreshDirectoryButtons();
  239. });
  240. UIBuilder.SetButtonText(ref _enterFolderButton, "Enter");
  241. // Create up folder button
  242. _upFolderButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  243. new Vector2(buttonX - 4.0f, buttonY),
  244. new Vector2(5.5f, buttonHeight),
  245. new Vector2(0f, 0f),
  246. new Vector2(0.85f, 0.85f),
  247. new Vector2(2.0f, 2.0f),
  248. 180);
  249. _upFolderButton.onClick.RemoveAllListeners();
  250. _upFolderButton.onClick.AddListener(delegate ()
  251. {
  252. _model.PopDirectory();
  253. this.RefreshSongList();
  254. this.RefreshDirectoryButtons();
  255. });
  256. }
  257. RefreshSortButtonUI();
  258. RefreshDirectoryButtons();
  259. }
  260. catch (Exception e)
  261. {
  262. _log.Exception("Exception CreateUIElements:", e);
  263. }
  264. }
  265. /// <summary>
  266. /// Sort button clicked.
  267. /// </summary>
  268. private void onSortButtonClickEvent(SongSortMode sortMode)
  269. {
  270. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  271. _model.LastSelectedLevelId = null;
  272. if (_model.Settings.sortMode == sortMode)
  273. {
  274. _model.ToggleInverting();
  275. }
  276. _model.Settings.sortMode = sortMode;
  277. _model.Settings.Save();
  278. UpdateSongList();
  279. RefreshSongList();
  280. // Handle instant queue logic, avoid picking a folder.
  281. if (_model.Settings.sortMode == SongSortMode.Random && _model.Settings.randomInstantQueue)
  282. {
  283. for (int i = 0; i < _model.SortedSongList.Count; i++)
  284. {
  285. if (!_model.SortedSongList[i].levelID.StartsWith("Folder_"))
  286. {
  287. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[i].levelID);
  288. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, _model.SortedSongList[i].difficultyBeatmaps.Length-1);
  289. _playButton.onClick.Invoke();
  290. break;
  291. }
  292. }
  293. }
  294. }
  295. /// <summary>
  296. /// Search button clicked.
  297. /// </summary>
  298. /// <param name="sortMode"></param>
  299. private void onSearchButtonClickEvent(SongSortMode sortMode)
  300. {
  301. _model.Settings.sortMode = sortMode;
  302. _model.Settings.Save();
  303. this.ShowSearchKeyboard();
  304. }
  305. /// <summary>
  306. /// Display the playlist selector.
  307. /// </summary>
  308. /// <param name="sortMode"></param>
  309. private void onPlaylistButtonClickEvent(SongSortMode sortMode)
  310. {
  311. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  312. _model.LastSelectedLevelId = null;
  313. PlaylistFlowCoordinator view = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  314. view.didSelectPlaylist += HandleDidSelectPlaylist;
  315. view.Present(_levelSelectionNavigationController);
  316. }
  317. /// <summary>
  318. /// Adjust UI based on level selected.
  319. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  320. /// </summary>
  321. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  322. {
  323. try
  324. {
  325. _log.Trace("OnDidSelectLevelEvent()");
  326. if (level == null)
  327. {
  328. _log.Debug("No level selected?");
  329. return;
  330. }
  331. if (_model.Settings == null)
  332. {
  333. _log.Debug("Settings not instantiated yet?");
  334. return;
  335. }
  336. _model.LastSelectedLevelId = level.levelID;
  337. RefreshAddFavoriteButton(level.levelID);
  338. RefreshQuickScrollButtons();
  339. if (level.levelID.StartsWith("Folder_"))
  340. {
  341. _log.Debug("Folder selected! Adjust PlayButton logic...");
  342. HandleDidSelectFolderRow(level);
  343. }
  344. else
  345. {
  346. HandleDidSelectLevelRow(level);
  347. }
  348. }
  349. catch (Exception e)
  350. {
  351. _log.Exception("Exception selecting song:", e);
  352. }
  353. }
  354. /// <summary>
  355. ///
  356. /// </summary>
  357. private void HandleDidSelectFolderRow(IStandardLevel level)
  358. {
  359. _enterFolderButton.gameObject.SetActive(true);
  360. _playButton.gameObject.SetActive(false);
  361. }
  362. /// <summary>
  363. ///
  364. /// </summary>
  365. /// <param name="level"></param>
  366. private void HandleDidSelectLevelRow(IStandardLevel level)
  367. {
  368. if (_enterFolderButton != null)
  369. {
  370. _enterFolderButton.gameObject.SetActive(false);
  371. }
  372. _playButton.gameObject.SetActive(true);
  373. }
  374. /// <summary>
  375. /// Pop up a delete dialog.
  376. /// </summary>
  377. private void HandleDeleteSelectedLevel()
  378. {
  379. IStandardLevel level = this._levelListViewController.selectedLevel;
  380. if (level == null)
  381. {
  382. _log.Info("No level selected, cannot delete nothing...");
  383. return;
  384. }
  385. if (level.levelID.StartsWith("Level"))
  386. {
  387. _log.Debug("Cannot delete non-custom levels.");
  388. return;
  389. }
  390. if (level.levelID.StartsWith("Folder"))
  391. {
  392. _log.Debug("Cannot delete folders.");
  393. return;
  394. }
  395. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  396. 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");
  397. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  398. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  399. }
  400. /// <summary>
  401. /// Handle delete dialog resolution.
  402. /// </summary>
  403. /// <param name="viewController"></param>
  404. /// <param name="ok"></param>
  405. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  406. {
  407. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  408. if (!ok)
  409. {
  410. viewController.DismissModalViewController(null, false);
  411. }
  412. else
  413. {
  414. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  415. IStandardLevel level = this._levelListViewController.selectedLevel;
  416. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  417. string songPath = customLevel.customSongInfo.path;
  418. bool isZippedSong = false;
  419. viewController.DismissModalViewController(null, false);
  420. _log.Debug("Deleting: {0}", songPath);
  421. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  422. {
  423. isZippedSong = true;
  424. }
  425. if (isZippedSong)
  426. {
  427. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  428. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  429. Directory.Delete(songHashDir.FullName, true);
  430. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  431. {
  432. string hash = CreateMD5FromFile(file);
  433. if (hash != null)
  434. {
  435. if (hash == songHashDir.Name)
  436. {
  437. _log.Debug("Deleting zipped song: {0}", file);
  438. File.Delete(file);
  439. break;
  440. }
  441. }
  442. }
  443. }
  444. else
  445. {
  446. FileAttributes attr = File.GetAttributes(songPath);
  447. if (attr.HasFlag(FileAttributes.Directory))
  448. {
  449. _log.Debug("Deleting song: {0}", songPath);
  450. Directory.Delete(songPath, true);
  451. }
  452. }
  453. _model.LastSelectedLevelId = null;
  454. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  455. this.UpdateSongList();
  456. this.RefreshSongList();
  457. }
  458. }
  459. /// <summary>
  460. /// Create MD5 of a file.
  461. /// </summary>
  462. /// <param name="path"></param>
  463. /// <returns></returns>
  464. public static string CreateMD5FromFile(string path)
  465. {
  466. string hash = "";
  467. if (!File.Exists(path)) return null;
  468. using (MD5 md5 = MD5.Create())
  469. {
  470. using (var stream = File.OpenRead(path))
  471. {
  472. byte[] hashBytes = md5.ComputeHash(stream);
  473. StringBuilder sb = new StringBuilder();
  474. foreach (byte hashByte in hashBytes)
  475. {
  476. sb.Append(hashByte.ToString("X2"));
  477. }
  478. hash = sb.ToString();
  479. return hash;
  480. }
  481. }
  482. }
  483. /// <summary>
  484. /// Handle selection of a playlist. Show just the songs in the playlist.
  485. /// </summary>
  486. /// <param name="p"></param>
  487. private void HandleDidSelectPlaylist(Playlist p)
  488. {
  489. _log.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  490. _model.Settings.sortMode = SongSortMode.Playlist;
  491. _model.CurrentPlaylist = p;
  492. _model.Settings.Save();
  493. this.UpdateSongList();
  494. this.RefreshSongList();
  495. }
  496. /// <summary>
  497. /// Display the search keyboard
  498. /// </summary>
  499. void ShowSearchKeyboard()
  500. {
  501. if (_searchViewController == null)
  502. {
  503. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  504. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  505. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  506. }
  507. _log.Debug("Presenting keyboard");
  508. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  509. }
  510. /// <summary>
  511. /// Handle back button event from search keyboard.
  512. /// </summary>
  513. private void SearchViewControllerbackButtonPressed()
  514. {
  515. }
  516. /// <summary>
  517. /// Handle search.
  518. /// </summary>
  519. /// <param name="searchFor"></param>
  520. private void SearchViewControllerSearchButtonPressed(string searchFor)
  521. {
  522. _log.Debug("Searching for \"{0}\"...", searchFor);
  523. _model.Settings.searchTerms.Insert(0, searchFor);
  524. _model.Settings.Save();
  525. _model.LastSelectedLevelId = null;
  526. this.UpdateSongList();
  527. this.RefreshSongList();
  528. }
  529. /// <summary>
  530. /// Make big jumps in the song list.
  531. /// </summary>
  532. /// <param name="numJumps"></param>
  533. private void JumpSongList(int numJumps, float segmentPercent)
  534. {
  535. int totalSize = _model.SortedSongList.Count;
  536. int segmentSize = (int)(totalSize * segmentPercent);
  537. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  538. HashSet<int> rows = tableView.GetPrivateField<HashSet<int>>("_selectedRows");
  539. int listSegment = (rows.First() / segmentSize);
  540. int newSegment = listSegment + numJumps;
  541. int newRow = 0;
  542. if (newSegment > 0)
  543. {
  544. newRow = Math.Min(newSegment * segmentSize, totalSize - 1);
  545. }
  546. _log.Debug("ListSegment: {0}, newRow: {1}", listSegment, newRow);
  547. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  548. }
  549. /// <summary>
  550. /// Add/Remove song from favorites depending on if it already exists.
  551. /// </summary>
  552. private void ToggleSongInFavorites()
  553. {
  554. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  555. if (_model.Settings.favorites.Contains(songInfo.levelID))
  556. {
  557. _log.Info("Remove {0} from favorites", songInfo.songName);
  558. _model.Settings.favorites.Remove(songInfo.levelID);
  559. }
  560. else
  561. {
  562. _log.Info("Add {0} to favorites", songInfo.songName);
  563. _model.Settings.favorites.Add(songInfo.levelID);
  564. }
  565. RefreshAddFavoriteButton(songInfo.levelID);
  566. _model.Settings.Save();
  567. }
  568. /// <summary>
  569. /// Update interactive state of the quick scroll buttons.
  570. /// </summary>
  571. private void RefreshQuickScrollButtons()
  572. {
  573. // Refresh the fast scroll buttons
  574. _pageUpTenPercent.interactable = _tableViewPageUpButton.interactable;
  575. _pageUpTenPercent.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  576. _pageDownTenPercent.interactable = _tableViewPageDownButton.interactable;
  577. _pageDownTenPercent.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  578. }
  579. /// <summary>
  580. /// Helper to quickly refresh add to favorites button
  581. /// </summary>
  582. /// <param name="levelId"></param>
  583. private void RefreshAddFavoriteButton(String levelId)
  584. {
  585. if (levelId == null)
  586. {
  587. _currentAddFavoriteButtonSprite = null;
  588. }
  589. else
  590. {
  591. if (_model.Settings.favorites.Contains(levelId))
  592. {
  593. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  594. }
  595. else
  596. {
  597. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  598. }
  599. }
  600. UIBuilder.SetButtonIcon(ref _addFavoriteButton, _currentAddFavoriteButtonSprite);
  601. }
  602. /// <summary>
  603. /// Adjust the UI colors.
  604. /// </summary>
  605. public void RefreshSortButtonUI()
  606. {
  607. // So far all we need to refresh is the sort buttons.
  608. foreach (SongSortButton sortButton in _sortButtonGroup)
  609. {
  610. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  611. if (sortButton.SortMode == _model.Settings.sortMode)
  612. {
  613. if (_model.InvertingResults)
  614. {
  615. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  616. }
  617. else
  618. {
  619. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  620. }
  621. }
  622. }
  623. if (_model.Settings.sortMode == SongSortMode.Search)
  624. {
  625. UIBuilder.SetButtonBorder(ref _searchButton, Color.green);
  626. }
  627. else
  628. {
  629. UIBuilder.SetButtonBorder(ref _searchButton, Color.clear);
  630. }
  631. }
  632. /// <summary>
  633. /// Refresh the UI state of any directory buttons.
  634. /// </summary>
  635. public void RefreshDirectoryButtons()
  636. {
  637. // bail if no button, likely folder support not enabled.
  638. if (_upFolderButton == null)
  639. {
  640. return;
  641. }
  642. if (_model.DirStackSize > 1)
  643. {
  644. _upFolderButton.interactable = true;
  645. }
  646. else
  647. {
  648. _upFolderButton.interactable = false;
  649. }
  650. }
  651. /// <summary>
  652. /// Try to refresh the song list. Broken for now.
  653. /// </summary>
  654. public void RefreshSongList()
  655. {
  656. _log.Info("Refreshing the song list view.");
  657. try
  658. {
  659. if (_model.SortedSongList == null)
  660. {
  661. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  662. return;
  663. }
  664. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  665. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  666. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  667. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  668. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  669. tableView.ReloadData();
  670. String selectedLevelID = null;
  671. if (_model.LastSelectedLevelId != null)
  672. {
  673. selectedLevelID = _model.LastSelectedLevelId;
  674. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  675. }
  676. else
  677. {
  678. if (levels.Length > 0)
  679. {
  680. selectedLevelID = levels.FirstOrDefault().levelID;
  681. }
  682. }
  683. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  684. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  685. {
  686. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  687. }
  688. RefreshSortButtonUI();
  689. }
  690. catch (Exception e)
  691. {
  692. _log.Exception("Exception refreshing song list:", e);
  693. }
  694. }
  695. /// <summary>
  696. /// Scroll TableView to proper row, fire events.
  697. /// </summary>
  698. /// <param name="table"></param>
  699. /// <param name="levelID"></param>
  700. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  701. {
  702. int row = table.RowNumberForLevelID(levelID);
  703. TableView tableView = table.GetComponentInChildren<TableView>();
  704. tableView.SelectRow(row, true);
  705. tableView.ScrollToRow(row, true);
  706. _lastRow = row;
  707. }
  708. /// <summary>
  709. /// Helper for updating the model (which updates the song list)c
  710. /// </summary>
  711. public void UpdateSongList()
  712. {
  713. _log.Trace("UpdateSongList()");
  714. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  715. _model.UpdateSongLists(gameplayMode);
  716. this.RefreshDirectoryButtons();
  717. }
  718. /// <summary>
  719. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  720. /// </summary>
  721. public void LateUpdate()
  722. {
  723. CheckDebugUserInput();
  724. }
  725. /// <summary>
  726. /// Map some key presses directly to UI interactions to make testing easier.
  727. /// </summary>
  728. private void CheckDebugUserInput()
  729. {
  730. try
  731. {
  732. if (this._levelListViewController.isActiveAndEnabled)
  733. {
  734. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  735. // back
  736. if (Input.GetKeyDown(KeyCode.Escape))
  737. {
  738. this._levelSelectionNavigationController.DismissButtonWasPressed();
  739. }
  740. // cycle sort modes
  741. if (Input.GetKeyDown(KeyCode.T))
  742. {
  743. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  744. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  745. }
  746. if (Input.GetKeyDown(KeyCode.S))
  747. {
  748. onSearchButtonClickEvent(SongSortMode.Search);
  749. }
  750. // select current sort mode again (toggle inverting)
  751. if (Input.GetKeyDown(KeyCode.Y))
  752. {
  753. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  754. }
  755. // playlists
  756. if (Input.GetKeyDown(KeyCode.P))
  757. {
  758. _sortButtonGroup[_sortButtonGroup.Count - 2].Button.onClick.Invoke();
  759. }
  760. // delete
  761. if (Input.GetKeyDown(KeyCode.D))
  762. {
  763. _deleteButton.onClick.Invoke();
  764. }
  765. // c - select difficulty for top song
  766. if (Input.GetKeyDown(KeyCode.C))
  767. {
  768. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  769. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  770. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  771. }
  772. // v start a song or enter a folder
  773. if (Input.GetKeyDown(KeyCode.Return))
  774. {
  775. if (_playButton.isActiveAndEnabled)
  776. {
  777. _playButton.onClick.Invoke();
  778. }
  779. else if (_enterFolderButton.isActiveAndEnabled)
  780. {
  781. _enterFolderButton.onClick.Invoke();
  782. }
  783. }
  784. // backspace - up a folder
  785. if (Input.GetKeyDown(KeyCode.Backspace))
  786. {
  787. _upFolderButton.onClick.Invoke();
  788. }
  789. // change song index
  790. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  791. {
  792. _pageUpTenPercent.onClick.Invoke();
  793. }
  794. else if (Input.GetKeyDown(KeyCode.N))
  795. {
  796. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  797. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  798. }
  799. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  800. {
  801. _pageDownTenPercent.onClick.Invoke();
  802. }
  803. else if (Input.GetKeyDown(KeyCode.M))
  804. {
  805. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  806. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  807. }
  808. // add to favorites
  809. if (Input.GetKeyDown(KeyCode.F))
  810. {
  811. ToggleSongInFavorites();
  812. }
  813. }
  814. else if (_deleteDialog.isInViewControllerHierarchy)
  815. {
  816. // accept delete
  817. if (Input.GetKeyDown(KeyCode.Return))
  818. {
  819. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  820. }
  821. if (Input.GetKeyDown(KeyCode.Escape))
  822. {
  823. _deleteDialog.GetPrivateField<TextMeshProButton>("_cancelButton").button.onClick.Invoke();
  824. }
  825. }
  826. }
  827. catch (Exception e)
  828. {
  829. _log.Exception("Debug Input caused Exception: ", e);
  830. }
  831. }
  832. }
  833. }