SongBrowserUI.cs 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412
  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. int selectedIndex = levels.FindIndex(x => x.levelID == _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level.levelID);
  799. if (selectedIndex > -1)
  800. {
  801. var song = SongCore.Loader.CustomLevels.First(x => x.Value.levelID == _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID).Value;
  802. Logger.Info($"Deleting song: {song.customLevelPath}");
  803. SongCore.Loader.Instance.DeleteSong(song.customLevelPath);
  804. int removedLevels = levels.RemoveAll(x => x.levelID == _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level.levelID);
  805. Logger.Info($"Removed [{removedLevels}] level(s) from song list!");
  806. this.UpdateLevelDataModel();
  807. // if we have a song to select at the same index, set the last selected level id, UI updates takes care of the rest.
  808. if (selectedIndex < levels.Count)
  809. {
  810. if (levels[selectedIndex].levelID != null)
  811. {
  812. _model.LastSelectedLevelId = levels[selectedIndex].levelID;
  813. }
  814. }
  815. this.RefreshSongList();
  816. }
  817. }
  818. catch (Exception e)
  819. {
  820. Logger.Error("Unable to delete song! Exception: " + e);
  821. }
  822. }
  823. });
  824. _beatUi.ScreenSystem.titleViewController.gameObject.SetActive(false);
  825. _beatUi.LevelSelectionNavigationController.__PresentViewController(_deleteDialog, null);
  826. }
  827. /// <summary>
  828. /// Display the search keyboard
  829. /// </summary>
  830. void ShowSearchKeyboard()
  831. {
  832. var modalKbTag = new BeatSaberMarkupLanguage.Tags.ModalKeyboardTag();
  833. var modalKbView = modalKbTag.CreateObject(_beatUi.LevelSelectionNavigationController.rectTransform);
  834. modalKbView.gameObject.SetActive(true);
  835. var modalKb = modalKbView.GetComponent<ModalKeyboard>();
  836. modalKb.gameObject.SetActive(true);
  837. modalKb.keyboard.EnterPressed += SearchViewControllerSearchButtonPressed;
  838. modalKb.modalView.Show(true, true);
  839. }
  840. /// <summary>
  841. /// Handle search.
  842. /// </summary>
  843. /// <param name="searchFor"></param>
  844. private void SearchViewControllerSearchButtonPressed(string searchFor)
  845. {
  846. Logger.Debug("Searching for \"{0}\"...", searchFor);
  847. _model.Settings.filterMode = SongFilterMode.Search;
  848. _model.Settings.searchTerms.Insert(0, searchFor);
  849. _model.Settings.Save();
  850. _model.LastSelectedLevelId = null;
  851. ProcessSongList();
  852. RefreshSongUI();
  853. }
  854. /// <summary>
  855. /// Make big jumps in the song list.
  856. /// </summary>
  857. /// <param name="numJumps"></param>
  858. private void JumpSongList(int numJumps, float segmentPercent)
  859. {
  860. var levels = _beatUi.GetCurrentLevelCollectionLevels();
  861. if (levels == null)
  862. {
  863. return;
  864. }
  865. int totalSize = levels.Count();
  866. int segmentSize = (int)(totalSize * segmentPercent);
  867. // Jump at least one scree size.
  868. if (segmentSize < LIST_ITEMS_VISIBLE_AT_ONCE)
  869. {
  870. segmentSize = LIST_ITEMS_VISIBLE_AT_ONCE;
  871. }
  872. int currentRow = _beatUi.LevelCollectionTableView.GetPrivateField<int>("_selectedRow");
  873. int jumpDirection = Math.Sign(numJumps);
  874. int newRow = currentRow + (jumpDirection * segmentSize);
  875. if (newRow <= 0)
  876. {
  877. newRow = 0;
  878. }
  879. else if (newRow >= totalSize)
  880. {
  881. newRow = totalSize - 1;
  882. }
  883. Logger.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
  884. _beatUi.ScrollToLevelByRow(newRow);
  885. RefreshQuickScrollButtons();
  886. }
  887. /// <summary>
  888. /// Update GUI elements that show score saber data.
  889. /// </summary>
  890. public void RefreshScoreSaberData(IPreviewBeatmapLevel level)
  891. {
  892. Logger.Trace("RefreshScoreSaberData({0})", level.levelID);
  893. if (!SongDataCore.Plugin.Songs.IsDataAvailable())
  894. {
  895. return;
  896. }
  897. BeatmapDifficulty difficulty = _beatUi.LevelDifficultyViewController.selectedDifficulty;
  898. string difficultyString = difficulty.ToString();
  899. if (difficultyString.Equals("ExpertPlus"))
  900. {
  901. difficultyString = "Expert+";
  902. }
  903. Logger.Debug(difficultyString);
  904. // Check if we have data for this song
  905. Logger.Debug("Checking if have info for song {0}", level.songName);
  906. var hash = SongBrowserModel.GetSongHash(level.levelID);
  907. if (SongDataCore.Plugin.Songs.Data.Songs.ContainsKey(hash))
  908. {
  909. Logger.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
  910. BeatStarSong scoreSaberSong = SongDataCore.Plugin.Songs.Data.Songs[hash];
  911. BeatStarSongDifficultyStats scoreSaberSongDifficulty = scoreSaberSong.diffs.FirstOrDefault(x => String.Equals(x.diff, difficultyString));
  912. if (scoreSaberSongDifficulty != null)
  913. {
  914. Logger.Debug("Display pp for song.");
  915. double pp = scoreSaberSongDifficulty.pp;
  916. double star = scoreSaberSongDifficulty.star;
  917. BeatSaberUI.SetStatButtonText(_ppStatButton, String.Format("{0:0.#}", pp));
  918. BeatSaberUI.SetStatButtonText(_starStatButton, String.Format("{0:0.#}", star));
  919. }
  920. else
  921. {
  922. BeatSaberUI.SetStatButtonText(_ppStatButton, "NA");
  923. BeatSaberUI.SetStatButtonText(_starStatButton, "NA");
  924. }
  925. }
  926. else
  927. {
  928. BeatSaberUI.SetStatButtonText(_ppStatButton, "NA");
  929. BeatSaberUI.SetStatButtonText(_starStatButton, "NA");
  930. }
  931. Logger.Debug("Done refreshing score saber stats.");
  932. }
  933. /// <summary>
  934. /// Helper to refresh the NJS widget.
  935. /// </summary>
  936. /// <param name="noteJumpMovementSpeed"></param>
  937. private void RefreshNoteJumpSpeed(float noteJumpMovementSpeed, float noteJumpStartBeatOffset)
  938. {
  939. BeatSaberUI.SetStatButtonText(_njsStatButton, String.Format("{0}", noteJumpMovementSpeed));
  940. BeatSaberUI.SetStatButtonText(_noteJumpStartBeatOffsetLabel, String.Format("{0}", noteJumpStartBeatOffset));
  941. }
  942. /// <summary>
  943. /// Update interactive state of the quick scroll buttons.
  944. /// </summary>
  945. private void RefreshQuickScrollButtons()
  946. {
  947. if (!_uiCreated)
  948. {
  949. return;
  950. }
  951. _pageUpFastButton.interactable = _beatUi.TableViewPageUpButton.interactable;
  952. _pageUpFastButton.gameObject.SetActive(_beatUi.TableViewPageUpButton.IsActive());
  953. _pageDownFastButton.interactable = _beatUi.TableViewPageDownButton.interactable;
  954. _pageDownFastButton.gameObject.SetActive(_beatUi.TableViewPageDownButton.IsActive());
  955. }
  956. /// <summary>
  957. /// TODO - evaluate this sillyness...
  958. /// </summary>
  959. /// <returns></returns>
  960. public IEnumerator RefreshQuickScrollButtonsAsync()
  961. {
  962. yield return new WaitForEndOfFrame();
  963. RefreshQuickScrollButtons();
  964. }
  965. /// <summary>
  966. /// Update delete button state. Enable for custom levels, disable for all else.
  967. /// </summary>
  968. /// <param name="levelId"></param>
  969. public void UpdateDeleteButtonState(String levelId)
  970. {
  971. if (_deleteButton == null)
  972. {
  973. return;
  974. }
  975. _deleteButton.gameObject.SetActive(levelId.Length >= 32);
  976. }
  977. /// <summary>
  978. /// Show the UI.
  979. /// </summary>
  980. public void Show()
  981. {
  982. Logger.Trace("Show SongBrowserUI()");
  983. this.SetVisibility(true);
  984. }
  985. /// <summary>
  986. /// Hide the UI.
  987. /// </summary>
  988. public void Hide()
  989. {
  990. Logger.Trace("Hide SongBrowserUI()");
  991. this.SetVisibility(false);
  992. }
  993. /// <summary>
  994. /// Handle showing or hiding UI logic.
  995. /// </summary>
  996. /// <param name="visible"></param>
  997. private void SetVisibility(bool visible)
  998. {
  999. // UI not created, nothing visible to hide...
  1000. if (!_uiCreated)
  1001. {
  1002. return;
  1003. }
  1004. _ppStatButton?.gameObject.SetActive(visible);
  1005. _starStatButton?.gameObject.SetActive(visible);
  1006. _njsStatButton?.gameObject.SetActive(visible);
  1007. RefreshOuterUIState(visible == true ? UIState.Main : UIState.Disabled);
  1008. _deleteButton?.gameObject.SetActive(visible);
  1009. _pageUpFastButton?.gameObject.SetActive(visible);
  1010. _pageDownFastButton?.gameObject.SetActive(visible);
  1011. }
  1012. /// <summary>
  1013. /// Update the top UI state.
  1014. /// Hides the outer ui, sort, and filter buttons depending on the state.
  1015. /// </summary>
  1016. private void RefreshOuterUIState(UIState state)
  1017. {
  1018. bool sortButtons = false;
  1019. bool filterButtons = false;
  1020. bool outerButtons = false;
  1021. bool clearButton = true;
  1022. if (state == UIState.SortBy)
  1023. {
  1024. sortButtons = true;
  1025. }
  1026. else if (state == UIState.FilterBy)
  1027. {
  1028. filterButtons = true;
  1029. }
  1030. else if (state == UIState.Main)
  1031. {
  1032. outerButtons = true;
  1033. }
  1034. else
  1035. {
  1036. clearButton = false;
  1037. }
  1038. _sortButtonGroup.ForEach(x => x.Button.gameObject.SetActive(sortButtons));
  1039. _filterButtonGroup.ForEach(x => x.Button.gameObject.SetActive(filterButtons));
  1040. _sortByButton?.gameObject.SetActive(outerButtons);
  1041. _sortByDisplay?.gameObject.SetActive(outerButtons);
  1042. _filterByButton?.gameObject.SetActive(outerButtons);
  1043. _filterByDisplay?.gameObject.SetActive(outerButtons);
  1044. _clearSortFilterButton?.gameObject.SetActive(clearButton);
  1045. _randomButton?.gameObject.SetActive(outerButtons);
  1046. RefreshCurrentSelectionDisplay();
  1047. _currentUiState = state;
  1048. }
  1049. /// <summary>
  1050. /// Adjust the text field of the sort by and filter by displays.
  1051. /// </summary>
  1052. private void RefreshCurrentSelectionDisplay()
  1053. {
  1054. string sortByDisplay;
  1055. if (_model.Settings.sortMode == SongSortMode.Default)
  1056. {
  1057. sortByDisplay = "Title";
  1058. }
  1059. else
  1060. {
  1061. sortByDisplay = _model.Settings.sortMode.ToString();
  1062. }
  1063. _sortByDisplay.SetButtonText(sortByDisplay);
  1064. if (_model.Settings.filterMode != SongFilterMode.Custom)
  1065. {
  1066. // Custom SongFilterMod implies that another mod has modified the text of this button (do not overwrite)
  1067. _filterByDisplay.SetButtonText(_model.Settings.filterMode.ToString());
  1068. }
  1069. }
  1070. /// <summary>
  1071. /// Adjust the UI colors.
  1072. /// </summary>
  1073. public void RefreshSortButtonUI()
  1074. {
  1075. if (!_uiCreated)
  1076. {
  1077. return;
  1078. }
  1079. foreach (SongSortButton sortButton in _sortButtonGroup)
  1080. {
  1081. if (sortButton.SortMode.NeedsScoreSaberData() && !SongDataCore.Plugin.Songs.IsDataAvailable())
  1082. {
  1083. sortButton.Button.SetButtonUnderlineColor(Color.gray);
  1084. }
  1085. else
  1086. {
  1087. sortButton.Button.SetButtonUnderlineColor(Color.white);
  1088. }
  1089. if (sortButton.SortMode == _model.Settings.sortMode)
  1090. {
  1091. if (this._model.Settings.invertSortResults)
  1092. {
  1093. sortButton.Button.SetButtonUnderlineColor(Color.red);
  1094. }
  1095. else
  1096. {
  1097. sortButton.Button.SetButtonUnderlineColor(Color.green);
  1098. }
  1099. }
  1100. }
  1101. foreach (SongFilterButton filterButton in _filterButtonGroup)
  1102. {
  1103. filterButton.Button.SetButtonUnderlineColor(Color.white);
  1104. if (filterButton.FilterMode == _model.Settings.filterMode)
  1105. {
  1106. filterButton.Button.SetButtonUnderlineColor(Color.green);
  1107. }
  1108. }
  1109. if (this._model.Settings.invertSortResults)
  1110. {
  1111. _sortByDisplay.SetButtonUnderlineColor(Color.red);
  1112. }
  1113. else
  1114. {
  1115. _sortByDisplay.SetButtonUnderlineColor(Color.green);
  1116. }
  1117. if (this._model.Settings.filterMode != SongFilterMode.None)
  1118. {
  1119. _filterByDisplay.SetButtonUnderlineColor(Color.green);
  1120. }
  1121. else
  1122. {
  1123. _filterByDisplay.SetButtonUnderlineColor(Color.white);
  1124. }
  1125. }
  1126. /// <summary>
  1127. ///
  1128. /// </summary>
  1129. public void RefreshSongList()
  1130. {
  1131. if (!_uiCreated)
  1132. {
  1133. return;
  1134. }
  1135. _beatUi.RefreshSongList(_model.LastSelectedLevelId);
  1136. }
  1137. /// <summary>
  1138. /// Helper for updating the model (which updates the song list)
  1139. /// </summary>
  1140. public void UpdateLevelDataModel()
  1141. {
  1142. try
  1143. {
  1144. Logger.Trace("UpdateLevelDataModel()");
  1145. // get a current beatmap characteristic...
  1146. if (_model.CurrentBeatmapCharacteristicSO == null && _uiCreated)
  1147. {
  1148. _model.CurrentBeatmapCharacteristicSO = _beatUi.BeatmapCharacteristicSelectionViewController.GetPrivateField<BeatmapCharacteristicSO>("_selectedBeatmapCharacteristic");
  1149. }
  1150. _model.UpdateLevelRecords();
  1151. }
  1152. catch (Exception e)
  1153. {
  1154. Logger.Exception("SongBrowser UI crashed trying to update the internal song lists: ", e);
  1155. }
  1156. }
  1157. /// <summary>
  1158. /// Logic for fixing BeatSaber's level pack selection bugs.
  1159. /// </summary>
  1160. public bool UpdateLevelCollectionSelection()
  1161. {
  1162. if (_uiCreated)
  1163. {
  1164. IAnnotatedBeatmapLevelCollection currentSelected = _beatUi.GetCurrentSelectedAnnotatedBeatmapLevelCollection();
  1165. Logger.Debug("Updating level collection, current selected level collection: {0}", currentSelected);
  1166. // select category
  1167. if (!String.IsNullOrEmpty(_model.Settings.currentLevelCategoryName))
  1168. {
  1169. _selectingCategory = true;
  1170. _beatUi.SelectLevelCategory(_model.Settings.currentLevelCategoryName);
  1171. _selectingCategory = false;
  1172. }
  1173. // select collection
  1174. if (String.IsNullOrEmpty(_model.Settings.currentLevelCollectionName))
  1175. {
  1176. if (currentSelected == null && String.IsNullOrEmpty(_model.Settings.currentLevelCategoryName))
  1177. {
  1178. Logger.Debug("No level collection selected, acquiring the first available, likely OST1...");
  1179. currentSelected = _beatUi.BeatmapLevelsModel.allLoadedBeatmapLevelPackCollection.beatmapLevelPacks[0];
  1180. }
  1181. }
  1182. else if (currentSelected == null || (currentSelected.collectionName != _model.Settings.currentLevelCollectionName))
  1183. {
  1184. Logger.Debug("Automatically selecting level collection: {0}", _model.Settings.currentLevelCollectionName);
  1185. _beatUi.LevelFilteringNavigationController.didSelectAnnotatedBeatmapLevelCollectionEvent -= _levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent;
  1186. _lastLevelCollection = _beatUi.GetLevelCollectionByName(_model.Settings.currentLevelCollectionName);
  1187. if (_lastLevelCollection as PreviewBeatmapLevelPackSO)
  1188. {
  1189. Hide();
  1190. }
  1191. _beatUi.SelectLevelCollection(_model.Settings.currentLevelCollectionName);
  1192. _beatUi.LevelFilteringNavigationController.didSelectAnnotatedBeatmapLevelCollectionEvent += _levelFilteringNavController_didSelectAnnotatedBeatmapLevelCollectionEvent;
  1193. }
  1194. if (_lastLevelCollection == null)
  1195. {
  1196. if (currentSelected != null && currentSelected.collectionName != SongBrowserModel.FilteredSongsCollectionName && currentSelected.collectionName != SongBrowserModel.PlaylistSongsCollectionName)
  1197. {
  1198. _lastLevelCollection = currentSelected;
  1199. }
  1200. }
  1201. Logger.Debug("Current Level Collection is: {0}", _lastLevelCollection);
  1202. ProcessSongList();
  1203. }
  1204. return false;
  1205. }
  1206. }
  1207. }