SongBrowserUI.cs 72 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764
  1. using UnityEngine;
  2. using System.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using UnityEngine.UI;
  7. using HMUI;
  8. using VRUI;
  9. using SongBrowser.DataAccess;
  10. using System.IO;
  11. using SongLoaderPlugin;
  12. using System.Security.Cryptography;
  13. using System.Text;
  14. using TMPro;
  15. using Logger = SongBrowser.Logging.Logger;
  16. using SongBrowser.DataAccess.BeatSaverApi;
  17. using System.Collections;
  18. namespace SongBrowser.UI
  19. {
  20. /// <summary>
  21. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  22. /// </summary>
  23. public class SongBrowserUI : MonoBehaviour
  24. {
  25. // Logging
  26. public const String Name = "SongBrowserUI";
  27. private const float SEGMENT_PERCENT = 0.1f;
  28. private const int LIST_ITEMS_VISIBLE_AT_ONCE = 6;
  29. // Beat Saber UI Elements
  30. private FlowCoordinator _levelSelectionFlowCoordinator;
  31. private LevelPacksViewController _levelPackViewController;
  32. private LevelPacksTableView _levelPacksTableView;
  33. private LevelPackDetailViewController _levelPackDetailViewController;
  34. private LevelPackLevelsViewController _levelPackLevelsViewController;
  35. private LevelPackLevelsTableView _levelPackLevelsTableView;
  36. private StandardLevelDetailViewController _levelDetailViewController;
  37. private StandardLevelDetailView _standardLevelDetailView;
  38. private BeatmapDifficultySegmentedControlController _levelDifficultyViewController;
  39. private BeatmapCharacteristicSegmentedControlController _beatmapCharacteristicSelectionViewController;
  40. private DismissableNavigationController _levelSelectionNavigationController;
  41. private RectTransform _levelPackLevelsTableViewRectTransform;
  42. private Button _tableViewPageUpButton;
  43. private Button _tableViewPageDownButton;
  44. private Button _playButton;
  45. // New UI Elements
  46. private List<SongSortButton> _sortButtonGroup;
  47. private List<SongFilterButton> _filterButtonGroup;
  48. private Button _clearSortFilterButton;
  49. private Button _addFavoriteButton;
  50. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  51. private SimpleDialogPromptViewController _deleteDialog;
  52. private Button _deleteButton;
  53. private Button _pageUpFastButton;
  54. private Button _pageDownFastButton;
  55. private SearchKeyboardViewController _searchViewController;
  56. private PlaylistFlowCoordinator _playListFlowCoordinator;
  57. private RectTransform _ppStatButton;
  58. private RectTransform _starStatButton;
  59. private RectTransform _njsStatButton;
  60. // Cached items
  61. private Sprite _addFavoriteSprite;
  62. private Sprite _removeFavoriteSprite;
  63. private Sprite _currentAddFavoriteButtonSprite;
  64. // Plugin Compat checks
  65. private bool _detectedTwitchPluginQueue = false;
  66. private bool _checkedForTwitchPlugin = false;
  67. // Debug
  68. private int _sortButtonLastPushedIndex = 0;
  69. private int _lastRow = 0;
  70. // Model
  71. private SongBrowserModel _model;
  72. // UI Created
  73. private bool _uiCreated = false;
  74. public SongBrowserModel Model
  75. {
  76. get
  77. {
  78. return _model;
  79. }
  80. }
  81. /// <summary>
  82. /// Constructor
  83. /// </summary>
  84. public SongBrowserUI() : base()
  85. {
  86. if (_model == null)
  87. {
  88. _model = new SongBrowserModel();
  89. }
  90. _model.Init();
  91. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  92. }
  93. /// <summary>
  94. /// Builds the UI for this plugin.
  95. /// </summary>
  96. public void CreateUI(MainMenuViewController.MenuButton mode)
  97. {
  98. Logger.Trace("CreateUI()");
  99. // Determine the flow controller to use
  100. if (mode == MainMenuViewController.MenuButton.SoloFreePlay)
  101. {
  102. Logger.Debug("Entering SOLO mode...");
  103. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<SoloFreePlayFlowCoordinator>().First();
  104. }
  105. else if (mode == MainMenuViewController.MenuButton.Party)
  106. {
  107. Logger.Debug("Entering PARTY mode...");
  108. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<PartyFreePlayFlowCoordinator>().First();
  109. }
  110. else
  111. {
  112. Logger.Debug("Entering SOLO CAMPAIGN mode...");
  113. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<CampaignFlowCoordinator>().First();
  114. }
  115. // returning to the menu and switching modes could trigger this.
  116. if (_uiCreated)
  117. {
  118. return;
  119. }
  120. try
  121. {
  122. // gather controllers and ui elements.
  123. _levelPackViewController = _levelSelectionFlowCoordinator.GetPrivateField<LevelPacksViewController>("_levelPacksViewController");
  124. Logger.Debug("Acquired LevelPacksViewController [{0}]", _levelPackViewController.GetInstanceID());
  125. _levelPackDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<LevelPackDetailViewController>("_levelPackDetailViewController");
  126. Logger.Debug("Acquired LevelPackDetailViewController [{0}]", _levelPackDetailViewController.GetInstanceID());
  127. _levelPacksTableView = _levelPackViewController.GetPrivateField<LevelPacksTableView>("_levelPacksTableView");
  128. Logger.Debug("Acquired LevelPacksTableView [{0}]", _levelPacksTableView.GetInstanceID());
  129. _levelPackLevelsViewController = _levelSelectionFlowCoordinator.GetPrivateField<LevelPackLevelsViewController>("_levelPackLevelsViewController");
  130. Logger.Debug("Acquired LevelPackLevelsViewController [{0}]", _levelPackLevelsViewController.GetInstanceID());
  131. _levelPackLevelsTableView = this._levelPackLevelsViewController.GetPrivateField<LevelPackLevelsTableView>("_levelPackLevelsTableView");
  132. Logger.Debug("Acquired LevelPackLevelsTableView [{0}]", _levelPackLevelsTableView.GetInstanceID());
  133. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  134. Logger.Debug("Acquired StandardLevelDetailViewController [{0}]", _levelDetailViewController.GetInstanceID());
  135. _standardLevelDetailView = _levelDetailViewController.GetPrivateField<StandardLevelDetailView>("_standardLevelDetailView");
  136. Logger.Debug("Acquired StandardLevelDetailView [{0}]", _standardLevelDetailView.GetInstanceID());
  137. _beatmapCharacteristicSelectionViewController = Resources.FindObjectsOfTypeAll<BeatmapCharacteristicSegmentedControlController>().First();
  138. Logger.Debug("Acquired BeatmapCharacteristicSegmentedControlController [{0}]", _beatmapCharacteristicSelectionViewController.GetInstanceID());
  139. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<DismissableNavigationController>("_navigationController");
  140. Logger.Debug("Acquired DismissableNavigationController [{0}]", _levelSelectionNavigationController.GetInstanceID());
  141. _levelDifficultyViewController = _standardLevelDetailView.GetPrivateField<BeatmapDifficultySegmentedControlController>("_beatmapDifficultySegmentedControlController");
  142. Logger.Debug("Acquired BeatmapDifficultySegmentedControlController [{0}]", _levelDifficultyViewController.GetInstanceID());
  143. _levelPackLevelsTableViewRectTransform = _levelPackLevelsTableView.transform as RectTransform;
  144. Logger.Debug("Acquired TableViewRectTransform from LevelPackLevelsTableView [{0}]", _levelPackLevelsTableViewRectTransform.GetInstanceID());
  145. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelPackLevelsTableView, "_tableView");
  146. _tableViewPageUpButton = tableView.GetPrivateField<Button>("_pageUpButton");
  147. _tableViewPageDownButton = tableView.GetPrivateField<Button>("_pageDownButton");
  148. Logger.Debug("Acquired Page Up and Down buttons...");
  149. _playButton = _standardLevelDetailView.GetComponentsInChildren<Button>().FirstOrDefault(x => x.name == "PlayButton");
  150. Logger.Debug("Acquired PlayButton [{0}]", _playButton.GetInstanceID());
  151. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  152. if (_playListFlowCoordinator == null)
  153. {
  154. _playListFlowCoordinator = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  155. _playListFlowCoordinator.didFinishEvent += HandleDidSelectPlaylist;
  156. }
  157. // delete dialog
  158. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  159. this._deleteDialog.name = "DeleteDialogPromptViewController";
  160. this._deleteDialog.gameObject.SetActive(false);
  161. // sprites
  162. this._addFavoriteSprite = Base64Sprites.AddToFavoritesIcon;
  163. this._removeFavoriteSprite = Base64Sprites.RemoveFromFavoritesIcon;
  164. // create song browser main ui
  165. this.CreateUIElements();
  166. this.InstallHandlers();
  167. this.ResizeStatsPanel();
  168. this.ResizeSongUI();
  169. _uiCreated = true;
  170. Logger.Debug("Done Creating UI...");
  171. }
  172. catch (Exception e)
  173. {
  174. Logger.Exception("Exception during CreateUI: ", e);
  175. }
  176. }
  177. /// <summary>
  178. /// Builds the SongBrowser UI
  179. /// </summary>
  180. private void CreateUIElements()
  181. {
  182. Logger.Trace("CreateUIElements");
  183. try
  184. {
  185. // Gather some transforms and templates to use.
  186. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  187. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  188. Button sortButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "SettingsButton"));
  189. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "SettingsButton"));
  190. RectTransform playContainerRect = _standardLevelDetailView.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayContainer");
  191. RectTransform playButtonsRect = playContainerRect.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayButtons");
  192. Button practiceButton = playButtonsRect.GetComponentsInChildren<Button>().First(x => x.name == "PracticeButton");
  193. RectTransform practiceButtonRect = (practiceButton.transform as RectTransform);
  194. Button playButton = Resources.FindObjectsOfTypeAll<Button>().First(x => x.name == "PlayButton");
  195. RectTransform playButtonRect = (playButton.transform as RectTransform);
  196. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  197. Sprite borderSprite = SongBrowserApplication.Instance.CachedIcons["RoundRectBigStroke"];
  198. // Create Sorting Songs By-Buttons
  199. Logger.Debug("Creating sort by buttons...");
  200. float buttonSpacing = 0.5f;
  201. float fontSize = 2.0f;
  202. float buttonWidth = 12.25f;
  203. float buttonHeight = 5.0f;
  204. float startButtonX = 24.50f;
  205. float curButtonX = 0.0f;
  206. float buttonY = -5.25f;
  207. Vector2 iconButtonSize = new Vector2(buttonHeight, buttonHeight);
  208. // Create cancel button
  209. Logger.Debug("Creating cancel button...");
  210. _clearSortFilterButton = UIBuilder.CreateIconButton(
  211. sortButtonTransform,
  212. otherButtonTemplate,
  213. Base64Sprites.XIcon,
  214. new Vector2(startButtonX - buttonHeight, buttonY),
  215. new Vector2(iconButtonSize.x, iconButtonSize.y),
  216. new Vector2(3.5f, 3.5f),
  217. new Vector2(1.0f, 1.0f),
  218. 0);
  219. _clearSortFilterButton.onClick.RemoveAllListeners();
  220. _clearSortFilterButton.onClick.AddListener(delegate () {
  221. OnClearButtonClickEvent();
  222. });
  223. startButtonX += (buttonHeight * 2.0f);
  224. // define sort buttons
  225. string[] sortButtonNames = new string[]
  226. {
  227. "Song", "Author", "Newest", "Plays", "PP", "Difficult", "Random"
  228. };
  229. SongSortMode[] sortModes = new SongSortMode[]
  230. {
  231. SongSortMode.Default, SongSortMode.Author, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.PP, SongSortMode.Difficulty, SongSortMode.Random
  232. };
  233. _sortButtonGroup = new List<SongSortButton>();
  234. for (int i = 0; i < sortButtonNames.Length; i++)
  235. {
  236. curButtonX = startButtonX + (buttonWidth*i) + (buttonSpacing*i);
  237. SongSortButton newButton = UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon, borderSprite,
  238. sortButtonNames[i],
  239. fontSize,
  240. curButtonX,
  241. buttonY,
  242. buttonWidth,
  243. buttonHeight,
  244. sortModes[i],
  245. OnSortButtonClickEvent);
  246. _sortButtonGroup.Add(newButton);
  247. newButton.Button.name = "Sort" + sortModes[i].ToString() + "Button";
  248. }
  249. // Create filter buttons
  250. Logger.Debug("Creating filter buttons...");
  251. float filterButtonX = curButtonX + (buttonWidth / 2.0f);
  252. List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>> filterButtonSetup = new List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>>()
  253. {
  254. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Favorites, OnFavoriteFilterButtonClickEvent, Base64Sprites.StarFullIcon),
  255. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Playlist, OnPlaylistButtonClickEvent, Base64Sprites.PlaylistIcon),
  256. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Search, OnSearchButtonClickEvent, Base64Sprites.SearchIcon),
  257. };
  258. _filterButtonGroup = new List<SongFilterButton>();
  259. for (int i = 0; i < filterButtonSetup.Count; i++)
  260. {
  261. Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite> t = filterButtonSetup[i];
  262. Button b = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate,
  263. t.Item3,
  264. new Vector2(filterButtonX + (iconButtonSize.x * i) + (buttonSpacing * i), buttonY),
  265. new Vector2(iconButtonSize.x, iconButtonSize.y),
  266. new Vector2(3.5f, 3.5f),
  267. new Vector2(1.0f, 1.0f),
  268. 0);
  269. SongFilterButton filterButton = new SongFilterButton
  270. {
  271. Button = b,
  272. FilterMode = t.Item1
  273. };
  274. b.onClick.AddListener(t.Item2);
  275. filterButton.Button.name = "Filter" + t.Item1.ToString() + "Button";
  276. _filterButtonGroup.Add(filterButton);
  277. }
  278. // Create add favorite button
  279. Logger.Debug("Creating Add to favorites button...");
  280. _addFavoriteButton = UIBuilder.CreateIconButton(playButtonsRect,
  281. practiceButton,
  282. Base64Sprites.AddToFavoritesIcon
  283. );
  284. _addFavoriteButton.onClick.RemoveAllListeners();
  285. _addFavoriteButton.onClick.AddListener(delegate () {
  286. ToggleSongInPlaylist();
  287. });
  288. // Create delete button
  289. Logger.Debug("Creating delete button...");
  290. _deleteButton = UIBuilder.CreateIconButton(playButtonsRect,
  291. practiceButton,
  292. Base64Sprites.DeleteIcon
  293. );
  294. _deleteButton.onClick.RemoveAllListeners();
  295. _deleteButton.onClick.AddListener(delegate () {
  296. HandleDeleteSelectedLevel();
  297. });
  298. // Create fast scroll buttons
  299. int pageFastButtonX = 25;
  300. Vector2 pageFastSize = new Vector2(12.5f, 7.75f);
  301. Vector2 pageFastIconSize = new Vector2(0.1f, 0.1f);
  302. Vector2 pageFastIconScale = new Vector2(0.4f, 0.4f);
  303. Logger.Debug("Creating fast scroll button...");
  304. _pageUpFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  305. new Vector2(pageFastButtonX, -13f),
  306. pageFastSize,
  307. pageFastIconSize,
  308. pageFastIconScale,
  309. 180);
  310. UnityEngine.GameObject.Destroy(_pageUpFastButton.GetComponentsInChildren<UnityEngine.UI.Image>().First(btn => btn.name == "Stroke"));
  311. _pageUpFastButton.onClick.AddListener(delegate ()
  312. {
  313. this.JumpSongList(-1, SEGMENT_PERCENT);
  314. });
  315. _pageDownFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  316. new Vector2(pageFastButtonX, -80f),
  317. pageFastSize,
  318. pageFastIconSize,
  319. pageFastIconScale,
  320. 0);
  321. UnityEngine.GameObject.Destroy(_pageDownFastButton.GetComponentsInChildren<UnityEngine.UI.Image>().First(btn => btn.name == "Stroke"));
  322. _pageDownFastButton.onClick.AddListener(delegate ()
  323. {
  324. this.JumpSongList(1, SEGMENT_PERCENT);
  325. });
  326. RefreshSortButtonUI();
  327. Logger.Debug("Done Creating UIElements");
  328. }
  329. catch (Exception e)
  330. {
  331. Logger.Exception("Exception CreateUIElements:", e);
  332. }
  333. }
  334. /// <summary>
  335. /// Resize the stats panel to fit more stats.
  336. /// </summary>
  337. private void ResizeStatsPanel()
  338. {
  339. // modify details view
  340. Logger.Debug("Resizing Stats Panel...");
  341. var statsPanel = _standardLevelDetailView.GetPrivateField<LevelParamsPanel>("_levelParamsPanel");
  342. var statTransforms = statsPanel.GetComponentsInChildren<RectTransform>();
  343. var valueTexts = statsPanel.GetComponentsInChildren<TextMeshProUGUI>().Where(x => x.name == "ValueText").ToList();
  344. RectTransform panelRect = (statsPanel.transform as RectTransform);
  345. panelRect.sizeDelta = new Vector2(panelRect.sizeDelta.x * 1.2f, panelRect.sizeDelta.y * 1.2f);
  346. for (int i = 0; i < statTransforms.Length; i++)
  347. {
  348. var r = statTransforms[i];
  349. if (r.name == "Separator")
  350. {
  351. continue;
  352. }
  353. r.sizeDelta = new Vector2(r.sizeDelta.x * 0.75f, r.sizeDelta.y * 0.75f);
  354. }
  355. for (int i = 0; i < valueTexts.Count; i++)
  356. {
  357. var text = valueTexts[i];
  358. text.fontSize = 3.25f;
  359. }
  360. _ppStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  361. UIBuilder.SetStatButtonIcon(_ppStatButton, Base64Sprites.GraphIcon);
  362. _starStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  363. UIBuilder.SetStatButtonIcon(_starStatButton, Base64Sprites.StarIcon);
  364. _njsStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  365. UIBuilder.SetStatButtonIcon(_njsStatButton, Base64Sprites.SpeedIcon);
  366. // shrink title
  367. var titleText = this._levelDetailViewController.GetComponentsInChildren<TextMeshProUGUI>(true).First(x => x.name == "SongNameText");
  368. titleText.fontSize = 5.0f;
  369. }
  370. /// <summary>
  371. /// Resize some of the song table elements.
  372. /// </summary>
  373. public void ResizeSongUI()
  374. {
  375. // Reposition the table view a bit
  376. _levelPackLevelsTableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  377. // Move the page up/down buttons a bit
  378. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelPackLevelsTableView, "_tableView");
  379. RectTransform pageUpButton = _tableViewPageUpButton.transform as RectTransform;
  380. RectTransform pageDownButton = _tableViewPageDownButton.transform as RectTransform;
  381. pageUpButton.anchoredPosition = new Vector2(pageUpButton.anchoredPosition.x, pageUpButton.anchoredPosition.y - 1f);
  382. pageDownButton.anchoredPosition = new Vector2(pageDownButton.anchoredPosition.x, pageDownButton.anchoredPosition.y + 1f);
  383. // shrink play button container
  384. RectTransform playContainerRect = _standardLevelDetailView.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayContainer");
  385. RectTransform playButtonsRect = playContainerRect.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayButtons");
  386. playButtonsRect.localScale = new Vector3(0.95f, 0.95f, 0.95f);
  387. }
  388. /// <summary>
  389. /// Show the UI.
  390. /// </summary>
  391. public void Show()
  392. {
  393. Logger.Trace("Show SongBrowserUI()");
  394. this.SetVisibility(true);
  395. }
  396. /// <summary>
  397. /// Hide the UI.
  398. /// </summary>
  399. public void Hide()
  400. {
  401. Logger.Trace("Hide SongBrowserUI()");
  402. this.SetVisibility(false);
  403. }
  404. /// <summary>
  405. /// Handle showing or hiding UI logic.
  406. /// </summary>
  407. /// <param name="visible"></param>
  408. private void SetVisibility(bool visible)
  409. {
  410. // UI not created, nothing visible to hide...
  411. if (!_uiCreated)
  412. {
  413. return;
  414. }
  415. _ppStatButton.gameObject.SetActive(visible);
  416. _starStatButton.gameObject.SetActive(visible);
  417. _njsStatButton.gameObject.SetActive(visible);
  418. _clearSortFilterButton.gameObject.SetActive(visible);
  419. _sortButtonGroup.ForEach(x => x.Button.gameObject.SetActive(visible));
  420. _filterButtonGroup.ForEach(x => x.Button.gameObject.SetActive(visible));
  421. _addFavoriteButton.gameObject.SetActive(visible);
  422. _deleteButton.gameObject.SetActive(visible);
  423. _pageUpFastButton.gameObject.SetActive(visible);
  424. _pageDownFastButton.gameObject.SetActive(visible);
  425. }
  426. /// <summary>
  427. /// Add our handlers into BeatSaber.
  428. /// </summary>
  429. private void InstallHandlers()
  430. {
  431. // level pack, level, difficulty handlers, characteristics
  432. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelPackLevelsTableView, "_tableView");
  433. tableView.didSelectCellWithIdxEvent -= HandleDidSelectTableViewRow;
  434. tableView.didSelectCellWithIdxEvent += HandleDidSelectTableViewRow;
  435. _levelPackLevelsViewController.didSelectLevelEvent -= OnDidSelectLevelEvent;
  436. _levelPackLevelsViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  437. _levelDifficultyViewController.didSelectDifficultyEvent -= OnDidSelectDifficultyEvent;
  438. _levelDifficultyViewController.didSelectDifficultyEvent += OnDidSelectDifficultyEvent;
  439. _levelPacksTableView.didSelectPackEvent -= _levelPacksTableView_didSelectPackEvent;
  440. _levelPacksTableView.didSelectPackEvent += _levelPacksTableView_didSelectPackEvent;
  441. _levelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
  442. _levelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
  443. _beatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent -= OnDidSelectBeatmapCharacteristic;
  444. _beatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent += OnDidSelectBeatmapCharacteristic;
  445. // make sure the quick scroll buttons don't desync with regular scrolling
  446. _tableViewPageDownButton.onClick.AddListener(delegate ()
  447. {
  448. this.RefreshQuickScrollButtons();
  449. });
  450. _tableViewPageUpButton.onClick.AddListener(delegate ()
  451. {
  452. this.RefreshQuickScrollButtons();
  453. });
  454. // finished level
  455. ResultsViewController resultsViewController = _levelSelectionFlowCoordinator.GetPrivateField<ResultsViewController>("_resultsViewController");
  456. resultsViewController.continueButtonPressedEvent += ResultsViewController_continueButtonPressedEvent;
  457. }
  458. /// <summary>
  459. /// Handle updating the level pack selection after returning from a song.
  460. /// </summary>
  461. /// <param name="obj"></param>
  462. private void ResultsViewController_continueButtonPressedEvent(ResultsViewController obj)
  463. {
  464. StartCoroutine(this.UpdateLevelPackSelectionEndOfFrame());
  465. }
  466. public IEnumerator UpdateLevelPackSelectionEndOfFrame()
  467. {
  468. yield return new WaitForEndOfFrame();
  469. try
  470. {
  471. bool didUpdateLevelPack = this.UpdateLevelPackSelection();
  472. if (!didUpdateLevelPack)
  473. {
  474. _model.ProcessSongList();
  475. }
  476. SelectAndScrollToLevel(_levelPackLevelsTableView, _model.LastSelectedLevelId);
  477. }
  478. catch (Exception e)
  479. {
  480. Logger.Exception("Exception:", e);
  481. }
  482. }
  483. /// <summary>
  484. /// Handler for level pack selection.
  485. /// </summary>
  486. /// <param name="arg1"></param>
  487. /// <param name="arg2"></param>
  488. private void _levelPacksTableView_didSelectPackEvent(LevelPacksTableView arg1, IBeatmapLevelPack arg2)
  489. {
  490. Logger.Trace("_levelPacksTableView_didSelectPackEvent(arg2={0})", arg2);
  491. try
  492. {
  493. //RefreshSongList();
  494. RefreshSortButtonUI();
  495. RefreshQuickScrollButtons();
  496. }
  497. catch (Exception e)
  498. {
  499. Logger.Exception("Exception handling didSelectPackEvent...", e);
  500. }
  501. }
  502. /// <summary>
  503. /// Handler for level pack selection, controller.
  504. /// Sets the current level pack into the model and updates.
  505. /// </summary>
  506. /// <param name="arg1"></param>
  507. /// <param name="arg2"></param>
  508. private void _levelPackViewController_didSelectPackEvent(LevelPacksViewController arg1, IBeatmapLevelPack arg2)
  509. {
  510. Logger.Trace("_levelPackViewController_didSelectPackEvent(arg2={0})", arg2);
  511. try
  512. {
  513. // reset filter mode always here
  514. if (this._model.Settings.currentLevelPackId != arg2.packID)// this._model.Settings.filterMode == SongFilterMode.Playlist)
  515. {
  516. this._model.Settings.filterMode = SongFilterMode.None;
  517. this._model.Settings.Save();
  518. }
  519. this._model.SetCurrentLevelPack(arg2);
  520. this._model.ProcessSongList();
  521. RefreshSongList();
  522. RefreshSortButtonUI();
  523. RefreshQuickScrollButtons();
  524. }
  525. catch (Exception e)
  526. {
  527. Logger.Exception("Exception handling didSelectPackEvent...", e);
  528. }
  529. }
  530. /// <summary>
  531. /// Remove all filters, update song list, save.
  532. /// </summary>
  533. private void OnClearButtonClickEvent()
  534. {
  535. Logger.Debug("Clearing all sorts and filters.");
  536. _model.Settings.sortMode = SongSortMode.Original;
  537. _model.Settings.filterMode = SongFilterMode.None;
  538. _model.Settings.invertSortResults = false;
  539. _model.Settings.Save();
  540. this._model.ProcessSongList();
  541. RefreshSongList();
  542. RefreshSortButtonUI();
  543. }
  544. /// <summary>
  545. /// Sort button clicked.
  546. /// </summary>
  547. private void OnSortButtonClickEvent(SongSortMode sortMode)
  548. {
  549. Logger.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  550. _model.LastSelectedLevelId = null;
  551. if (_model.Settings.sortMode == sortMode)
  552. {
  553. _model.ToggleInverting();
  554. }
  555. _model.Settings.sortMode = sortMode;
  556. // update the seed
  557. if (_model.Settings.sortMode == SongSortMode.Random)
  558. {
  559. this.Model.Settings.randomSongSeed = Guid.NewGuid().GetHashCode();
  560. }
  561. _model.Settings.Save();
  562. this._model.ProcessSongList();
  563. RefreshSongList();
  564. RefreshSortButtonUI();
  565. RefreshQuickScrollButtons();
  566. // Handle instant queue logic
  567. if (_model.Settings.sortMode == SongSortMode.Random && _model.Settings.randomInstantQueue)
  568. {
  569. int index = 0;
  570. if (_model.SortedSongList.Count > index)
  571. {
  572. this.SelectAndScrollToLevel(_levelPackLevelsTableView, _model.SortedSongList[index].levelID);
  573. var beatMapDifficulties = _model.SortedSongList[index].difficultyBeatmapSets
  574. .Where(x => x.beatmapCharacteristic == _model.CurrentBeatmapCharacteristicSO)
  575. .SelectMany(x => x.difficultyBeatmaps);
  576. this._levelDifficultyViewController.HandleDifficultySegmentedControlDidSelectCell(null, beatMapDifficulties.Count() - 1);
  577. _playButton.onClick.Invoke();
  578. }
  579. }
  580. //Scroll to start of the list
  581. TableView listTableView = _levelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
  582. listTableView.ScrollToCellWithIdx(0, TableView.ScrollPositionType.Beginning, false);
  583. }
  584. /// <summary>
  585. /// Filter by favorites.
  586. /// </summary>
  587. private void OnFavoriteFilterButtonClickEvent()
  588. {
  589. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Favorites.ToString());
  590. if (_model.Settings.filterMode != SongFilterMode.Favorites)
  591. {
  592. _model.Settings.filterMode = SongFilterMode.Favorites;
  593. }
  594. else
  595. {
  596. _model.Settings.filterMode = SongFilterMode.None;
  597. }
  598. _model.Settings.Save();
  599. _model.ProcessSongList();
  600. RefreshSongList();
  601. RefreshSortButtonUI();
  602. RefreshQuickScrollButtons();
  603. }
  604. /// <summary>
  605. /// Filter button clicked.
  606. /// </summary>
  607. /// <param name="sortMode"></param>
  608. private void OnSearchButtonClickEvent()
  609. {
  610. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
  611. if (_model.Settings.filterMode != SongFilterMode.Search)
  612. {
  613. this.ShowSearchKeyboard();
  614. }
  615. else
  616. {
  617. _model.Settings.filterMode = SongFilterMode.None;
  618. _model.ProcessSongList();
  619. RefreshSongList();
  620. RefreshSortButtonUI();
  621. RefreshQuickScrollButtons();
  622. }
  623. _model.Settings.Save();
  624. }
  625. /// <summary>
  626. /// Display the playlist selector.
  627. /// </summary>
  628. /// <param name="sortMode"></param>
  629. private void OnPlaylistButtonClickEvent()
  630. {
  631. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
  632. _model.LastSelectedLevelId = null;
  633. if (_model.Settings.filterMode != SongFilterMode.Playlist)
  634. {
  635. _playListFlowCoordinator.parentFlowCoordinator = _levelSelectionFlowCoordinator;
  636. _levelSelectionFlowCoordinator.InvokePrivateMethod("PresentFlowCoordinator", new object[] { _playListFlowCoordinator, null, false, false });
  637. }
  638. else
  639. {
  640. _model.Settings.filterMode = SongFilterMode.None;
  641. _model.Settings.Save();
  642. _model.ProcessSongList();
  643. RefreshSongList();
  644. RefreshSortButtonUI();
  645. RefreshQuickScrollButtons();
  646. }
  647. }
  648. /// <summary>
  649. /// Adjust UI based on level selected.
  650. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  651. /// </summary>
  652. private void OnDidSelectLevelEvent(LevelPackLevelsViewController view, IPreviewBeatmapLevel level)
  653. {
  654. try
  655. {
  656. Logger.Trace("OnDidSelectLevelEvent()");
  657. if (level == null)
  658. {
  659. Logger.Debug("No level selected?");
  660. return;
  661. }
  662. if (_model.Settings == null)
  663. {
  664. Logger.Debug("Settings not instantiated yet?");
  665. return;
  666. }
  667. _model.LastSelectedLevelId = level.levelID;
  668. HandleDidSelectLevelRow(level);
  669. }
  670. catch (Exception e)
  671. {
  672. Logger.Exception("Exception selecting song:", e);
  673. }
  674. }
  675. /// <summary>
  676. /// Switching one-saber modes for example.
  677. /// </summary>
  678. /// <param name="view"></param>
  679. /// <param name="bc"></param>
  680. private void OnDidSelectBeatmapCharacteristic(BeatmapCharacteristicSegmentedControlController view, BeatmapCharacteristicSO bc)
  681. {
  682. Logger.Trace("OnDidSelectBeatmapCharacteristic({0}", bc.name);
  683. _model.CurrentBeatmapCharacteristicSO = bc;
  684. _model.UpdateLevelRecords();
  685. this.RefreshSongList();
  686. }
  687. /// <summary>
  688. /// Handle difficulty level selection.
  689. /// </summary>
  690. private void OnDidSelectDifficultyEvent(BeatmapDifficultySegmentedControlController view, BeatmapDifficulty beatmap)
  691. {
  692. Logger.Trace("OnDidSelectDifficultyEvent({0})", beatmap);
  693. _deleteButton.interactable = (_levelDetailViewController.selectedDifficultyBeatmap.level.levelID.Length >= 32);
  694. this.RefreshScoreSaberData(_levelDetailViewController.selectedDifficultyBeatmap.level);
  695. }
  696. /// <summary>
  697. /// Refresh stats panel.
  698. /// </summary>
  699. /// <param name="level"></param>
  700. private void HandleDidSelectLevelRow(IPreviewBeatmapLevel level)
  701. {
  702. Logger.Trace("HandleDidSelectLevelRow({0})", level);
  703. _deleteButton.interactable = (level.levelID.Length >= 32);
  704. RefreshScoreSaberData(level);
  705. RefreshQuickScrollButtons();
  706. RefreshAddFavoriteButton(level.levelID);
  707. }
  708. /// <summary>
  709. /// Track the current row.
  710. /// </summary>
  711. /// <param name="tableView"></param>
  712. /// <param name="row"></param>
  713. private void HandleDidSelectTableViewRow(TableView tableView, int row)
  714. {
  715. Logger.Trace("HandleDidSelectTableViewRow({0})", row);
  716. _lastRow = row;
  717. }
  718. /// <summary>
  719. /// Pop up a delete dialog.
  720. /// </summary>
  721. private void HandleDeleteSelectedLevel()
  722. {
  723. IBeatmapLevel level = _levelDetailViewController.selectedDifficultyBeatmap.level;
  724. _deleteDialog.Init("Delete song", $"Do you really want to delete \"{ level.songName} {level.songSubName}\"?", "Delete", "Cancel",
  725. (selectedButton) =>
  726. {
  727. _levelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _deleteDialog, null, false });
  728. if (selectedButton == 0)
  729. {
  730. try
  731. {
  732. // determine the index we are deleting so we can keep the cursor near the same spot after
  733. // the header counts as an index, so if the index came from the level array we have to add 1.
  734. List<IPreviewBeatmapLevel> levels = _levelPackLevelsTableView.GetPrivateField<IBeatmapLevelPack>("_pack").beatmapLevelCollection.beatmapLevels.ToList();
  735. int selectedIndex = 1 + levels.FindIndex(x => x.levelID == _levelDetailViewController.selectedDifficultyBeatmap.level.levelID);
  736. // we are only deleting custom levels, find the song, delete it
  737. var song = new Song(SongLoader.CustomLevels.First(x => x.levelID == _levelDetailViewController.selectedDifficultyBeatmap.level.levelID));
  738. SongDownloader.Instance.DeleteSong(song);
  739. if (selectedIndex > 0)
  740. {
  741. this._model.RemoveSongFromLevelPack(this._model.CurrentLevelPack, _levelDetailViewController.selectedDifficultyBeatmap.level.levelID);
  742. Logger.Log("Removed {0} from custom song list!", song.songName);
  743. this.UpdateLevelDataModel();
  744. this.RefreshSongList();
  745. TableView listTableView = _levelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
  746. listTableView.ScrollToCellWithIdx(selectedIndex, TableView.ScrollPositionType.Beginning, false);
  747. _levelPackLevelsTableView.SetPrivateField("_selectedRow", selectedIndex);
  748. listTableView.SelectCellWithIdx(selectedIndex, true);
  749. }
  750. }
  751. catch (Exception e)
  752. {
  753. Logger.Error("Unable to delete song! Exception: " + e);
  754. }
  755. }
  756. });
  757. _levelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { _deleteDialog, null, false });
  758. }
  759. /// <summary>
  760. /// Create MD5 of a file.
  761. /// </summary>
  762. /// <param name="path"></param>
  763. /// <returns></returns>
  764. public static string CreateMD5FromFile(string path)
  765. {
  766. string hash = "";
  767. if (!File.Exists(path)) return null;
  768. using (MD5 md5 = MD5.Create())
  769. {
  770. using (var stream = File.OpenRead(path))
  771. {
  772. byte[] hashBytes = md5.ComputeHash(stream);
  773. StringBuilder sb = new StringBuilder();
  774. foreach (byte hashByte in hashBytes)
  775. {
  776. sb.Append(hashByte.ToString("X2"));
  777. }
  778. hash = sb.ToString();
  779. return hash;
  780. }
  781. }
  782. }
  783. /// <summary>
  784. /// Handle selection of a playlist. Show just the songs in the playlist.
  785. /// </summary>
  786. /// <param name="p"></param>
  787. private void HandleDidSelectPlaylist(Playlist p)
  788. {
  789. if (p != null)
  790. {
  791. Logger.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  792. _model.Settings.filterMode = SongFilterMode.Playlist;
  793. _model.CurrentPlaylist = p;
  794. _model.Settings.Save();
  795. _model.ProcessSongList();
  796. this.RefreshSongList();
  797. this.RefreshSortButtonUI();
  798. }
  799. else
  800. {
  801. Logger.Debug("No playlist selected");
  802. }
  803. }
  804. /// <summary>
  805. /// Display the search keyboard
  806. /// </summary>
  807. void ShowSearchKeyboard()
  808. {
  809. if (_searchViewController == null)
  810. {
  811. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  812. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  813. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  814. }
  815. Logger.Debug("Presenting search keyboard");
  816. _levelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { _searchViewController, null, false });
  817. }
  818. /// <summary>
  819. /// Handle back button event from search keyboard.
  820. /// </summary>
  821. private void SearchViewControllerbackButtonPressed()
  822. {
  823. _levelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
  824. // force disable search filter.
  825. this._model.Settings.filterMode = SongFilterMode.None;
  826. this._model.Settings.Save();
  827. RefreshSortButtonUI();
  828. RefreshQuickScrollButtons();
  829. }
  830. /// <summary>
  831. /// Handle search.
  832. /// </summary>
  833. /// <param name="searchFor"></param>
  834. private void SearchViewControllerSearchButtonPressed(string searchFor)
  835. {
  836. _levelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
  837. Logger.Debug("Searching for \"{0}\"...", searchFor);
  838. _model.Settings.filterMode = SongFilterMode.Search;
  839. _model.Settings.searchTerms.Insert(0, searchFor);
  840. _model.Settings.Save();
  841. _model.LastSelectedLevelId = null;
  842. _model.ProcessSongList();
  843. RefreshSongList();
  844. RefreshSortButtonUI();
  845. RefreshQuickScrollButtons();
  846. }
  847. /// <summary>
  848. /// Make big jumps in the song list.
  849. /// </summary>
  850. /// <param name="numJumps"></param>
  851. private void JumpSongList(int numJumps, float segmentPercent)
  852. {
  853. int totalSize = _model.SortedSongList.Count;
  854. int segmentSize = (int)(totalSize * segmentPercent);
  855. // Jump at least one scree size.
  856. if (segmentSize < LIST_ITEMS_VISIBLE_AT_ONCE)
  857. {
  858. segmentSize = LIST_ITEMS_VISIBLE_AT_ONCE;
  859. }
  860. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelPackLevelsTableView, "_tableView");
  861. int jumpDirection = Math.Sign(numJumps);
  862. int newRow = _lastRow + (jumpDirection * segmentSize);
  863. if (newRow <= 0)
  864. {
  865. newRow = 0;
  866. }
  867. else if (newRow >= totalSize)
  868. {
  869. newRow = totalSize - 1;
  870. }
  871. Logger.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
  872. _lastRow = newRow;
  873. this.SelectAndScrollToLevel(_levelPackLevelsTableView, _model.SortedSongList[newRow].levelID);
  874. }
  875. /// <summary>
  876. /// Add/Remove song from favorites depending on if it already exists.
  877. /// </summary>
  878. private void ToggleSongInPlaylist()
  879. {
  880. IBeatmapLevel songInfo = _levelDetailViewController.selectedDifficultyBeatmap.level;
  881. if (_model.CurrentEditingPlaylist != null)
  882. {
  883. if (_model.CurrentEditingPlaylistLevelIds.Contains(songInfo.levelID))
  884. {
  885. Logger.Info("Remove {0} from editing playlist", songInfo.songName);
  886. _model.RemoveSongFromEditingPlaylist(songInfo);
  887. if (_model.Settings.filterMode == SongFilterMode.Favorites)
  888. {
  889. this._model.ProcessSongList();
  890. this.RefreshSongList();
  891. }
  892. }
  893. else
  894. {
  895. Logger.Info("Add {0} to editing playlist", songInfo.songName);
  896. _model.AddSongToEditingPlaylist(songInfo);
  897. }
  898. }
  899. RefreshAddFavoriteButton(songInfo.levelID);
  900. _model.Settings.Save();
  901. }
  902. /// <summary>
  903. /// Update GUI elements that show score saber data.
  904. /// </summary>
  905. public void RefreshScoreSaberData(IPreviewBeatmapLevel level)
  906. {
  907. Logger.Trace("RefreshScoreSaberData({0})", level.levelID);
  908. // use controllers level...
  909. if (level == null)
  910. {
  911. level = _levelDetailViewController.selectedDifficultyBeatmap.level;
  912. }
  913. // abort!
  914. if (level == null)
  915. {
  916. Logger.Debug("Aborting RefreshScoreSaberData()");
  917. return;
  918. }
  919. BeatmapDifficulty difficulty = this._levelDifficultyViewController.selectedDifficulty;
  920. string njsText;
  921. string difficultyString = difficulty.ToString();
  922. Logger.Debug(difficultyString);
  923. //Grab NJS for difficulty
  924. //Default to 10 if a standard level
  925. float njs = 0;
  926. if (!_model.LevelIdToCustomSongInfos.ContainsKey(level.levelID))
  927. {
  928. njsText = "OST";
  929. }
  930. else
  931. {
  932. //Grab the matching difficulty level
  933. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  934. CustomSongInfo.DifficultyLevel difficultyLevel = null;
  935. foreach (var diffLevel in customLevel.customSongInfo.difficultyLevels)
  936. {
  937. if (diffLevel.difficulty == difficultyString)
  938. {
  939. difficultyLevel = diffLevel;
  940. break;
  941. }
  942. }
  943. // set njs text
  944. if (difficultyLevel == null || String.IsNullOrEmpty(difficultyLevel.json))
  945. {
  946. njsText = "NA";
  947. }
  948. else
  949. {
  950. njs = GetNoteJump(difficultyLevel.json);
  951. njsText = njs.ToString();
  952. }
  953. }
  954. UIBuilder.SetStatButtonText(_njsStatButton, njsText);
  955. // check if we have score saber data
  956. if (this._model.LevelIdToScoreSaberData != null)
  957. {
  958. // Check for PP
  959. Logger.Debug("Checking if have info for song {0}", level.songName);
  960. if (this._model.LevelIdToScoreSaberData.ContainsKey(level.levelID))
  961. {
  962. Logger.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
  963. ScoreSaberData ppData = this._model.LevelIdToScoreSaberData[level.levelID];
  964. if (ppData.difficultyToSaberDifficulty.ContainsKey(difficultyString))
  965. {
  966. Logger.Debug("Display pp for song.");
  967. float pp = ppData.difficultyToSaberDifficulty[difficultyString].pp;
  968. float star = ppData.difficultyToSaberDifficulty[difficultyString].star;
  969. UIBuilder.SetStatButtonText(_ppStatButton, String.Format("{0:0.#}", pp));
  970. UIBuilder.SetStatButtonText(_starStatButton, String.Format("{0:0.#}", star));
  971. }
  972. else
  973. {
  974. UIBuilder.SetStatButtonText(_ppStatButton, "NA");
  975. UIBuilder.SetStatButtonText(_starStatButton, "NA");
  976. }
  977. }
  978. else
  979. {
  980. UIBuilder.SetStatButtonText(_ppStatButton, "?");
  981. UIBuilder.SetStatButtonText(_starStatButton, "?");
  982. }
  983. }
  984. else
  985. {
  986. Logger.Debug("No ScoreSaberData available... Cannot display pp/star stats...");
  987. }
  988. Logger.Debug("Done refreshing score saber stats.");
  989. }
  990. /// <summary>
  991. /// Update interactive state of the quick scroll buttons.
  992. /// </summary>
  993. private void RefreshQuickScrollButtons()
  994. {
  995. // Refresh the fast scroll buttons
  996. if (_tableViewPageUpButton != null && _pageUpFastButton != null)
  997. {
  998. _pageUpFastButton.interactable = _tableViewPageUpButton.interactable;
  999. _pageUpFastButton.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  1000. }
  1001. else
  1002. {
  1003. _pageUpFastButton.gameObject.SetActive(false);
  1004. }
  1005. if (_tableViewPageDownButton != null && _pageDownFastButton != null)
  1006. {
  1007. _pageDownFastButton.interactable = _tableViewPageDownButton.interactable;
  1008. _pageDownFastButton.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  1009. }
  1010. else
  1011. {
  1012. _pageDownFastButton.gameObject.SetActive(false);
  1013. }
  1014. }
  1015. /// <summary>
  1016. /// Helper to quickly refresh add to favorites button
  1017. /// </summary>
  1018. /// <param name="levelId"></param>
  1019. private void RefreshAddFavoriteButton(String levelId)
  1020. {
  1021. if (levelId == null)
  1022. {
  1023. _currentAddFavoriteButtonSprite = null;
  1024. }
  1025. else
  1026. {
  1027. if (_model.CurrentEditingPlaylistLevelIds.Contains(levelId))
  1028. {
  1029. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  1030. }
  1031. else
  1032. {
  1033. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  1034. }
  1035. }
  1036. UIBuilder.SetButtonIcon(_addFavoriteButton, _currentAddFavoriteButtonSprite);
  1037. }
  1038. /// <summary>
  1039. /// Adjust the UI colors.
  1040. /// </summary>
  1041. public void RefreshSortButtonUI()
  1042. {
  1043. // So far all we need to refresh is the sort buttons.
  1044. foreach (SongSortButton sortButton in _sortButtonGroup)
  1045. {
  1046. //UIBuilder.SetButtonTextColor(sortButton.Button, Color.white);
  1047. UIBuilder.SetButtonBorder(sortButton.Button, Color.white);
  1048. if (sortButton.SortMode == _model.Settings.sortMode)
  1049. {
  1050. if (this._model.Settings.invertSortResults)
  1051. {
  1052. //UIBuilder.SetButtonTextColor(sortButton.Button, Color.red);
  1053. UIBuilder.SetButtonBorder(sortButton.Button, Color.red);
  1054. }
  1055. else
  1056. {
  1057. //UIBuilder.SetButtonTextColor(sortButton.Button, Color.green);
  1058. UIBuilder.SetButtonBorder(sortButton.Button, Color.green);
  1059. }
  1060. }
  1061. }
  1062. // refresh filter buttons
  1063. foreach (SongFilterButton filterButton in _filterButtonGroup)
  1064. {
  1065. UIBuilder.SetButtonBorder(filterButton.Button, Color.white);
  1066. if (filterButton.FilterMode == _model.Settings.filterMode)
  1067. {
  1068. UIBuilder.SetButtonBorder(filterButton.Button, Color.green);
  1069. }
  1070. }
  1071. }
  1072. /// <summary>
  1073. /// Try to refresh the song list. Broken for now.
  1074. /// </summary>
  1075. public void RefreshSongList()
  1076. {
  1077. Logger.Info("Refreshing the song list view.");
  1078. try
  1079. {
  1080. // TODO - remove as part of unifying the we handle the song lists
  1081. if (_model.IsCurrentLevelPackPreview)
  1082. {
  1083. return;
  1084. }
  1085. if (_model.SortedSongList == null)
  1086. {
  1087. Logger.Debug("Songs are not sorted yet, nothing to refresh.");
  1088. return;
  1089. }
  1090. var levels = _model.SortedSongList.ToArray();
  1091. Logger.Debug("Checking if TableView is initialized...");
  1092. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelPackLevelsTableView, "_tableView");
  1093. bool tableViewInit = ReflectionUtil.GetPrivateField<bool>(tableView, "_isInitialized");
  1094. if (!tableViewInit)
  1095. {
  1096. Logger.Debug("LevelPackLevelListTableView.TableView is not initialized... nothing to reload...");
  1097. return;
  1098. }
  1099. Logger.Debug("Reloading SongList TableView");
  1100. tableView.ReloadData();
  1101. Logger.Debug("Attempting to scroll to level...");
  1102. String selectedLevelID = null;
  1103. if (_model.LastSelectedLevelId != null)
  1104. {
  1105. selectedLevelID = _model.LastSelectedLevelId;
  1106. Logger.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  1107. }
  1108. else
  1109. {
  1110. if (levels.Length > 0)
  1111. {
  1112. selectedLevelID = levels.FirstOrDefault().levelID;
  1113. }
  1114. }
  1115. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  1116. //if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  1117. {
  1118. SelectAndScrollToLevel(_levelPackLevelsTableView, selectedLevelID);
  1119. }
  1120. }
  1121. catch (Exception e)
  1122. {
  1123. Logger.Exception("Exception refreshing song list:", e);
  1124. }
  1125. }
  1126. /// <summary>
  1127. /// Acquire the level pack collection.
  1128. /// </summary>
  1129. /// <returns></returns>
  1130. private IBeatmapLevelPackCollection GetLevelPackCollection()
  1131. {
  1132. if (_levelPackViewController == null)
  1133. {
  1134. return null;
  1135. }
  1136. IBeatmapLevelPackCollection levelPackCollection = _levelPackViewController.GetPrivateField<IBeatmapLevelPackCollection>("_levelPackCollection");
  1137. return levelPackCollection;
  1138. }
  1139. /// <summary>
  1140. /// Get the currently selected level pack within the LevelPackLevelViewController hierarchy.
  1141. /// </summary>
  1142. /// <returns></returns>
  1143. private IBeatmapLevelPack GetCurrentSelectedLevelPackFromBeatSaber()
  1144. {
  1145. if (_levelPackLevelsTableView == null)
  1146. {
  1147. return null;
  1148. }
  1149. var pack = _levelPackLevelsTableView.GetPrivateField<IBeatmapLevelPack>("_pack");
  1150. return pack;
  1151. }
  1152. /// <summary>
  1153. /// Get level pack by level pack id.
  1154. /// </summary>
  1155. /// <param name="levelPackId"></param>
  1156. /// <returns></returns>
  1157. private IBeatmapLevelPack GetLevelPackByPackId(String levelPackId)
  1158. {
  1159. IBeatmapLevelPackCollection levelPackCollection = GetLevelPackCollection();
  1160. if (levelPackCollection == null)
  1161. {
  1162. return null;
  1163. }
  1164. IBeatmapLevelPack levelPack = levelPackCollection.beatmapLevelPacks.ToList().First(x => x.packID == levelPackId);
  1165. return levelPack;
  1166. }
  1167. /// <summary>
  1168. /// Get level pack index by level pack id.
  1169. /// </summary>
  1170. /// <param name="levelPackId"></param>
  1171. /// <returns></returns>
  1172. private int GetLevelPackIndexByPackId(String levelPackId)
  1173. {
  1174. IBeatmapLevelPackCollection levelPackCollection = GetLevelPackCollection();
  1175. if (levelPackCollection == null)
  1176. {
  1177. return -1;
  1178. }
  1179. int index = levelPackCollection.beatmapLevelPacks.ToList().FindIndex(x => x.packID == levelPackId);
  1180. return index;
  1181. }
  1182. /// <summary>
  1183. /// Select a level pack.
  1184. /// </summary>
  1185. /// <param name="levelPackId"></param>
  1186. public void SelectLevelPack(String levelPackId)
  1187. {
  1188. Logger.Trace("SelectLevelPack({0})", levelPackId);
  1189. try
  1190. {
  1191. var levelPacks = GetLevelPackCollection();
  1192. var index = GetLevelPackIndexByPackId(levelPackId);
  1193. var pack = GetLevelPackByPackId(levelPackId);
  1194. if (index < 0)
  1195. {
  1196. Logger.Debug("Cannot select level packs yet...");
  1197. return;
  1198. }
  1199. Logger.Info("Selecting level pack index: {0}", pack.packName);
  1200. var tableView = _levelPacksTableView.GetPrivateField<TableView>("_tableView");
  1201. _levelPacksTableView.SelectCellWithIdx(index);
  1202. tableView.SelectCellWithIdx(index, true);
  1203. tableView.ScrollToCellWithIdx(0, TableView.ScrollPositionType.Beginning, false);
  1204. for (int i = 0; i < index; i++)
  1205. {
  1206. tableView.PageScrollDown();
  1207. }
  1208. Logger.Debug("Done selecting level pack!");
  1209. }
  1210. catch (Exception e)
  1211. {
  1212. Logger.Exception(e);
  1213. }
  1214. }
  1215. /// <summary>
  1216. /// Scroll TableView to proper row, fire events.
  1217. /// </summary>
  1218. /// <param name="table"></param>
  1219. /// <param name="levelID"></param>
  1220. private void SelectAndScrollToLevel(LevelPackLevelsTableView table, string levelID)
  1221. {
  1222. Logger.Debug("Scrolling to LevelID: {0}", levelID);
  1223. // Check once per load
  1224. if (!_checkedForTwitchPlugin)
  1225. {
  1226. Logger.Info("Checking for BeatSaber Twitch Integration Plugin...");
  1227. // Try to detect BeatSaber Twitch Integration Plugin
  1228. _detectedTwitchPluginQueue = Resources.FindObjectsOfTypeAll<VRUIViewController>().Any(x => x.name == "RequestInfo");
  1229. Logger.Info("BeatSaber Twitch Integration plugin detected: " + _detectedTwitchPluginQueue);
  1230. _checkedForTwitchPlugin = true;
  1231. }
  1232. // Skip scrolling to level if twitch plugin has queue active.
  1233. if (_detectedTwitchPluginQueue)
  1234. {
  1235. Logger.Debug("Skipping SelectAndScrollToLevel() because we detected Twitch Integrtion Plugin has a Queue active...");
  1236. return;
  1237. }
  1238. // try to find the index and scroll to it
  1239. int selectedIndex = 0;
  1240. List<IPreviewBeatmapLevel> levels = table.GetPrivateField<IBeatmapLevelPack>("_pack").beatmapLevelCollection.beatmapLevels.ToList();
  1241. // check if we have any levels
  1242. if (levels.Count <= 0)
  1243. {
  1244. return;
  1245. }
  1246. // acquire the index or try the last row
  1247. selectedIndex = levels.FindIndex(x => x.levelID == levelID);
  1248. if (selectedIndex < 0)
  1249. {
  1250. // this might look like an off by one error but the _level list we keep is missing the header entry BeatSaber.
  1251. // so the last row is +1 the max index, the count.
  1252. int maxCount = _model.SortedSongList.Count;
  1253. Logger.Debug("Song is not in the level pack, cannot scroll to it... Using last known row {0}/{1}", _lastRow, maxCount);
  1254. selectedIndex = Math.Min(maxCount, _lastRow);
  1255. }
  1256. else
  1257. {
  1258. // the header counts as an index, so if the index came from the level array we have to add 1.
  1259. selectedIndex += 1;
  1260. }
  1261. Logger.Debug("Scrolling level list to idx: {0}", selectedIndex);
  1262. TableView tableView = _levelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
  1263. var scrollPosType = TableView.ScrollPositionType.Center;
  1264. if (selectedIndex == 0)
  1265. {
  1266. scrollPosType = TableView.ScrollPositionType.Beginning;
  1267. }
  1268. if (selectedIndex == _model.SortedSongList.Count - 1)
  1269. {
  1270. scrollPosType = TableView.ScrollPositionType.End;
  1271. }
  1272. _levelPackLevelsTableView.HandleDidSelectRowEvent(tableView, selectedIndex);
  1273. tableView.ScrollToCellWithIdx(selectedIndex, TableView.ScrollPositionType.Beginning, true);
  1274. tableView.SelectCellWithIdx(selectedIndex);
  1275. RefreshQuickScrollButtons();
  1276. _lastRow = selectedIndex;
  1277. }
  1278. /// <summary>
  1279. /// Helper for updating the model (which updates the song list)
  1280. /// </summary>
  1281. public void UpdateLevelDataModel()
  1282. {
  1283. try
  1284. {
  1285. Logger.Trace("UpdateLevelDataModel()");
  1286. // get a current beatmap characteristic...
  1287. if (_model.CurrentBeatmapCharacteristicSO == null && _beatmapCharacteristicSelectionViewController != null)
  1288. {
  1289. _model.CurrentBeatmapCharacteristicSO = _beatmapCharacteristicSelectionViewController.GetPrivateField<BeatmapCharacteristicSO>("_selectedBeatmapCharacteristic");
  1290. }
  1291. _model.UpdateLevelRecords();
  1292. bool didUpdateLevelPack = UpdateLevelPackSelection();
  1293. if (!didUpdateLevelPack)
  1294. {
  1295. _model.ProcessSongList();
  1296. }
  1297. }
  1298. catch (Exception e)
  1299. {
  1300. Logger.Exception("SongBrowser UI crashed trying to update the internal song lists: ", e);
  1301. }
  1302. }
  1303. /// <summary>
  1304. /// Update the level pack model.
  1305. /// </summary>
  1306. public void UpdateLevelPackModel()
  1307. {
  1308. _model.UpdateLevelPackOriginalLists();
  1309. }
  1310. /// <summary>
  1311. /// Logic for fixing BeatSaber's level pack selection bugs.
  1312. ///
  1313. /// </summary>
  1314. public bool UpdateLevelPackSelection()
  1315. {
  1316. if (_levelPackViewController != null)
  1317. {
  1318. IBeatmapLevelPack currentSelected = GetCurrentSelectedLevelPackFromBeatSaber();
  1319. Logger.Debug("Current selected level pack: {0}", currentSelected);
  1320. if (String.IsNullOrEmpty(_model.Settings.currentLevelPackId))
  1321. {
  1322. if (currentSelected == null)
  1323. {
  1324. Logger.Debug("No level pack selected, acquiring the first available...");
  1325. var levelPackCollection = _levelPackViewController.GetPrivateField<IBeatmapLevelPackCollection>("_levelPackCollection");
  1326. currentSelected = levelPackCollection.beatmapLevelPacks[0];
  1327. }
  1328. this._model.SetCurrentLevelPack(currentSelected);
  1329. }
  1330. else if (currentSelected == null || (currentSelected.packID != _model.Settings.currentLevelPackId))
  1331. {
  1332. Logger.Debug("Automatically selecting level pack: {0}", _model.Settings.currentLevelPackId);
  1333. // HACK - BeatSaber seems to always go back to OST1 internally.
  1334. // - Lets force it to the last pack id but not have SongBrowser functions fire.
  1335. // Turn off our event processing
  1336. _levelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
  1337. _levelPacksTableView.didSelectPackEvent -= _levelPacksTableView_didSelectPackEvent;
  1338. var levelPack = GetLevelPackByPackId(_model.Settings.currentLevelPackId);
  1339. this.SelectLevelPack(_model.Settings.currentLevelPackId);
  1340. this._model.SetCurrentLevelPack(levelPack);
  1341. this._model.ProcessSongList();
  1342. _levelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
  1343. _levelPacksTableView.didSelectPackEvent += _levelPacksTableView_didSelectPackEvent;
  1344. return true;
  1345. }
  1346. else
  1347. {
  1348. this._model.SetCurrentLevelPack(currentSelected);
  1349. }
  1350. }
  1351. return false;
  1352. }
  1353. //Pull njs from a difficulty, based on private function from SongLoader
  1354. public float GetNoteJump(string json)
  1355. {
  1356. float noteJumpSpeed = 0;
  1357. var split = json.Split(':');
  1358. for (var i = 0; i < split.Length; i++)
  1359. {
  1360. if (split[i].Contains("_noteJumpSpeed"))
  1361. {
  1362. noteJumpSpeed = Convert.ToSingle(split[i + 1].Split(',')[0], CultureInfo.InvariantCulture);
  1363. }
  1364. }
  1365. return noteJumpSpeed;
  1366. }
  1367. #if DEBUG
  1368. /// <summary>
  1369. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  1370. /// </summary>
  1371. public void LateUpdate()
  1372. {
  1373. CheckDebugUserInput();
  1374. }
  1375. /// <summary>
  1376. /// Map some key presses directly to UI interactions to make testing easier.
  1377. /// </summary>
  1378. private void CheckDebugUserInput()
  1379. {
  1380. try
  1381. {
  1382. if (this._levelPackLevelsViewController != null && this._levelPackLevelsViewController.isActiveAndEnabled)
  1383. {
  1384. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  1385. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.X))
  1386. {
  1387. this._beatmapCharacteristicSelectionViewController.HandleDifficultySegmentedControlDidSelectCell(null, 1);
  1388. }
  1389. else if (Input.GetKeyDown(KeyCode.X))
  1390. {
  1391. this._beatmapCharacteristicSelectionViewController.HandleDifficultySegmentedControlDidSelectCell(null, 0);
  1392. }
  1393. // back
  1394. if (Input.GetKeyDown(KeyCode.Escape))
  1395. {
  1396. this._levelSelectionNavigationController.GoBackButtonPressed();
  1397. }
  1398. // select current sort mode again (toggle inverting)
  1399. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.BackQuote))
  1400. {
  1401. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  1402. }
  1403. // cycle sort modes
  1404. else if (Input.GetKeyDown(KeyCode.BackQuote))
  1405. {
  1406. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  1407. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  1408. }
  1409. // filter favorites
  1410. if (Input.GetKeyDown(KeyCode.F1))
  1411. {
  1412. OnFavoriteFilterButtonClickEvent();
  1413. }
  1414. // filter playlists
  1415. if (Input.GetKeyDown(KeyCode.F2))
  1416. {
  1417. OnPlaylistButtonClickEvent();
  1418. }
  1419. // filter search
  1420. if (Input.GetKeyDown(KeyCode.F3))
  1421. {
  1422. OnSearchButtonClickEvent();
  1423. }
  1424. // delete
  1425. if (Input.GetKeyDown(KeyCode.Delete))
  1426. {
  1427. _deleteButton.onClick.Invoke();
  1428. }
  1429. // c - select difficulty for top song
  1430. if (Input.GetKeyDown(KeyCode.C))
  1431. {
  1432. _levelPacksTableView.SelectCellWithIdx(5);
  1433. _levelPacksTableView.HandleDidSelectColumnEvent(null, 2);
  1434. TableView listTableView = this._levelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
  1435. this._levelPackLevelsTableView.HandleDidSelectRowEvent(listTableView, 2);
  1436. listTableView.ScrollToCellWithIdx(2, TableView.ScrollPositionType.Beginning, false);
  1437. //this._levelDifficultyViewController.HandleDifficultySegmentedControlDidSelectCell(null, 0);
  1438. }
  1439. // v - select difficulty for top song
  1440. if (Input.GetKeyDown(KeyCode.V))
  1441. {
  1442. this.SelectAndScrollToLevel(_levelPackLevelsTableView, _model.SortedSongList[0].levelID);
  1443. this._levelDifficultyViewController.HandleDifficultySegmentedControlDidSelectCell(null, 0);
  1444. }
  1445. // return - start a song
  1446. if (Input.GetKeyDown(KeyCode.Return))
  1447. {
  1448. if (_playButton.isActiveAndEnabled)
  1449. {
  1450. _playButton.onClick.Invoke();
  1451. }
  1452. }
  1453. // change song index
  1454. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  1455. {
  1456. _pageUpFastButton.onClick.Invoke();
  1457. }
  1458. else if (Input.GetKeyDown(KeyCode.N))
  1459. {
  1460. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  1461. this.SelectAndScrollToLevel(_levelPackLevelsTableView, _model.SortedSongList[_lastRow].levelID);
  1462. }
  1463. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  1464. {
  1465. _pageDownFastButton.onClick.Invoke();
  1466. }
  1467. else if (Input.GetKeyDown(KeyCode.M))
  1468. {
  1469. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  1470. this.SelectAndScrollToLevel(_levelPackLevelsTableView, _model.SortedSongList[_lastRow].levelID);
  1471. }
  1472. // add to favorites
  1473. if (Input.GetKeyDown(KeyCode.KeypadPlus))
  1474. {
  1475. ToggleSongInPlaylist();
  1476. }
  1477. }
  1478. else if (_deleteDialog != null && _deleteDialog.isInViewControllerHierarchy)
  1479. {
  1480. // accept delete
  1481. if (Input.GetKeyDown(KeyCode.Return))
  1482. {
  1483. _deleteDialog.GetPrivateField<Button>("_okButton").onClick.Invoke();
  1484. }
  1485. if (Input.GetKeyDown(KeyCode.Escape))
  1486. {
  1487. _deleteDialog.GetPrivateField<Button>("_cancelButton").onClick.Invoke();
  1488. }
  1489. }
  1490. else
  1491. {
  1492. if (Input.GetKeyDown(KeyCode.B))
  1493. {
  1494. Logger.Debug("Invoking OK Button");
  1495. VRUIViewController view = Resources.FindObjectsOfTypeAll<VRUIViewController>().First(x => x.name == "StandardLevelResultsViewController");
  1496. view.GetComponentsInChildren<Button>().First(x => x.name == "Ok").onClick.Invoke();
  1497. }
  1498. }
  1499. }
  1500. catch (Exception e)
  1501. {
  1502. Logger.Exception("Debug Input caused Exception: ", e);
  1503. }
  1504. }
  1505. #endif
  1506. }
  1507. }