SongBrowserUI.cs 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438
  1. using BeatSaberMarkupLanguage.Components;
  2. using HMUI;
  3. using SongBrowser.DataAccess;
  4. using SongBrowser.Internals;
  5. using SongCore.Utilities;
  6. using SongDataCore.BeatStar;
  7. using System;
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using UnityEngine;
  12. using UnityEngine.UI;
  13. using VRUIControls;
  14. using Logger = SongBrowser.Logging.Logger;
  15. namespace SongBrowser.UI
  16. {
  17. public enum UIState
  18. {
  19. Disabled,
  20. Main,
  21. SortBy,
  22. FilterBy
  23. }
  24. public class SongBrowserViewController : ViewController
  25. {
  26. // Named instance
  27. }
  28. /// <summary>
  29. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  30. /// </summary>
  31. public class SongBrowserUI : MonoBehaviour
  32. {
  33. // Logging
  34. public const String Name = "SongBrowserUI";
  35. private const float SEGMENT_PERCENT = 0.1f;
  36. private const int LIST_ITEMS_VISIBLE_AT_ONCE = 6;
  37. private const float CLEAR_BUTTON_Y = -31.5f;
  38. private const float BUTTON_ROW_Y = -31.5f;
  39. // BeatSaber Internal UI structures
  40. DataAccess.BeatSaberUIController _beatUi;
  41. // New UI Elements
  42. private SongBrowserViewController _viewController;
  43. private List<SongSortButton> _sortButtonGroup;
  44. private List<SongFilterButton> _filterButtonGroup;
  45. private Button _sortByButton;
  46. private Button _sortByDisplay;
  47. private Button _filterByButton;
  48. private Button _filterByDisplay;
  49. private Button _randomButton;
  50. private Button _clearSortFilterButton;
  51. private SimpleDialogPromptViewController _deleteDialog;
  52. private Button _deleteButton;
  53. private Button _pageUpFastButton;
  54. private Button _pageDownFastButton;
  55. private RectTransform _ppStatButton;
  56. private RectTransform _starStatButton;
  57. private RectTransform _njsStatButton;
  58. private RectTransform _noteJumpStartBeatOffsetLabel;
  59. private IAnnotatedBeatmapLevelCollection _lastLevelCollection;
  60. bool _selectingCategory = false;
  61. private SongBrowserModel _model;
  62. public SongBrowserModel Model
  63. {
  64. set
  65. {
  66. _model = value;
  67. }
  68. get
  69. {
  70. return _model;
  71. }
  72. }
  73. private bool _uiCreated = false;
  74. private UIState _currentUiState = UIState.Disabled;
  75. private bool _asyncUpdating = false;
  76. /// <summary>
  77. /// Builds the UI for this plugin.
  78. /// </summary>
  79. public void CreateUI(MainMenuViewController.MenuButton mode)
  80. {
  81. Logger.Trace("CreateUI()");
  82. // Determine the flow controller to use
  83. FlowCoordinator flowCoordinator;
  84. if (mode == MainMenuViewController.MenuButton.SoloFreePlay)
  85. {
  86. Logger.Debug("Entering SOLO mode...");
  87. flowCoordinator = Resources.FindObjectsOfTypeAll<SoloFreePlayFlowCoordinator>().Last();
  88. }
  89. else if (mode == MainMenuViewController.MenuButton.Party)
  90. {
  91. Logger.Debug("Entering PARTY mode...");
  92. flowCoordinator = Resources.FindObjectsOfTypeAll<PartyFreePlayFlowCoordinator>().Last();
  93. }
  94. else
  95. {
  96. Logger.Info("Entering Unsupported mode...");
  97. return;
  98. }
  99. Logger.Debug("Done fetching Flow Coordinator for the appropriate mode...");
  100. _beatUi = new DataAccess.BeatSaberUIController(flowCoordinator);
  101. _lastLevelCollection = null;
  102. // returning to the menu and switching modes could trigger this.
  103. if (_uiCreated)
  104. {
  105. return;
  106. }
  107. try
  108. {
  109. // Create a view controller to store all SongBrowser elements
  110. if (_viewController)
  111. {
  112. UnityEngine.GameObject.Destroy(_viewController);
  113. }
  114. _viewController = BeatSaberUI.CreateCurvedViewController<SongBrowserViewController>("SongBrowserViewController", 125.0f);
  115. _viewController.rectTransform.SetParent(_beatUi.LevelCollectionNavigationController.rectTransform, false);
  116. _viewController.rectTransform.anchorMin = new Vector2(0f, 0f);
  117. _viewController.rectTransform.anchorMax = new Vector2(1f, 1f);
  118. _viewController.rectTransform.anchoredPosition = new Vector2(0, 0);
  119. _viewController.rectTransform.sizeDelta = new Vector2(125, 25);
  120. _viewController.gameObject.SetActive(true);
  121. // create song browser main ui
  122. CreateOuterUi();
  123. CreateSortButtons();
  124. CreateFilterButtons();
  125. CreateDeleteUI();
  126. CreateFastPageButtons();
  127. this.InstallHandlers();
  128. this.ModifySongStatsPanel();
  129. this.ResizeSongUI();
  130. _uiCreated = true;
  131. RefreshSortButtonUI();
  132. Logger.Debug("Done Creating UI...");
  133. }
  134. catch (Exception e)
  135. {
  136. Logger.Exception("Exception during CreateUI: ", e);
  137. }
  138. }
  139. /// <summary>
  140. /// Create the outer ui.
  141. /// </summary>
  142. private void CreateOuterUi()
  143. {
  144. Logger.Debug("Creating outer UI...");
  145. float clearButtonX = -72.5f;
  146. float clearButtonY = CLEAR_BUTTON_Y;
  147. float buttonY = BUTTON_ROW_Y;
  148. float buttonHeight = 5.0f;
  149. float sortByButtonX = -62.5f + buttonHeight;
  150. float outerButtonFontSize = 3.0f;
  151. float displayButtonFontSize = 2.5f;
  152. float outerButtonWidth = 24.0f;
  153. float randomButtonWidth = 10.0f;
  154. // clear button
  155. _clearSortFilterButton = _viewController.CreateIconButton(
  156. "ClearSortAndFilterButton",
  157. "PracticeButton",
  158. new Vector2(clearButtonX, clearButtonY),
  159. new Vector2(randomButtonWidth, randomButtonWidth),
  160. () =>
  161. {
  162. if (_currentUiState == UIState.FilterBy || _currentUiState == UIState.SortBy)
  163. {
  164. RefreshOuterUIState(UIState.Main);
  165. }
  166. else
  167. {
  168. OnClearButtonClickEvent();
  169. }
  170. },
  171. Base64Sprites.XIcon);
  172. _clearSortFilterButton.SetButtonBackgroundActive(false);
  173. // create SortBy button and its display
  174. float curX = sortByButtonX;
  175. Logger.Debug("Creating Sort By...");
  176. _sortByButton = _viewController.CreateUIButton("sortBy", "PracticeButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  177. {
  178. RefreshOuterUIState(UIState.SortBy);
  179. }, "Sort By");
  180. _sortByButton.SetButtonTextSize(outerButtonFontSize);
  181. _sortByButton.ToggleWordWrapping(false);
  182. curX += outerButtonWidth;
  183. Logger.Debug("Creating Sort By Display...");
  184. _sortByDisplay = _viewController.CreateUIButton("sortByValue", "PracticeButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  185. {
  186. OnSortButtonClickEvent(_model.Settings.sortMode);
  187. }, "");
  188. _sortByDisplay.SetButtonTextSize(displayButtonFontSize);
  189. _sortByDisplay.ToggleWordWrapping(false);
  190. curX += outerButtonWidth;
  191. // create FilterBy button and its display
  192. Logger.Debug("Creating Filter By...");
  193. _filterByButton = _viewController.CreateUIButton("filterBy", "PracticeButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  194. {
  195. RefreshOuterUIState(UIState.FilterBy);
  196. }, "Filter By");
  197. _filterByButton.SetButtonTextSize(outerButtonFontSize);
  198. _filterByButton.ToggleWordWrapping(false);
  199. curX += outerButtonWidth;
  200. Logger.Debug("Creating Filter By Display...");
  201. _filterByDisplay = _viewController.CreateUIButton("filterValue", "PracticeButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  202. {
  203. _model.Settings.filterMode = SongFilterMode.None;
  204. CancelFilter();
  205. ProcessSongList();
  206. RefreshSongUI();
  207. }, "");
  208. _filterByDisplay.SetButtonTextSize(displayButtonFontSize);
  209. _filterByDisplay.ToggleWordWrapping(false);
  210. // random button
  211. Logger.Debug("Creating Random Button...");
  212. _randomButton = _viewController.CreateIconButton("randomButton", "PracticeButton", new Vector2(curX + (outerButtonWidth / 2.0f) + (randomButtonWidth / 4.0f), clearButtonY), new Vector2(randomButtonWidth, randomButtonWidth), () =>
  213. {
  214. OnSortButtonClickEvent(SongSortMode.Random);
  215. }, Base64Sprites.RandomIcon);
  216. _randomButton.SetButtonBackgroundActive(false);
  217. }
  218. /// <summary>
  219. /// Create the sort button ribbon
  220. /// </summary>
  221. private void CreateSortButtons()
  222. {
  223. Logger.Debug("Create sort buttons...");
  224. float sortButtonFontSize = 2.0f;
  225. float sortButtonX = -63.0f;
  226. float sortButtonWidth = 12.0f;
  227. float buttonSpacing = 0.25f;
  228. float buttonY = BUTTON_ROW_Y;
  229. float buttonHeight = 5.0f;
  230. string[] sortButtonNames = new string[]
  231. {
  232. "Title", "Author", "Newest", "#Plays", "PP", "Stars", "UpVotes", "Rating", "Heat"
  233. };
  234. SongSortMode[] sortModes = new SongSortMode[]
  235. {
  236. SongSortMode.Default, SongSortMode.Author, SongSortMode.Newest, SongSortMode.YourPlayCount, SongSortMode.PP, SongSortMode.Stars, SongSortMode.UpVotes, SongSortMode.Rating, SongSortMode.Heat
  237. };
  238. _sortButtonGroup = new List<SongSortButton>();
  239. for (int i = 0; i < sortButtonNames.Length; i++)
  240. {
  241. float curButtonX = sortButtonX + (sortButtonWidth * i) + (buttonSpacing * i);
  242. SongSortButton sortButton = new SongSortButton();
  243. sortButton.SortMode = sortModes[i];
  244. sortButton.Button = _viewController.CreateUIButton(String.Format("Sort{0}Button", sortButton.SortMode), "PracticeButton",
  245. new Vector2(curButtonX, buttonY), new Vector2(sortButtonWidth, buttonHeight),
  246. () =>
  247. {
  248. OnSortButtonClickEvent(sortButton.SortMode);
  249. RefreshOuterUIState(UIState.Main);
  250. },
  251. sortButtonNames[i]);
  252. sortButton.Button.SetButtonTextSize(sortButtonFontSize);
  253. sortButton.Button.ToggleWordWrapping(false);
  254. _sortButtonGroup.Add(sortButton);
  255. }
  256. }
  257. /// <summary>
  258. /// Create the filter by buttons
  259. /// </summary>
  260. private void CreateFilterButtons()
  261. {
  262. Logger.Debug("Creating filter buttons...");
  263. float filterButtonFontSize = 2.25f;
  264. float filterButtonX = -63.0f;
  265. float filterButtonWidth = 14.25f;
  266. float buttonSpacing = 0.5f;
  267. float buttonY = BUTTON_ROW_Y;
  268. float buttonHeight = 5.0f;
  269. string[] filterButtonNames = new string[]
  270. {
  271. "Search", "Ranked", "Unranked"
  272. };
  273. SongFilterMode[] filterModes = new SongFilterMode[]
  274. {
  275. SongFilterMode.Search, SongFilterMode.Ranked, SongFilterMode.Unranked
  276. };
  277. _filterButtonGroup = new List<SongFilterButton>();
  278. for (int i = 0; i < filterButtonNames.Length; i++)
  279. {
  280. float curButtonX = filterButtonX + (filterButtonWidth * i) + (buttonSpacing * i);
  281. SongFilterButton filterButton = new SongFilterButton();
  282. filterButton.FilterMode = filterModes[i];
  283. filterButton.Button = _viewController.CreateUIButton(String.Format("Filter{0}Button", filterButton.FilterMode), "PracticeButton",
  284. new Vector2(curButtonX, buttonY), new Vector2(filterButtonWidth, buttonHeight),
  285. () =>
  286. {
  287. OnFilterButtonClickEvent(filterButton.FilterMode);
  288. RefreshOuterUIState(UIState.Main);
  289. },
  290. filterButtonNames[i]);
  291. filterButton.Button.SetButtonTextSize(filterButtonFontSize);
  292. filterButton.Button.ToggleWordWrapping(false);
  293. _filterButtonGroup.Add(filterButton);
  294. }
  295. }
  296. /// <summary>
  297. /// Create the fast page up and down buttons
  298. /// </summary>
  299. private void CreateFastPageButtons()
  300. {
  301. Logger.Debug("Creating fast scroll button...");
  302. _pageUpFastButton = BeatSaberUI.CreatePageButton("PageUpFast",
  303. _beatUi.LevelCollectionNavigationController.transform as RectTransform, "UpButton",
  304. new Vector2(2.0f, 24f),
  305. new Vector2(8f, 8f),
  306. delegate ()
  307. {
  308. this.JumpSongList(-1, SEGMENT_PERCENT);
  309. }, Base64Sprites.DoubleArrow);
  310. _pageDownFastButton = BeatSaberUI.CreatePageButton("PageDownFast",
  311. _beatUi.LevelCollectionNavigationController.transform as RectTransform, "DownButton",
  312. new Vector2(2.0f, -24f),
  313. new Vector2(8f, 8f),
  314. delegate ()
  315. {
  316. this.JumpSongList(1, SEGMENT_PERCENT);
  317. }, Base64Sprites.DoubleArrow);
  318. }
  319. /// <summary>
  320. /// Create the delete button in the play button container
  321. /// </summary>
  322. private void CreateDeleteUI()
  323. {
  324. Logger.Debug("Creating delete dialog...");
  325. _deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(_beatUi.SimpleDialogPromptViewControllerPrefab);
  326. _deleteDialog.GetComponent<VRGraphicRaycaster>().SetField("_physicsRaycaster", BeatSaberUI.PhysicsRaycasterWithCache);
  327. _deleteDialog.name = "DeleteDialogPromptViewController";
  328. _deleteDialog.gameObject.SetActive(false);
  329. Logger.Debug("Creating delete button...");
  330. _deleteButton = BeatSaberUI.CreateIconButton("DeleteLevelButton", _beatUi.ActionButtons, "PracticeButton", Base64Sprites.DeleteIcon);
  331. _deleteButton.transform.SetAsFirstSibling();
  332. _deleteButton.onClick.AddListener(delegate () {
  333. HandleDeleteSelectedLevel();
  334. });
  335. }
  336. /// <summary>
  337. /// Resize the stats panel to fit more stats.
  338. /// </summary>
  339. private void ModifySongStatsPanel()
  340. {
  341. // modify stat panel, inject extra row of stats
  342. Logger.Debug("Resizing Stats Panel...");
  343. var statsPanel = _beatUi.StandardLevelDetailView.GetPrivateField<LevelParamsPanel>("_levelParamsPanel");
  344. (statsPanel.transform as RectTransform).Translate(0, 0.05f, 0);
  345. _ppStatButton = BeatSaberUI.CreateStatIcon("PPStatLabel",
  346. statsPanel.GetComponentsInChildren<RectTransform>().First(x => x.name == "NPS"),
  347. statsPanel.transform,
  348. Base64Sprites.GraphIcon,
  349. "PP Value");
  350. _starStatButton = BeatSaberUI.CreateStatIcon("StarStatLabel",
  351. statsPanel.GetComponentsInChildren<RectTransform>().First(x => x.name == "NotesCount"),
  352. statsPanel.transform,
  353. Base64Sprites.StarFullIcon,
  354. "Star Difficulty Rating");
  355. _njsStatButton = BeatSaberUI.CreateStatIcon("NoteJumpSpeedLabel",
  356. statsPanel.GetComponentsInChildren<RectTransform>().First(x => x.name == "ObstaclesCount"),
  357. statsPanel.transform,
  358. Base64Sprites.SpeedIcon,
  359. "Note Jump Speed");
  360. _noteJumpStartBeatOffsetLabel = BeatSaberUI.CreateStatIcon("NoteJumpStartBeatOffsetLabel",
  361. statsPanel.GetComponentsInChildren<RectTransform>().First(x => x.name == "BombsCount"),
  362. statsPanel.transform,
  363. Base64Sprites.NoteStartOffsetIcon,
  364. "Note Jump Start Beat Offset");
  365. }
  366. /// <summary>
  367. /// Resize some of the song table elements.
  368. /// </summary>
  369. public void ResizeSongUI()
  370. {
  371. // shrink play button container
  372. _beatUi.ActionButtons.localScale = new Vector3(0.875f, 0.875f, 0.875f);
  373. }
  374. /// <summary>
  375. /// Add our handlers into BeatSaber.
  376. /// </summary>
  377. private void InstallHandlers()
  378. {
  379. // level collection, level, difficulty handlers, characteristics
  380. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_beatUi.LevelCollectionTableView, "_tableView");
  381. // update stats
  382. _beatUi.LevelCollectionViewController.didSelectLevelEvent -= OnDidSelectLevelEvent;
  383. _beatUi.LevelCollectionViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  384. _beatUi.LevelDetailViewController.didChangeContentEvent -= OnDidPresentContentEvent;
  385. _beatUi.LevelDetailViewController.didChangeContentEvent += OnDidPresentContentEvent;
  386. _beatUi.LevelDetailViewController.didChangeDifficultyBeatmapEvent -= OnDidChangeDifficultyEvent;
  387. _beatUi.LevelDetailViewController.didChangeDifficultyBeatmapEvent += OnDidChangeDifficultyEvent;
  388. // update our view of the game state
  389. _beatUi.LevelFilteringNavigationController.didSelectAnnotatedBeatmapLevelCollectionEvent -= _levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent;
  390. _beatUi.LevelFilteringNavigationController.didSelectAnnotatedBeatmapLevelCollectionEvent += _levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent;
  391. _beatUi.AnnotatedBeatmapLevelCollectionsViewController.didSelectAnnotatedBeatmapLevelCollectionEvent -= handleDidSelectAnnotatedBeatmapLevelCollection;
  392. _beatUi.AnnotatedBeatmapLevelCollectionsViewController.didSelectAnnotatedBeatmapLevelCollectionEvent += handleDidSelectAnnotatedBeatmapLevelCollection;
  393. // Respond to characteristics changes
  394. _beatUi.BeatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent -= OnDidSelectBeatmapCharacteristic;
  395. _beatUi.BeatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent += OnDidSelectBeatmapCharacteristic;
  396. // make sure the quick scroll buttons don't desync with regular scrolling
  397. _beatUi.TableViewPageDownButton.onClick.AddListener(delegate ()
  398. {
  399. StartCoroutine(RefreshQuickScrollButtonsAsync());
  400. });
  401. _beatUi.TableViewPageUpButton.onClick.AddListener(delegate ()
  402. {
  403. StartCoroutine(RefreshQuickScrollButtonsAsync());
  404. });
  405. }
  406. /// <summary>
  407. /// Waits for the song UI to be available before trying to update.
  408. /// </summary>
  409. /// <returns></returns>
  410. public IEnumerator AsyncWaitForSongUIUpdate()
  411. {
  412. if (_asyncUpdating)
  413. {
  414. yield break;
  415. }
  416. if (!_uiCreated)
  417. {
  418. yield break;
  419. }
  420. if (!_model.SortWasMissingData)
  421. {
  422. yield break;
  423. }
  424. _asyncUpdating = true;
  425. while (_beatUi != null && (_beatUi.LevelSelectionNavigationController.GetPrivateField<bool>("_isInTransition") ||
  426. _beatUi.LevelDetailViewController.GetPrivateField<bool>("_isInTransition") ||
  427. !_beatUi.LevelSelectionNavigationController.isInViewControllerHierarchy ||
  428. !_beatUi.LevelDetailViewController.isInViewControllerHierarchy ||
  429. !_beatUi.LevelSelectionNavigationController.isActiveAndEnabled ||
  430. !_beatUi.LevelDetailViewController.isActiveAndEnabled))
  431. {
  432. yield return null;
  433. }
  434. //yield return new WaitForEndOfFrame();
  435. if (_model.Settings.sortMode.NeedsScoreSaberData() && SongDataCore.Plugin.Songs.IsDataAvailable())
  436. {
  437. ProcessSongList();
  438. RefreshSongUI();
  439. }
  440. _asyncUpdating = false;
  441. }
  442. /// <summary>
  443. /// Helper to reduce code duplication...
  444. /// </summary>
  445. public void RefreshSongUI(bool scrollToLevel = true)
  446. {
  447. if (!_uiCreated)
  448. {
  449. return;
  450. }
  451. RefreshSongList();
  452. RefreshSortButtonUI();
  453. if (!scrollToLevel)
  454. {
  455. _beatUi.ScrollToLevelByRow(0);
  456. }
  457. RefreshQuickScrollButtons();
  458. RefreshCurrentSelectionDisplay();
  459. }
  460. /// <summary>
  461. /// External helper.
  462. /// </summary>
  463. public void ProcessSongList()
  464. {
  465. if (!_uiCreated)
  466. {
  467. return;
  468. }
  469. this._model.ProcessSongList(_lastLevelCollection, _beatUi.LevelSelectionNavigationController);
  470. }
  471. /// <summary>
  472. /// Helper for common filter cancellation logic.
  473. /// </summary>
  474. public void CancelFilter()
  475. {
  476. Logger.Debug($"Cancelling filter, levelCollection {_lastLevelCollection}");
  477. _model.Settings.filterMode = SongFilterMode.None;
  478. GameObject _noDataGO = _beatUi.LevelCollectionViewController.GetPrivateField<GameObject>("_noDataInfoGO");
  479. string _headerText = _beatUi.LevelCollectionTableView.GetPrivateField<string>("_headerText");
  480. Sprite _headerSprite = _beatUi.LevelCollectionTableView.GetPrivateField<Sprite>("_headerSprite");
  481. IBeatmapLevelCollection levelCollection = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection().beatmapLevelCollection;
  482. _beatUi.LevelCollectionViewController.SetData(levelCollection, _headerText, _headerSprite, false, _noDataGO);
  483. }
  484. /// <summary>
  485. /// Playlists (fancy name for AnnotatedBeatmapLevelCollection)
  486. /// </summary>
  487. /// <param name="annotatedBeatmapLevelCollection"></param>
  488. public virtual void handleDidSelectAnnotatedBeatmapLevelCollection(IAnnotatedBeatmapLevelCollection annotatedBeatmapLevelCollection)
  489. {
  490. Logger.Trace("handleDidSelectAnnotatedBeatmapLevelCollection()");
  491. _lastLevelCollection = annotatedBeatmapLevelCollection;
  492. Model.Settings.currentLevelCategoryName = _beatUi.LevelFilteringNavigationController.selectedLevelCategory.ToString();
  493. Model.Settings.Save();
  494. Logger.Debug("AnnotatedBeatmapLevelCollection, Selected Level Collection={0}", _lastLevelCollection);
  495. }
  496. /// <summary>
  497. /// Handler for level collection selection, controller.
  498. /// Sets the current level collection into the model and updates.
  499. /// </summary>
  500. /// <param name="arg1"></param>
  501. /// <param name="arg2"></param>
  502. /// <param name="arg3"></param>
  503. /// <param name="arg4"></param>
  504. private void _levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent(LevelFilteringNavigationController arg1, IAnnotatedBeatmapLevelCollection arg2,
  505. GameObject arg3, BeatmapCharacteristicSO arg4)
  506. {
  507. Logger.Trace("_levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent(levelCollection={0})", arg2);
  508. if (arg2 == null)
  509. {
  510. // Probably means we transitioned between Music Packs and Playlists
  511. arg2 = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection();
  512. if (arg2 == null)
  513. {
  514. Logger.Warning("Nothing selected. This is likely an error.");
  515. return;
  516. }
  517. }
  518. Logger.Debug("Selected Level Collection={0}", arg2);
  519. // Do something about preview level packs, they can't be used past this point
  520. if (arg2 as PreviewBeatmapLevelPackSO)
  521. {
  522. Logger.Info("Hiding SongBrowser, previewing a song pack.");
  523. Hide();
  524. return;
  525. }
  526. Show();
  527. // category transition, just record the new collection
  528. if (_selectingCategory)
  529. {
  530. Logger.Info("Transitioning level category");
  531. _lastLevelCollection = arg2;
  532. StartCoroutine(RefreshSongListEndOfFrame());
  533. return;
  534. }
  535. // Skip the first time - prevents a bunch of reload content spam
  536. if (_lastLevelCollection == null)
  537. {
  538. return;
  539. }
  540. SelectLevelCollection(arg2);
  541. }
  542. /// <summary>
  543. /// Logic for selecting a level collection.
  544. /// </summary>
  545. /// <param name="levelPack"></param>
  546. public void SelectLevelCollection(IAnnotatedBeatmapLevelCollection levelCollection)
  547. {
  548. try
  549. {
  550. if (levelCollection == null)
  551. {
  552. Logger.Debug("No level collection selected...");
  553. return;
  554. }
  555. // store the real level collection
  556. if (levelCollection.collectionName != SongBrowserModel.FilteredSongsCollectionName && _lastLevelCollection != null)
  557. {
  558. Logger.Debug("Recording levelCollection: {0}", levelCollection.collectionName);
  559. _lastLevelCollection = levelCollection;
  560. Model.Settings.currentLevelCategoryName = _beatUi.LevelFilteringNavigationController.selectedLevelCategory.ToString();
  561. }
  562. // reset level selection
  563. _model.LastSelectedLevelId = null;
  564. // save level collection
  565. this._model.Settings.currentLevelCollectionName = levelCollection.collectionName;
  566. this._model.Settings.Save();
  567. StartCoroutine(ProcessSongListEndOfFrame());
  568. }
  569. catch (Exception e)
  570. {
  571. Logger.Exception("Exception handling SelectLevelCollection...", e);
  572. }
  573. }
  574. /// <summary>
  575. /// End of frame update the song list, the game seems to stomp on us sometimes otherwise
  576. /// TODO - Might not be nice to other plugins
  577. /// </summary>
  578. /// <returns></returns>
  579. public IEnumerator ProcessSongListEndOfFrame()
  580. {
  581. yield return new WaitForEndOfFrame();
  582. ProcessSongList();
  583. RefreshSongUI();
  584. }
  585. public IEnumerator RefreshSongListEndOfFrame()
  586. {
  587. yield return new WaitForEndOfFrame();
  588. RefreshSongUI();
  589. }
  590. /// <summary>
  591. /// Remove all filters, update song list, save.
  592. /// </summary>
  593. private void OnClearButtonClickEvent()
  594. {
  595. Logger.Debug("Clearing all sorts and filters.");
  596. _model.Settings.sortMode = SongSortMode.Original;
  597. _model.Settings.invertSortResults = false;
  598. _model.Settings.filterMode = SongFilterMode.None;
  599. _model.Settings.Save();
  600. CancelFilter();
  601. ProcessSongList();
  602. RefreshSongUI();
  603. }
  604. /// <summary>
  605. /// Sort button clicked.
  606. /// </summary>
  607. private void OnSortButtonClickEvent(SongSortMode sortMode)
  608. {
  609. Logger.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  610. if ((sortMode.NeedsScoreSaberData() && !SongDataCore.Plugin.Songs.IsDataAvailable()))
  611. {
  612. Logger.Info("Data for sort type is not available.");
  613. return;
  614. }
  615. // Clear current selected level id so our song list jumps to the start
  616. _model.LastSelectedLevelId = null;
  617. if (_model.Settings.sortMode == sortMode)
  618. {
  619. _model.ToggleInverting();
  620. }
  621. _model.Settings.sortMode = sortMode;
  622. // update the seed
  623. if (_model.Settings.sortMode == SongSortMode.Random)
  624. {
  625. _model.Settings.randomSongSeed = Guid.NewGuid().GetHashCode();
  626. }
  627. _model.Settings.Save();
  628. ProcessSongList();
  629. RefreshSongUI();
  630. }
  631. /// <summary>
  632. /// Handle filter button logic. Some filters have sub menus that need special logic.
  633. /// </summary>
  634. /// <param name="mode"></param>
  635. private void OnFilterButtonClickEvent(SongFilterMode mode)
  636. {
  637. Logger.Debug($"FilterButton {mode} clicked.");
  638. var curCollection = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection();
  639. if (_lastLevelCollection == null ||
  640. (curCollection != null &&
  641. curCollection.collectionName != SongBrowserModel.FilteredSongsCollectionName &&
  642. curCollection.collectionName != SongBrowserModel.PlaylistSongsCollectionName))
  643. {
  644. _lastLevelCollection = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection();
  645. }
  646. if (mode == SongFilterMode.Favorites)
  647. {
  648. _beatUi.SelectLevelCategory(SelectLevelCategoryViewController.LevelCategory.Favorites.ToString());
  649. }
  650. else
  651. {
  652. GameObject _noDataGO = _beatUi.LevelCollectionViewController.GetPrivateField<GameObject>("_noDataInfoGO");
  653. string _headerText = _beatUi.LevelCollectionTableView.GetPrivateField<string>("_headerText");
  654. Sprite _headerSprite = _beatUi.LevelCollectionTableView.GetPrivateField<Sprite>("_headerSprite");
  655. IBeatmapLevelCollection levelCollection = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection().beatmapLevelCollection;
  656. _beatUi.LevelCollectionViewController.SetData(levelCollection, _headerText, _headerSprite, false, _noDataGO);
  657. }
  658. // If selecting the same filter, cancel
  659. if (_model.Settings.filterMode == mode)
  660. {
  661. _model.Settings.filterMode = SongFilterMode.None;
  662. }
  663. else
  664. {
  665. _model.Settings.filterMode = mode;
  666. }
  667. switch (mode)
  668. {
  669. case SongFilterMode.Search:
  670. OnSearchButtonClickEvent();
  671. break;
  672. default:
  673. _model.Settings.Save();
  674. ProcessSongList();
  675. RefreshSongUI();
  676. break;
  677. }
  678. }
  679. /// <summary>
  680. /// Display the keyboard.
  681. /// </summary>
  682. /// <param name="sortMode"></param>
  683. private void OnSearchButtonClickEvent()
  684. {
  685. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
  686. this.ShowSearchKeyboard();
  687. }
  688. /// <summary>
  689. /// Adjust UI based on level selected.
  690. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  691. /// </summary>
  692. private void OnDidSelectLevelEvent(LevelCollectionViewController view, IPreviewBeatmapLevel level)
  693. {
  694. try
  695. {
  696. Logger.Trace("OnDidSelectLevelEvent()");
  697. if (level == null)
  698. {
  699. Logger.Debug("No level selected?");
  700. return;
  701. }
  702. if (_model.Settings == null)
  703. {
  704. Logger.Debug("Settings not instantiated yet?");
  705. return;
  706. }
  707. _model.LastSelectedLevelId = level.levelID;
  708. HandleDidSelectLevelRow(level);
  709. }
  710. catch (Exception e)
  711. {
  712. Logger.Exception("Exception selecting song:", e);
  713. }
  714. }
  715. /// <summary>
  716. /// Switching one-saber modes for example.
  717. /// </summary>
  718. /// <param name="view"></param>
  719. /// <param name="bc"></param>
  720. private void OnDidSelectBeatmapCharacteristic(BeatmapCharacteristicSegmentedControlController view, BeatmapCharacteristicSO bc)
  721. {
  722. try
  723. {
  724. Logger.Trace("OnDidSelectBeatmapCharacteristic({0})", bc.compoundIdPartName);
  725. _model.CurrentBeatmapCharacteristicSO = bc;
  726. if (_beatUi.StandardLevelDetailView != null)
  727. {
  728. RefreshScoreSaberData(_beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level);
  729. RefreshNoteJumpSpeed(_beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.noteJumpMovementSpeed,
  730. _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.noteJumpStartBeatOffset);
  731. }
  732. }
  733. catch (Exception e)
  734. {
  735. Logger.Exception(e);
  736. }
  737. }
  738. /// <summary>
  739. /// Handle difficulty level selection.
  740. /// </summary>
  741. private void OnDidChangeDifficultyEvent(StandardLevelDetailViewController view, IDifficultyBeatmap beatmap)
  742. {
  743. Logger.Trace("OnDidChangeDifficultyEvent({0})", beatmap);
  744. if (view.selectedDifficultyBeatmap == null)
  745. {
  746. return;
  747. }
  748. UpdateDeleteButtonState(view.selectedDifficultyBeatmap.level.levelID);
  749. RefreshScoreSaberData(view.selectedDifficultyBeatmap.level);
  750. RefreshNoteJumpSpeed(beatmap.noteJumpMovementSpeed, beatmap.noteJumpStartBeatOffset);
  751. }
  752. /// <summary>
  753. /// BeatSaber finished loading content. This is when the difficulty is finally updated.
  754. /// </summary>
  755. /// <param name="view"></param>
  756. /// <param name="type"></param>
  757. private void OnDidPresentContentEvent(StandardLevelDetailViewController view, StandardLevelDetailViewController.ContentType type)
  758. {
  759. Logger.Trace("OnDidPresentContentEvent()");
  760. if (type != StandardLevelDetailViewController.ContentType.OwnedAndReady)
  761. {
  762. return;
  763. }
  764. if (view.selectedDifficultyBeatmap == null)
  765. {
  766. return;
  767. }
  768. UpdateDeleteButtonState(_beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID);
  769. RefreshScoreSaberData(view.selectedDifficultyBeatmap.level);
  770. RefreshNoteJumpSpeed(view.selectedDifficultyBeatmap.noteJumpMovementSpeed, view.selectedDifficultyBeatmap.noteJumpStartBeatOffset);
  771. }
  772. /// <summary>
  773. /// Refresh stats panel.
  774. /// </summary>
  775. /// <param name="level"></param>
  776. private void HandleDidSelectLevelRow(IPreviewBeatmapLevel level)
  777. {
  778. Logger.Trace("HandleDidSelectLevelRow({0})", level);
  779. UpdateDeleteButtonState(level.levelID);
  780. RefreshQuickScrollButtons();
  781. }
  782. /// <summary>
  783. /// Pop up a delete dialog.
  784. /// </summary>
  785. private void HandleDeleteSelectedLevel()
  786. {
  787. IBeatmapLevel level = _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level;
  788. _deleteDialog.Init("Delete song", $"Do you really want to delete \"{ level.songName} {level.songSubName}\"?", "Delete", "Cancel",
  789. (selectedButton) =>
  790. {
  791. _deleteDialog.__DismissViewController(null);
  792. _beatUi.ScreenSystem.titleViewController.gameObject.SetActive(true);
  793. if (selectedButton == 0)
  794. {
  795. try
  796. {
  797. List<IPreviewBeatmapLevel> levels = _beatUi.GetCurrentLevelCollectionLevels().ToList();
  798. String collection = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection().collectionName;
  799. String selectedLevelID = _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level.levelID;
  800. int selectedIndex = levels.FindIndex(x => x.levelID == selectedLevelID);
  801. if (selectedIndex > -1)
  802. {
  803. CustomPreviewBeatmapLevel song;
  804. switch (collection)
  805. {
  806. case "Custom Levels":
  807. song = SongCore.Loader.CustomLevels.First(x => x.Value.levelID == selectedLevelID).Value;
  808. break;
  809. case "WIP Levels":
  810. song = SongCore.Loader.CustomWIPLevels.First(x => x.Value.levelID == selectedLevelID).Value;
  811. break;
  812. case "Cached WIP Levels":
  813. throw new Exception("Cannot delete cached levels.");
  814. default:
  815. var names = SongCore.Loader.SeperateSongFolders.Select(x => x.SongFolderEntry.Name);
  816. var separateFolders = SongCore.Loader.SeperateSongFolders;
  817. if (names.Contains(collection))
  818. {
  819. int folder_index = separateFolders.FindIndex(x => x.SongFolderEntry.Name.Equals(collection));
  820. song = separateFolders[folder_index].Levels.First(x => x.Value.levelID == selectedLevelID).Value;
  821. }
  822. else
  823. throw new Exception("Could not find level path. Is the selected collection a playlist?");
  824. break;
  825. }
  826. Logger.Info($"Deleting song: {song.customLevelPath}");
  827. SongCore.Loader.Instance.DeleteSong(song.customLevelPath);
  828. int removedLevels = levels.RemoveAll(x => x.levelID == selectedLevelID);
  829. Logger.Info($"Removed [{removedLevels}] level(s) from song list!");
  830. this.UpdateLevelDataModel();
  831. // if we have a song to select at the same index, set the last selected level id, UI updates takes care of the rest.
  832. if (selectedIndex < levels.Count)
  833. {
  834. if (levels[selectedIndex].levelID != null)
  835. {
  836. _model.LastSelectedLevelId = levels[selectedIndex].levelID;
  837. }
  838. }
  839. this.RefreshSongList();
  840. }
  841. }
  842. catch (Exception e)
  843. {
  844. Logger.Error("Unable to delete song! Exception: " + e);
  845. }
  846. }
  847. });
  848. _beatUi.ScreenSystem.titleViewController.gameObject.SetActive(false);
  849. _beatUi.LevelSelectionNavigationController.__PresentViewController(_deleteDialog, null);
  850. }
  851. /// <summary>
  852. /// Display the search keyboard
  853. /// </summary>
  854. void ShowSearchKeyboard()
  855. {
  856. var modalKbTag = new BeatSaberMarkupLanguage.Tags.ModalKeyboardTag();
  857. var modalKbView = modalKbTag.CreateObject(_beatUi.LevelSelectionNavigationController.rectTransform);
  858. modalKbView.gameObject.SetActive(true);
  859. var modalKb = modalKbView.GetComponent<ModalKeyboard>();
  860. modalKb.gameObject.SetActive(true);
  861. modalKb.keyboard.EnterPressed += SearchViewControllerSearchButtonPressed;
  862. modalKb.modalView.Show(true, true);
  863. }
  864. /// <summary>
  865. /// Handle search.
  866. /// </summary>
  867. /// <param name="searchFor"></param>
  868. private void SearchViewControllerSearchButtonPressed(string searchFor)
  869. {
  870. Logger.Debug("Searching for \"{0}\"...", searchFor);
  871. _model.Settings.filterMode = SongFilterMode.Search;
  872. _model.Settings.searchTerms.Insert(0, searchFor);
  873. _model.Settings.Save();
  874. _model.LastSelectedLevelId = null;
  875. ProcessSongList();
  876. RefreshSongUI();
  877. }
  878. /// <summary>
  879. /// Make big jumps in the song list.
  880. /// </summary>
  881. /// <param name="numJumps"></param>
  882. private void JumpSongList(int numJumps, float segmentPercent)
  883. {
  884. var levels = _beatUi.GetCurrentLevelCollectionLevels();
  885. if (levels == null)
  886. {
  887. return;
  888. }
  889. int totalSize = levels.Count();
  890. int segmentSize = (int)(totalSize * segmentPercent);
  891. // Jump at least one scree size.
  892. if (segmentSize < LIST_ITEMS_VISIBLE_AT_ONCE)
  893. {
  894. segmentSize = LIST_ITEMS_VISIBLE_AT_ONCE;
  895. }
  896. int currentRow = _beatUi.LevelCollectionTableView.GetPrivateField<int>("_selectedRow");
  897. int jumpDirection = Math.Sign(numJumps);
  898. int newRow = currentRow + (jumpDirection * segmentSize);
  899. if (newRow <= 0)
  900. {
  901. newRow = 0;
  902. }
  903. else if (newRow >= totalSize)
  904. {
  905. newRow = totalSize - 1;
  906. }
  907. Logger.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
  908. _beatUi.ScrollToLevelByRow(newRow);
  909. RefreshQuickScrollButtons();
  910. }
  911. /// <summary>
  912. /// Update GUI elements that show score saber data.
  913. /// </summary>
  914. public void RefreshScoreSaberData(IPreviewBeatmapLevel level)
  915. {
  916. Logger.Trace("RefreshScoreSaberData({0})", level.levelID);
  917. if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  918. {
  919. return;
  920. }
  921. BeatmapDifficulty difficulty = _beatUi.LevelDifficultyViewController.selectedDifficulty;
  922. string difficultyString = difficulty.ToString();
  923. if (difficultyString.Equals("ExpertPlus"))
  924. {
  925. difficultyString = "Expert+";
  926. }
  927. Logger.Debug(difficultyString);
  928. // Check if we have data for this song
  929. Logger.Debug("Checking if have info for song {0}", level.songName);
  930. var hash = SongBrowserModel.GetSongHash(level.levelID);
  931. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  932. {
  933. Logger.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
  934. BeatStarSong scoreSaberSong = SongDataCore.Plugin.Songs.Data.Songs[hash];
  935. BeatStarSongDifficultyStats scoreSaberSongDifficulty = scoreSaberSong.diffs.FirstOrDefault(x => String.Equals(x.diff, difficultyString));
  936. if (scoreSaberSongDifficulty != null)
  937. {
  938. Logger.Debug("Display pp for song.");
  939. double pp = scoreSaberSongDifficulty.pp;
  940. double star = scoreSaberSongDifficulty.star;
  941. BeatSaberUI.SetStatButtonText(_ppStatButton, String.Format("{0:0.#}", pp));
  942. BeatSaberUI.SetStatButtonText(_starStatButton, String.Format("{0:0.#}", star));
  943. }
  944. else
  945. {
  946. BeatSaberUI.SetStatButtonText(_ppStatButton, "NA");
  947. BeatSaberUI.SetStatButtonText(_starStatButton, "NA");
  948. }
  949. }
  950. else
  951. {
  952. BeatSaberUI.SetStatButtonText(_ppStatButton, "NA");
  953. BeatSaberUI.SetStatButtonText(_starStatButton, "NA");
  954. }
  955. Logger.Debug("Done refreshing score saber stats.");
  956. }
  957. /// <summary>
  958. /// Helper to refresh the NJS widget.
  959. /// </summary>
  960. /// <param name="noteJumpMovementSpeed"></param>
  961. private void RefreshNoteJumpSpeed(float noteJumpMovementSpeed, float noteJumpStartBeatOffset)
  962. {
  963. BeatSaberUI.SetStatButtonText(_njsStatButton, String.Format("{0}", noteJumpMovementSpeed));
  964. BeatSaberUI.SetStatButtonText(_noteJumpStartBeatOffsetLabel, String.Format("{0}", noteJumpStartBeatOffset));
  965. }
  966. /// <summary>
  967. /// Update interactive state of the quick scroll buttons.
  968. /// </summary>
  969. private void RefreshQuickScrollButtons()
  970. {
  971. if (!_uiCreated)
  972. {
  973. return;
  974. }
  975. _pageUpFastButton.interactable = _beatUi.TableViewPageUpButton.interactable;
  976. _pageUpFastButton.gameObject.SetActive(_beatUi.TableViewPageUpButton.IsActive());
  977. _pageDownFastButton.interactable = _beatUi.TableViewPageDownButton.interactable;
  978. _pageDownFastButton.gameObject.SetActive(_beatUi.TableViewPageDownButton.IsActive());
  979. }
  980. /// <summary>
  981. /// TODO - evaluate this sillyness...
  982. /// </summary>
  983. /// <returns></returns>
  984. public IEnumerator RefreshQuickScrollButtonsAsync()
  985. {
  986. yield return new WaitForEndOfFrame();
  987. RefreshQuickScrollButtons();
  988. }
  989. /// <summary>
  990. /// Update delete button state. Enable for custom levels, disable for all else.
  991. /// </summary>
  992. /// <param name="levelId"></param>
  993. public void UpdateDeleteButtonState(String levelId)
  994. {
  995. if (_deleteButton == null)
  996. {
  997. return;
  998. }
  999. _deleteButton.gameObject.SetActive(levelId.Length >= 32);
  1000. }
  1001. /// <summary>
  1002. /// Show the UI.
  1003. /// </summary>
  1004. public void Show()
  1005. {
  1006. Logger.Trace("Show SongBrowserUI()");
  1007. this.SetVisibility(true);
  1008. }
  1009. /// <summary>
  1010. /// Hide the UI.
  1011. /// </summary>
  1012. public void Hide()
  1013. {
  1014. Logger.Trace("Hide SongBrowserUI()");
  1015. this.SetVisibility(false);
  1016. }
  1017. /// <summary>
  1018. /// Handle showing or hiding UI logic.
  1019. /// </summary>
  1020. /// <param name="visible"></param>
  1021. private void SetVisibility(bool visible)
  1022. {
  1023. // UI not created, nothing visible to hide...
  1024. if (!_uiCreated)
  1025. {
  1026. return;
  1027. }
  1028. _ppStatButton?.gameObject.SetActive(visible);
  1029. _starStatButton?.gameObject.SetActive(visible);
  1030. _njsStatButton?.gameObject.SetActive(visible);
  1031. RefreshOuterUIState(visible == true ? UIState.Main : UIState.Disabled);
  1032. _deleteButton?.gameObject.SetActive(visible);
  1033. _pageUpFastButton?.gameObject.SetActive(visible);
  1034. _pageDownFastButton?.gameObject.SetActive(visible);
  1035. }
  1036. /// <summary>
  1037. /// Update the top UI state.
  1038. /// Hides the outer ui, sort, and filter buttons depending on the state.
  1039. /// </summary>
  1040. private void RefreshOuterUIState(UIState state)
  1041. {
  1042. bool sortButtons = false;
  1043. bool filterButtons = false;
  1044. bool outerButtons = false;
  1045. bool clearButton = true;
  1046. if (state == UIState.SortBy)
  1047. {
  1048. sortButtons = true;
  1049. }
  1050. else if (state == UIState.FilterBy)
  1051. {
  1052. filterButtons = true;
  1053. }
  1054. else if (state == UIState.Main)
  1055. {
  1056. outerButtons = true;
  1057. }
  1058. else
  1059. {
  1060. clearButton = false;
  1061. }
  1062. _sortButtonGroup.ForEach(x => x.Button.gameObject.SetActive(sortButtons));
  1063. _filterButtonGroup.ForEach(x => x.Button.gameObject.SetActive(filterButtons));
  1064. _sortByButton?.gameObject.SetActive(outerButtons);
  1065. _sortByDisplay?.gameObject.SetActive(outerButtons);
  1066. _filterByButton?.gameObject.SetActive(outerButtons);
  1067. _filterByDisplay?.gameObject.SetActive(outerButtons);
  1068. _clearSortFilterButton?.gameObject.SetActive(clearButton);
  1069. _randomButton?.gameObject.SetActive(outerButtons);
  1070. RefreshCurrentSelectionDisplay();
  1071. _currentUiState = state;
  1072. }
  1073. /// <summary>
  1074. /// Adjust the text field of the sort by and filter by displays.
  1075. /// </summary>
  1076. private void RefreshCurrentSelectionDisplay()
  1077. {
  1078. string sortByDisplay;
  1079. if (_model.Settings.sortMode == SongSortMode.Default)
  1080. {
  1081. sortByDisplay = "Title";
  1082. }
  1083. else
  1084. {
  1085. sortByDisplay = _model.Settings.sortMode.ToString();
  1086. }
  1087. _sortByDisplay.SetButtonText(sortByDisplay);
  1088. if (_model.Settings.filterMode != SongFilterMode.Custom)
  1089. {
  1090. // Custom SongFilterMod implies that another mod has modified the text of this button (do not overwrite)
  1091. _filterByDisplay.SetButtonText(_model.Settings.filterMode.ToString());
  1092. }
  1093. }
  1094. /// <summary>
  1095. /// Adjust the UI colors.
  1096. /// </summary>
  1097. public void RefreshSortButtonUI()
  1098. {
  1099. if (!_uiCreated)
  1100. {
  1101. return;
  1102. }
  1103. foreach (SongSortButton sortButton in _sortButtonGroup)
  1104. {
  1105. if (sortButton.SortMode.NeedsScoreSaberData() && !SongDataCore.Plugin.Songs.IsDataAvailable())
  1106. {
  1107. sortButton.Button.SetButtonUnderlineColor(Color.gray);
  1108. }
  1109. else
  1110. {
  1111. sortButton.Button.SetButtonUnderlineColor(Color.white);
  1112. }
  1113. if (sortButton.SortMode == _model.Settings.sortMode)
  1114. {
  1115. if (this._model.Settings.invertSortResults)
  1116. {
  1117. sortButton.Button.SetButtonUnderlineColor(Color.red);
  1118. }
  1119. else
  1120. {
  1121. sortButton.Button.SetButtonUnderlineColor(Color.green);
  1122. }
  1123. }
  1124. }
  1125. foreach (SongFilterButton filterButton in _filterButtonGroup)
  1126. {
  1127. filterButton.Button.SetButtonUnderlineColor(Color.white);
  1128. if (filterButton.FilterMode == _model.Settings.filterMode)
  1129. {
  1130. filterButton.Button.SetButtonUnderlineColor(Color.green);
  1131. }
  1132. }
  1133. if (this._model.Settings.invertSortResults)
  1134. {
  1135. _sortByDisplay.SetButtonUnderlineColor(Color.red);
  1136. }
  1137. else
  1138. {
  1139. _sortByDisplay.SetButtonUnderlineColor(Color.green);
  1140. }
  1141. if (this._model.Settings.filterMode != SongFilterMode.None)
  1142. {
  1143. _filterByDisplay.SetButtonUnderlineColor(Color.green);
  1144. }
  1145. else
  1146. {
  1147. _filterByDisplay.SetButtonUnderlineColor(Color.white);
  1148. }
  1149. }
  1150. /// <summary>
  1151. ///
  1152. /// </summary>
  1153. public void RefreshSongList()
  1154. {
  1155. if (!_uiCreated)
  1156. {
  1157. return;
  1158. }
  1159. _beatUi.RefreshSongList(_model.LastSelectedLevelId);
  1160. }
  1161. /// <summary>
  1162. /// Helper for updating the model (which updates the song list)
  1163. /// </summary>
  1164. public void UpdateLevelDataModel()
  1165. {
  1166. try
  1167. {
  1168. Logger.Trace("UpdateLevelDataModel()");
  1169. // get a current beatmap characteristic...
  1170. if (_model.CurrentBeatmapCharacteristicSO == null && _uiCreated)
  1171. {
  1172. _model.CurrentBeatmapCharacteristicSO = _beatUi.BeatmapCharacteristicSelectionViewController.GetPrivateField<BeatmapCharacteristicSO>("_selectedBeatmapCharacteristic");
  1173. }
  1174. _model.UpdateLevelRecords();
  1175. }
  1176. catch (Exception e)
  1177. {
  1178. Logger.Exception("SongBrowser UI crashed trying to update the internal song lists: ", e);
  1179. }
  1180. }
  1181. /// <summary>
  1182. /// Logic for fixing BeatSaber's level pack selection bugs.
  1183. /// </summary>
  1184. public bool UpdateLevelCollectionSelection()
  1185. {
  1186. if (_uiCreated)
  1187. {
  1188. IAnnotatedBeatmapLevelCollection currentSelected = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection();
  1189. Logger.Debug("Updating level collection, current selected level collection: {0}", currentSelected);
  1190. // select category
  1191. if (!String.IsNullOrEmpty(_model.Settings.currentLevelCategoryName))
  1192. {
  1193. _selectingCategory = true;
  1194. _beatUi.SelectLevelCategory(_model.Settings.currentLevelCategoryName);
  1195. _selectingCategory = false;
  1196. }
  1197. // select collection
  1198. if (String.IsNullOrEmpty(_model.Settings.currentLevelCollectionName))
  1199. {
  1200. if (currentSelected == null && String.IsNullOrEmpty(_model.Settings.currentLevelCategoryName))
  1201. {
  1202. Logger.Debug("No level collection selected, acquiring the first available, likely OST1...");
  1203. currentSelected = _beatUi.BeatmapLevelsModel.allLoadedBeatmapLevelPackCollection.beatmapLevelPacks[0];
  1204. }
  1205. }
  1206. else if (currentSelected == null || (currentSelected.collectionName != _model.Settings.currentLevelCollectionName))
  1207. {
  1208. Logger.Debug("Automatically selecting level collection: {0}", _model.Settings.currentLevelCollectionName);
  1209. _beatUi.LevelFilteringNavigationController.didSelectAnnotatedBeatmapLevelCollectionEvent -= _levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent;
  1210. _lastLevelCollection = _beatUi.GetLevelCollectionByName(_model.Settings.currentLevelCollectionName);
  1211. if (_lastLevelCollection as PreviewBeatmapLevelPackSO)
  1212. {
  1213. Hide();
  1214. }
  1215. _beatUi.SelectLevelCollection(_model.Settings.currentLevelCollectionName);
  1216. _beatUi.LevelFilteringNavigationController.didSelectAnnotatedBeatmapLevelCollectionEvent += _levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent;
  1217. }
  1218. if (_lastLevelCollection == null)
  1219. {
  1220. if (currentSelected != null && currentSelected.collectionName != SongBrowserModel.FilteredSongsCollectionName && currentSelected.collectionName != SongBrowserModel.PlaylistSongsCollectionName)
  1221. {
  1222. _lastLevelCollection = currentSelected;
  1223. }
  1224. }
  1225. Logger.Debug("Current Level Collection is: {0}", _lastLevelCollection);
  1226. ProcessSongList();
  1227. }
  1228. return false;
  1229. }
  1230. }
  1231. }