SongBrowserUI.cs 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430
  1. using UnityEngine;
  2. using System.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEngine.UI;
  6. using HMUI;
  7. using VRUI;
  8. using SongBrowser.DataAccess;
  9. using TMPro;
  10. using Logger = SongBrowser.Logging.Logger;
  11. using SongBrowser.DataAccess.BeatSaverApi;
  12. using System.Collections;
  13. using SongCore.Utilities;
  14. using SongBrowser.Internals;
  15. using CustomUI.BeatSaber;
  16. namespace SongBrowser.UI
  17. {
  18. public enum UIState
  19. {
  20. Disabled,
  21. Main,
  22. SortBy,
  23. FilterBy
  24. }
  25. /// <summary>
  26. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  27. /// </summary>
  28. public class SongBrowserUI : MonoBehaviour
  29. {
  30. // Logging
  31. public const String Name = "SongBrowserUI";
  32. private const float SEGMENT_PERCENT = 0.1f;
  33. private const int LIST_ITEMS_VISIBLE_AT_ONCE = 6;
  34. // BeatSaber Internal UI structures
  35. DataAccess.BeatSaberUIController _beatUi;
  36. // New UI Elements
  37. private List<SongSortButton> _sortButtonGroup;
  38. private List<SongFilterButton> _filterButtonGroup;
  39. private Button _sortByButton;
  40. private Button _sortByDisplay;
  41. private Button _filterByButton;
  42. private Button _filterByDisplay;
  43. private Button _randomButton;
  44. private Button _clearSortFilterButton;
  45. private Button _addFavoriteButton;
  46. private SimpleDialogPromptViewController _deleteDialog;
  47. private Button _deleteButton;
  48. private Button _pageUpFastButton;
  49. private Button _pageDownFastButton;
  50. private SearchKeyboardViewController _searchViewController;
  51. private PlaylistFlowCoordinator _playListFlowCoordinator;
  52. private RectTransform _ppStatButton;
  53. private RectTransform _starStatButton;
  54. private RectTransform _njsStatButton;
  55. private Sprite _currentAddFavoriteButtonSprite;
  56. // Model
  57. private SongBrowserModel _model;
  58. public SongBrowserModel Model
  59. {
  60. get
  61. {
  62. return _model;
  63. }
  64. }
  65. // UI Created
  66. private bool _uiCreated = false;
  67. private UIState _currentUiState = UIState.Disabled;
  68. /// <summary>
  69. /// Constructor
  70. /// </summary>
  71. public SongBrowserUI() : base()
  72. {
  73. if (_model == null)
  74. {
  75. _model = new SongBrowserModel();
  76. }
  77. _model.Init();
  78. }
  79. /// <summary>
  80. /// Builds the UI for this plugin.
  81. /// </summary>
  82. public void CreateUI(MainMenuViewController.MenuButton mode)
  83. {
  84. Logger.Trace("CreateUI()");
  85. // Determine the flow controller to use
  86. FlowCoordinator flowCoordinator = null;
  87. if (mode == MainMenuViewController.MenuButton.SoloFreePlay)
  88. {
  89. Logger.Debug("Entering SOLO mode...");
  90. flowCoordinator = Resources.FindObjectsOfTypeAll<SoloFreePlayFlowCoordinator>().First();
  91. }
  92. else if (mode == MainMenuViewController.MenuButton.Party)
  93. {
  94. Logger.Debug("Entering PARTY mode...");
  95. flowCoordinator = Resources.FindObjectsOfTypeAll<PartyFreePlayFlowCoordinator>().First();
  96. }
  97. else
  98. {
  99. Logger.Debug("Entering SOLO CAMPAIGN mode...");
  100. flowCoordinator = Resources.FindObjectsOfTypeAll<CampaignFlowCoordinator>().First();
  101. }
  102. _beatUi = new DataAccess.BeatSaberUIController(flowCoordinator);
  103. // returning to the menu and switching modes could trigger this.
  104. if (_uiCreated)
  105. {
  106. return;
  107. }
  108. try
  109. {
  110. if (_playListFlowCoordinator == null)
  111. {
  112. _playListFlowCoordinator = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  113. _playListFlowCoordinator.didFinishEvent += HandleDidSelectPlaylist;
  114. }
  115. // delete dialog
  116. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(_beatUi.SimpleDialogPromptViewControllerPrefab);
  117. this._deleteDialog.name = "DeleteDialogPromptViewController";
  118. this._deleteDialog.gameObject.SetActive(false);
  119. // create song browser main ui
  120. CreateOuterUi();
  121. CreateSortButtons();
  122. CreateFilterButtons();
  123. CreateAddFavoritesButton();
  124. CreateDeleteButton();
  125. CreateFastPageButtons();
  126. RefreshSortButtonUI();
  127. this.InstallHandlers();
  128. this.ModifySongStatsPanel();
  129. this.ResizeSongUI();
  130. _uiCreated = true;
  131. Logger.Debug("Done Creating UI...");
  132. }
  133. catch (Exception e)
  134. {
  135. Logger.Exception("Exception during CreateUI: ", e);
  136. }
  137. }
  138. /// <summary>
  139. /// Create the outer ui.
  140. /// </summary>
  141. private void CreateOuterUi()
  142. {
  143. Logger.Debug("Creating outer UI...");
  144. float clearButtonX = -32.5f;
  145. float clearButtonY = 34.5f;
  146. float buttonY = 37f;
  147. float buttonHeight = 5.0f;
  148. float sortByButtonX = -22.5f + buttonHeight;
  149. float outerButtonFontSize = 3.0f;
  150. float displayButtonFontSize = 2.5f;
  151. float outerButtonWidth = 24.0f;
  152. float randomButtonWidth = 8.0f;
  153. // clear button
  154. _clearSortFilterButton = CreateClearButton(clearButtonX, clearButtonY, buttonHeight, () =>
  155. {
  156. if (_currentUiState == UIState.FilterBy || _currentUiState == UIState.SortBy)
  157. {
  158. RefreshOuterUIState(UIState.Main);
  159. }
  160. else
  161. {
  162. OnClearButtonClickEvent();
  163. }
  164. });
  165. // create SortBy button and its display
  166. float curX = sortByButtonX;
  167. _sortByButton = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  168. {
  169. RefreshOuterUIState(UIState.SortBy);
  170. }, "Sort By");
  171. _sortByButton.SetButtonTextSize(outerButtonFontSize);
  172. _sortByButton.ToggleWordWrapping(false);
  173. curX += outerButtonWidth;
  174. _sortByDisplay = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  175. {
  176. OnSortButtonClickEvent(_model.Settings.sortMode);
  177. }, "");
  178. _sortByDisplay.SetButtonTextSize(displayButtonFontSize);
  179. _sortByDisplay.ToggleWordWrapping(false);
  180. curX += outerButtonWidth;
  181. // create FilterBy button and its display
  182. _filterByButton = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  183. {
  184. RefreshOuterUIState(UIState.FilterBy);
  185. }, "Filter By");
  186. _filterByButton.SetButtonTextSize(outerButtonFontSize);
  187. _filterByButton.ToggleWordWrapping(false);
  188. curX += outerButtonWidth;
  189. _filterByDisplay = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
  190. {
  191. CancelFilter();
  192. RefreshSongUI();
  193. }, "");
  194. _filterByDisplay.SetButtonTextSize(displayButtonFontSize);
  195. _filterByDisplay.ToggleWordWrapping(false);
  196. // random button
  197. _randomButton = _beatUi.LevelPackLevelsViewController.CreateUIButton("HowToPlayButton", new Vector2(curX + (outerButtonWidth / 2.0f) + (randomButtonWidth / 2.0f), clearButtonY), new Vector2(randomButtonWidth, buttonHeight), () =>
  198. {
  199. OnSortButtonClickEvent(SongSortMode.Random);
  200. }, "",
  201. Base64Sprites.RandomIcon);
  202. _randomButton.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(0, 0, 0, 0);
  203. var textRect = _randomButton.GetComponentsInChildren<RectTransform>(true).FirstOrDefault(c => c.name == "Text");
  204. if (textRect != null)
  205. {
  206. UnityEngine.Object.Destroy(textRect.gameObject);
  207. }
  208. UIBuilder.SetButtonBorderActive(_randomButton, false);
  209. }
  210. /// <summary>
  211. /// Create the back button
  212. /// </summary>
  213. /// <returns></returns>
  214. private Button CreateClearButton(float x, float y, float h, UnityEngine.Events.UnityAction callback)
  215. {
  216. Button b = _beatUi.LevelPackLevelsViewController.CreateUIButton("HowToPlayButton", new Vector2(x, y), new Vector2(h, h), callback, "", Base64Sprites.XIcon);
  217. b.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(1, 1, 0, 0);
  218. RectTransform textRect = b.GetComponentsInChildren<RectTransform>(true).FirstOrDefault(c => c.name == "Text");
  219. if (textRect != null)
  220. {
  221. UnityEngine.Object.Destroy(textRect.gameObject);
  222. }
  223. UIBuilder.SetButtonBorderActive(b, false);
  224. return b;
  225. }
  226. /// <summary>
  227. /// Create the sort button ribbon
  228. /// </summary>
  229. private void CreateSortButtons()
  230. {
  231. Logger.Debug("Create sort buttons...");
  232. float sortButtonFontSize = 2.25f;
  233. float sortButtonX = -23.0f;
  234. float sortButtonWidth = 12.25f;
  235. float buttonSpacing = 0.65f;
  236. float buttonY = 37f;
  237. float buttonHeight = 5.0f;
  238. string[] sortButtonNames = new string[]
  239. {
  240. "Title", "Author", "Newest", "YourPlays", "PP", "Stars", "UpVotes", "PlayCount", "Rating"
  241. };
  242. SongSortMode[] sortModes = new SongSortMode[]
  243. {
  244. SongSortMode.Default, SongSortMode.Author, SongSortMode.Newest, SongSortMode.YourPlayCount, SongSortMode.PP, SongSortMode.Stars, SongSortMode.UpVotes, SongSortMode.PlayCount, SongSortMode.Rating
  245. };
  246. _sortButtonGroup = new List<SongSortButton>();
  247. for (int i = 0; i < sortButtonNames.Length; i++)
  248. {
  249. float curButtonX = sortButtonX + (sortButtonWidth * i) + (buttonSpacing * i);
  250. SongSortButton sortButton = new SongSortButton();
  251. sortButton.SortMode = sortModes[i];
  252. sortButton.Button = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton",
  253. new Vector2(curButtonX, buttonY), new Vector2(sortButtonWidth, buttonHeight),
  254. () =>
  255. {
  256. OnSortButtonClickEvent(sortButton.SortMode);
  257. RefreshOuterUIState(UIState.Main);
  258. },
  259. sortButtonNames[i]);
  260. sortButton.Button.SetButtonTextSize(sortButtonFontSize);
  261. sortButton.Button.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(4, 4, 2, 2);
  262. sortButton.Button.ToggleWordWrapping(false);
  263. sortButton.Button.name = "Sort" + sortModes[i].ToString() + "Button";
  264. _sortButtonGroup.Add(sortButton);
  265. }
  266. }
  267. /// <summary>
  268. /// Create the filter by buttons
  269. /// </summary>
  270. private void CreateFilterButtons()
  271. {
  272. Logger.Debug("Creating filter buttons...");
  273. float filterButtonFontSize = 2.25f;
  274. float filterButtonX = -23.0f;
  275. float filterButtonWidth = 12.25f;
  276. float buttonSpacing = 0.5f;
  277. float buttonY = 37f;
  278. float buttonHeight = 5.0f;
  279. List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>> filterButtonSetup = new List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>>()
  280. {
  281. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Favorites, OnFavoriteFilterButtonClickEvent, Base64Sprites.StarFullIcon),
  282. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Playlist, OnPlaylistButtonClickEvent, Base64Sprites.PlaylistIcon),
  283. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Search, OnSearchButtonClickEvent, Base64Sprites.SearchIcon),
  284. };
  285. _filterButtonGroup = new List<SongFilterButton>();
  286. for (int i = 0; i < filterButtonSetup.Count; i++)
  287. {
  288. Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite> t = filterButtonSetup[i];
  289. float curButtonX = filterButtonX + (filterButtonWidth * i) + (buttonSpacing * i);
  290. SongFilterButton filterButton = new SongFilterButton();
  291. filterButton.FilterMode = t.Item1;
  292. filterButton.Button = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton",
  293. new Vector2(curButtonX, buttonY), new Vector2(filterButtonWidth, buttonHeight),
  294. t.Item2,
  295. t.Item1.ToString());
  296. filterButton.Button.SetButtonTextSize(filterButtonFontSize);
  297. filterButton.Button.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(4, 4, 2, 2);
  298. filterButton.Button.ToggleWordWrapping(false);
  299. filterButton.Button.onClick.AddListener(() =>
  300. {
  301. RefreshOuterUIState(UIState.Main);
  302. });
  303. filterButton.Button.name = "Filter" + t.Item1.ToString() + "Button";
  304. _filterButtonGroup.Add(filterButton);
  305. }
  306. }
  307. /// <summary>
  308. /// Create the fast page up and down buttons
  309. /// </summary>
  310. private void CreateFastPageButtons()
  311. {
  312. Logger.Debug("Creating fast scroll button...");
  313. _pageUpFastButton = Instantiate(_beatUi.TableViewPageUpButton, _beatUi.LevelPackLevelsTableViewRectTransform, false);
  314. (_pageUpFastButton.transform as RectTransform).anchorMin = new Vector2(0.5f, 1f);
  315. (_pageUpFastButton.transform as RectTransform).anchorMax = new Vector2(0.5f, 1f);
  316. (_pageUpFastButton.transform as RectTransform).anchoredPosition = new Vector2(-26f, 0.25f);
  317. (_pageUpFastButton.transform as RectTransform).sizeDelta = new Vector2(8f, 6f);
  318. _pageUpFastButton.GetComponentsInChildren<RectTransform>().First(x => x.name == "BG").sizeDelta = new Vector2(8f, 6f);
  319. _pageUpFastButton.GetComponentsInChildren<UnityEngine.UI.Image>().First(x => x.name == "Arrow").sprite = Base64Sprites.DoubleArrow;
  320. _pageUpFastButton.onClick.AddListener(delegate ()
  321. {
  322. this.JumpSongList(-1, SEGMENT_PERCENT);
  323. });
  324. _pageDownFastButton = Instantiate(_beatUi.TableViewPageDownButton, _beatUi.LevelPackLevelsTableViewRectTransform, false);
  325. (_pageDownFastButton.transform as RectTransform).anchorMin = new Vector2(0.5f, 0f);
  326. (_pageDownFastButton.transform as RectTransform).anchorMax = new Vector2(0.5f, 0f);
  327. (_pageDownFastButton.transform as RectTransform).anchoredPosition = new Vector2(-26f, -1f);
  328. (_pageDownFastButton.transform as RectTransform).sizeDelta = new Vector2(8f, 6f);
  329. _pageDownFastButton.GetComponentsInChildren<RectTransform>().First(x => x.name == "BG").sizeDelta = new Vector2(8f, 6f);
  330. _pageDownFastButton.GetComponentsInChildren<UnityEngine.UI.Image>().First(x => x.name == "Arrow").sprite = Base64Sprites.DoubleArrow;
  331. _pageDownFastButton.onClick.AddListener(delegate ()
  332. {
  333. this.JumpSongList(1, SEGMENT_PERCENT);
  334. });
  335. }
  336. /// <summary>
  337. /// Create the +/- favorite button in the play button container.
  338. /// </summary>
  339. private void CreateAddFavoritesButton()
  340. {
  341. // Create add favorite button
  342. Logger.Debug("Creating Add to favorites button...");
  343. _addFavoriteButton = UIBuilder.CreateIconButton(_beatUi.PlayButtons, _beatUi.PracticeButton, Base64Sprites.AddToFavoritesIcon);
  344. _addFavoriteButton.onClick.AddListener(delegate () {
  345. ToggleSongInPlaylist();
  346. });
  347. }
  348. /// <summary>
  349. /// Create the delete button in the play button container
  350. /// </summary>
  351. private void CreateDeleteButton()
  352. {
  353. // Create delete button
  354. Logger.Debug("Creating delete button...");
  355. _deleteButton = UIBuilder.CreateIconButton(_beatUi.PlayButtons, _beatUi.PracticeButton, Base64Sprites.DeleteIcon);
  356. _deleteButton.onClick.AddListener(delegate () {
  357. HandleDeleteSelectedLevel();
  358. });
  359. }
  360. /// <summary>
  361. /// Resize the stats panel to fit more stats.
  362. /// </summary>
  363. private void ModifySongStatsPanel()
  364. {
  365. // modify details view
  366. Logger.Debug("Resizing Stats Panel...");
  367. var statsPanel = _beatUi.StandardLevelDetailView.GetPrivateField<LevelParamsPanel>("_levelParamsPanel");
  368. var statTransforms = statsPanel.GetComponentsInChildren<RectTransform>();
  369. var valueTexts = statsPanel.GetComponentsInChildren<TextMeshProUGUI>().Where(x => x.name == "ValueText").ToList();
  370. RectTransform panelRect = (statsPanel.transform as RectTransform);
  371. panelRect.sizeDelta = new Vector2(panelRect.sizeDelta.x * 1.2f, panelRect.sizeDelta.y * 1.2f);
  372. for (int i = 0; i < statTransforms.Length; i++)
  373. {
  374. var r = statTransforms[i];
  375. if (r.name == "Separator")
  376. {
  377. continue;
  378. }
  379. r.sizeDelta = new Vector2(r.sizeDelta.x * 0.75f, r.sizeDelta.y * 0.75f);
  380. }
  381. for (int i = 0; i < valueTexts.Count; i++)
  382. {
  383. var text = valueTexts[i];
  384. text.fontSize = 3.25f;
  385. }
  386. // inject our components
  387. _ppStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  388. UIBuilder.SetStatButtonIcon(_ppStatButton, Base64Sprites.GraphIcon);
  389. _starStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  390. UIBuilder.SetStatButtonIcon(_starStatButton, Base64Sprites.StarFullIcon);
  391. _njsStatButton = UnityEngine.Object.Instantiate(statTransforms[1], statsPanel.transform, false);
  392. UIBuilder.SetStatButtonIcon(_njsStatButton, Base64Sprites.SpeedIcon);
  393. // shrink title
  394. var titleText = _beatUi.LevelDetailViewController.GetComponentsInChildren<TextMeshProUGUI>(true).First(x => x.name == "SongNameText");
  395. titleText.fontSize = 5.0f;
  396. }
  397. /// <summary>
  398. /// Resize some of the song table elements.
  399. /// </summary>
  400. public void ResizeSongUI()
  401. {
  402. // Reposition the table view a bit
  403. _beatUi.LevelPackLevelsTableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  404. // Move the page up/down buttons a bit
  405. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_beatUi.LevelPackLevelsTableView, "_tableView");
  406. RectTransform pageUpButton = _beatUi.TableViewPageUpButton.transform as RectTransform;
  407. RectTransform pageDownButton = _beatUi.TableViewPageDownButton.transform as RectTransform;
  408. pageUpButton.anchoredPosition = new Vector2(pageUpButton.anchoredPosition.x, pageUpButton.anchoredPosition.y - 1f);
  409. pageDownButton.anchoredPosition = new Vector2(pageDownButton.anchoredPosition.x, pageDownButton.anchoredPosition.y + 1f);
  410. // shrink play button container
  411. RectTransform playContainerRect = _beatUi.StandardLevelDetailView.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayContainer");
  412. RectTransform playButtonsRect = playContainerRect.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayButtons");
  413. playButtonsRect.localScale = new Vector3(0.825f, 0.825f, 0.825f);
  414. }
  415. /// <summary>
  416. /// Add our handlers into BeatSaber.
  417. /// </summary>
  418. private void InstallHandlers()
  419. {
  420. // level pack, level, difficulty handlers, characteristics
  421. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_beatUi.LevelPackLevelsTableView, "_tableView");
  422. _beatUi.LevelPackLevelsViewController.didSelectLevelEvent -= OnDidSelectLevelEvent;
  423. _beatUi.LevelPackLevelsViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  424. _beatUi.LevelDetailViewController.didPresentContentEvent -= OnDidPresentContentEvent;
  425. _beatUi.LevelDetailViewController.didPresentContentEvent += OnDidPresentContentEvent;
  426. _beatUi.LevelDetailViewController.didChangeDifficultyBeatmapEvent -= OnDidChangeDifficultyEvent;
  427. _beatUi.LevelDetailViewController.didChangeDifficultyBeatmapEvent += OnDidChangeDifficultyEvent;
  428. _beatUi.LevelPacksTableView.didSelectPackEvent -= _levelPacksTableView_didSelectPackEvent;
  429. _beatUi.LevelPacksTableView.didSelectPackEvent += _levelPacksTableView_didSelectPackEvent;
  430. _beatUi.LevelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
  431. _beatUi.LevelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
  432. _beatUi.BeatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent -= OnDidSelectBeatmapCharacteristic;
  433. _beatUi.BeatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent += OnDidSelectBeatmapCharacteristic;
  434. // make sure the quick scroll buttons don't desync with regular scrolling
  435. _beatUi.TableViewPageDownButton.onClick.AddListener(delegate ()
  436. {
  437. StartCoroutine(RefreshQuickScrollButtonsAsync());
  438. });
  439. _beatUi.TableViewPageUpButton.onClick.AddListener(delegate ()
  440. {
  441. StartCoroutine(RefreshQuickScrollButtonsAsync());
  442. });
  443. // finished level
  444. ResultsViewController resultsViewController = _beatUi.LevelSelectionFlowCoordinator.GetPrivateField<ResultsViewController>("_resultsViewController");
  445. resultsViewController.continueButtonPressedEvent += ResultsViewController_continueButtonPressedEvent;
  446. }
  447. /// <summary>
  448. /// Helper to reduce code duplication...
  449. /// </summary>
  450. public void RefreshSongUI(bool scrollToLevel=true)
  451. {
  452. RefreshSongList(scrollToLevel);
  453. RefreshSortButtonUI();
  454. if (!scrollToLevel)
  455. {
  456. _beatUi.ScrollToLevelByRow(0);
  457. }
  458. RefreshQuickScrollButtons();
  459. RefreshCurrentSelectionDisplay();
  460. }
  461. /// <summary>
  462. /// External helper.
  463. /// </summary>
  464. public void ProcessSongList()
  465. {
  466. if (!_uiCreated)
  467. {
  468. return;
  469. }
  470. this._model.ProcessSongList(_beatUi.GetCurrentSelectedLevelPack());
  471. }
  472. /// <summary>
  473. /// Helper for common filter cancellation logic.
  474. /// </summary>
  475. public void CancelFilter()
  476. {
  477. _model.Settings.filterMode = SongFilterMode.None;
  478. SongCore.Loader.Instance.RefreshLevelPacks();
  479. }
  480. /// <summary>
  481. /// Handle updating the level pack selection after returning from a song.
  482. /// </summary>
  483. /// <param name="obj"></param>
  484. private void ResultsViewController_continueButtonPressedEvent(ResultsViewController obj)
  485. {
  486. StartCoroutine(this.UpdateLevelPackSelectionEndOfFrame());
  487. }
  488. /// <summary>
  489. /// TODO - evaluate this sillyness...
  490. /// </summary>
  491. /// <returns></returns>
  492. public IEnumerator UpdateLevelPackSelectionEndOfFrame()
  493. {
  494. yield return new WaitForEndOfFrame();
  495. try
  496. {
  497. bool didUpdateLevelPack = this.UpdateLevelPackSelection();
  498. if (!didUpdateLevelPack)
  499. {
  500. _model.ProcessSongList(_beatUi.GetCurrentSelectedLevelPack());
  501. }
  502. _beatUi.SelectAndScrollToLevel(_beatUi.LevelPackLevelsTableView, _model.LastSelectedLevelId);
  503. RefreshQuickScrollButtons();
  504. }
  505. catch (Exception e)
  506. {
  507. Logger.Exception("Exception:", e);
  508. }
  509. }
  510. /// <summary>
  511. /// Handler for level pack selection.
  512. /// </summary>
  513. /// <param name="arg1"></param>
  514. /// <param name="arg2"></param>
  515. private void _levelPacksTableView_didSelectPackEvent(LevelPacksTableView arg1, IBeatmapLevelPack arg2)
  516. {
  517. Logger.Trace("_levelPacksTableView_didSelectPackEvent(arg2={0})", arg2);
  518. try
  519. {
  520. RefreshSortButtonUI();
  521. RefreshQuickScrollButtons();
  522. }
  523. catch (Exception e)
  524. {
  525. Logger.Exception("Exception handling didSelectPackEvent...", e);
  526. }
  527. }
  528. /// <summary>
  529. /// Handler for level pack selection, controller.
  530. /// Sets the current level pack into the model and updates.
  531. /// </summary>
  532. /// <param name="arg1"></param>
  533. /// <param name="arg2"></param>
  534. private void _levelPackViewController_didSelectPackEvent(LevelPacksViewController arg1, IBeatmapLevelPack arg2)
  535. {
  536. Logger.Trace("_levelPackViewController_didSelectPackEvent(arg2={0})", arg2);
  537. try
  538. {
  539. // reset filter mode always here
  540. if (this._model.Settings.currentLevelPackId != arg2.packID)
  541. {
  542. this._model.Settings.filterMode = SongFilterMode.None;
  543. }
  544. // save level pack
  545. this._model.Settings.currentLevelPackId = arg2.packID;
  546. this._model.Settings.Save();
  547. this._model.ProcessSongList(arg2);
  548. // trickery to handle Downloader playlist level packs
  549. // We need to avoid scrolling to a level and then select the header
  550. bool scrollToLevel = true;
  551. if (arg2.packID.Contains("Playlist_"))
  552. {
  553. scrollToLevel = false;
  554. }
  555. RefreshSongUI(scrollToLevel);
  556. }
  557. catch (Exception e)
  558. {
  559. Logger.Exception("Exception handling didSelectPackEvent...", e);
  560. }
  561. }
  562. /// <summary>
  563. /// Remove all filters, update song list, save.
  564. /// </summary>
  565. private void OnClearButtonClickEvent()
  566. {
  567. Logger.Debug("Clearing all sorts and filters.");
  568. _model.Settings.sortMode = SongSortMode.Original;
  569. _model.Settings.invertSortResults = false;
  570. CancelFilter();
  571. _model.Settings.Save();
  572. ProcessSongList();
  573. RefreshSongUI();
  574. }
  575. /// <summary>
  576. /// Sort button clicked.
  577. /// </summary>
  578. private void OnSortButtonClickEvent(SongSortMode sortMode)
  579. {
  580. Logger.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  581. // Clear current selected level id so our song list jumps to the start
  582. _model.LastSelectedLevelId = null;
  583. if (_model.Settings.sortMode == sortMode)
  584. {
  585. _model.ToggleInverting();
  586. }
  587. _model.Settings.sortMode = sortMode;
  588. // update the seed
  589. if (_model.Settings.sortMode == SongSortMode.Random)
  590. {
  591. _model.Settings.randomSongSeed = Guid.NewGuid().GetHashCode();
  592. }
  593. _model.Settings.Save();
  594. ProcessSongList();
  595. RefreshSongUI();
  596. // update the display
  597. _sortByDisplay.SetButtonText(_model.Settings.sortMode.ToString());
  598. }
  599. /// <summary>
  600. /// Filter by favorites.
  601. /// </summary>
  602. private void OnFavoriteFilterButtonClickEvent()
  603. {
  604. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Favorites.ToString());
  605. if (_model.Settings.filterMode != SongFilterMode.Favorites)
  606. {
  607. _model.Settings.filterMode = SongFilterMode.Favorites;
  608. _beatUi.SelectLevelPack(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID);
  609. }
  610. else
  611. {
  612. CancelFilter();
  613. }
  614. _model.Settings.Save();
  615. ProcessSongList();
  616. RefreshSongUI();
  617. }
  618. /// <summary>
  619. /// Filter button clicked.
  620. /// </summary>
  621. /// <param name="sortMode"></param>
  622. private void OnSearchButtonClickEvent()
  623. {
  624. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
  625. if (_model.Settings.filterMode != SongFilterMode.Search)
  626. {
  627. _beatUi.SelectLevelPack(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID);
  628. this.ShowSearchKeyboard();
  629. }
  630. else
  631. {
  632. CancelFilter();
  633. ProcessSongList();
  634. RefreshSongUI();
  635. _model.Settings.Save();
  636. }
  637. }
  638. /// <summary>
  639. /// Display the playlist selector.
  640. /// </summary>
  641. /// <param name="sortMode"></param>
  642. private void OnPlaylistButtonClickEvent()
  643. {
  644. Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
  645. _model.LastSelectedLevelId = null;
  646. if (_model.Settings.filterMode != SongFilterMode.Playlist)
  647. {
  648. _beatUi.SelectLevelPack(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID);
  649. _playListFlowCoordinator.parentFlowCoordinator = _beatUi.LevelSelectionFlowCoordinator;
  650. _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentFlowCoordinator", new object[] { _playListFlowCoordinator, null, false, false });
  651. }
  652. else
  653. {
  654. CancelFilter();
  655. ProcessSongList();
  656. RefreshSongUI();
  657. _model.Settings.Save();
  658. }
  659. }
  660. /// <summary>
  661. /// Adjust UI based on level selected.
  662. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  663. /// </summary>
  664. private void OnDidSelectLevelEvent(LevelPackLevelsViewController view, IPreviewBeatmapLevel level)
  665. {
  666. try
  667. {
  668. Logger.Trace("OnDidSelectLevelEvent()");
  669. if (level == null)
  670. {
  671. Logger.Debug("No level selected?");
  672. return;
  673. }
  674. if (_model.Settings == null)
  675. {
  676. Logger.Debug("Settings not instantiated yet?");
  677. return;
  678. }
  679. _model.LastSelectedLevelId = level.levelID;
  680. HandleDidSelectLevelRow(level);
  681. }
  682. catch (Exception e)
  683. {
  684. Logger.Exception("Exception selecting song:", e);
  685. }
  686. }
  687. /// <summary>
  688. /// Switching one-saber modes for example.
  689. /// </summary>
  690. /// <param name="view"></param>
  691. /// <param name="bc"></param>
  692. private void OnDidSelectBeatmapCharacteristic(BeatmapCharacteristicSegmentedControlController view, BeatmapCharacteristicSO bc)
  693. {
  694. Logger.Trace("OnDidSelectBeatmapCharacteristic({0}", bc.name);
  695. _model.CurrentBeatmapCharacteristicSO = bc;
  696. _model.UpdateLevelRecords();
  697. this.RefreshSongList();
  698. }
  699. /// <summary>
  700. /// Handle difficulty level selection.
  701. /// </summary>
  702. private void OnDidChangeDifficultyEvent(StandardLevelDetailViewController view, IDifficultyBeatmap beatmap)
  703. {
  704. Logger.Trace("OnDidChangeDifficultyEvent({0})", beatmap);
  705. if (view.selectedDifficultyBeatmap == null)
  706. {
  707. return;
  708. }
  709. _deleteButton.interactable = (view.selectedDifficultyBeatmap.level.levelID.Length >= 32);
  710. RefreshScoreSaberData(view.selectedDifficultyBeatmap.level, beatmap.difficulty);
  711. RefreshNoteJumpSpeed(beatmap.difficulty);
  712. }
  713. /// <summary>
  714. /// BeatSaber finished loading content. This is when the difficulty is finally updated.
  715. /// </summary>
  716. /// <param name="view"></param>
  717. /// <param name="type"></param>
  718. private void OnDidPresentContentEvent(StandardLevelDetailViewController view, StandardLevelDetailViewController.ContentType type)
  719. {
  720. Logger.Trace("OnDidPresentContentEvent()");
  721. if (view.selectedDifficultyBeatmap == null)
  722. {
  723. return;
  724. }
  725. _deleteButton.interactable = (_beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID.Length >= 32);
  726. RefreshScoreSaberData(view.selectedDifficultyBeatmap.level, view.selectedDifficultyBeatmap.difficulty);
  727. RefreshNoteJumpSpeed(view.selectedDifficultyBeatmap.difficulty);
  728. }
  729. /// <summary>
  730. /// Refresh stats panel.
  731. /// </summary>
  732. /// <param name="level"></param>
  733. private void HandleDidSelectLevelRow(IPreviewBeatmapLevel level)
  734. {
  735. Logger.Trace("HandleDidSelectLevelRow({0})", level);
  736. _deleteButton.interactable = (level.levelID.Length >= 32);
  737. RefreshQuickScrollButtons();
  738. RefreshAddFavoriteButton(level.levelID);
  739. }
  740. /// <summary>
  741. /// Call Downloader delete.
  742. /// </summary>
  743. private void CallDownloaderDelete()
  744. {
  745. BeatSaverDownloader.UI.SongListTweaks.Instance.DeletePressed();
  746. }
  747. /// <summary>
  748. /// Pop up a delete dialog.
  749. /// </summary>
  750. private void HandleDeleteSelectedLevel()
  751. {
  752. bool DownloaderInstalled = CustomHelpers.IsModInstalled("BeatSaverDownloader");
  753. if (DownloaderInstalled)
  754. {
  755. CallDownloaderDelete();
  756. return;
  757. }
  758. IBeatmapLevel level = _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level;
  759. _deleteDialog.Init("Delete song", $"Do you really want to delete \"{ level.songName} {level.songSubName}\"?", "Delete", "Cancel",
  760. (selectedButton) =>
  761. {
  762. _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _deleteDialog, null, false });
  763. if (selectedButton == 0)
  764. {
  765. try
  766. {
  767. // determine the index we are deleting so we can keep the cursor near the same spot after
  768. // the header counts as an index, so if the index came from the level array we have to add 1.
  769. var levelsTableView = _beatUi.LevelPackLevelsViewController.GetPrivateField<LevelPackLevelsTableView>("_levelPackLevelsTableView");
  770. List<IPreviewBeatmapLevel> levels = levelsTableView.GetPrivateField<IBeatmapLevelPack>("_pack").beatmapLevelCollection.beatmapLevels.ToList();
  771. int selectedIndex = levels.FindIndex(x => x.levelID == _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level.levelID);
  772. if (selectedIndex > -1)
  773. {
  774. var song = new Song(SongCore.Loader.CustomLevels.First(x => x.Value.levelID == _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID).Value);
  775. SongCore.Loader.Instance.DeleteSong(song.path);
  776. this._model.RemoveSongFromLevelPack(_beatUi.GetCurrentSelectedLevelPack(), _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level.levelID);
  777. this.UpdateLevelDataModel();
  778. this.RefreshSongList();
  779. int removedLevels = levels.RemoveAll(x => x.levelID == _beatUi.StandardLevelDetailView.selectedDifficultyBeatmap.level.levelID);
  780. Logger.Info("Removed " + removedLevels + " level(s) from song list!");
  781. TableView listTableView = levelsTableView.GetPrivateField<TableView>("_tableView");
  782. listTableView.ScrollToCellWithIdx(selectedIndex, TableView.ScrollPositionType.Beginning, false);
  783. levelsTableView.SetPrivateField("_selectedRow", selectedIndex);
  784. listTableView.SelectCellWithIdx(selectedIndex, true);
  785. }
  786. }
  787. catch (Exception e)
  788. {
  789. Logger.Error("Unable to delete song! Exception: " + e);
  790. }
  791. }
  792. });
  793. _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { _deleteDialog, null, false });
  794. }
  795. /// <summary>
  796. /// Handle selection of a playlist. Show just the songs in the playlist.
  797. /// </summary>
  798. /// <param name="p"></param>
  799. private void HandleDidSelectPlaylist(Playlist p)
  800. {
  801. if (p != null)
  802. {
  803. Logger.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  804. _model.Settings.filterMode = SongFilterMode.Playlist;
  805. _model.CurrentPlaylist = p;
  806. _model.Settings.Save();
  807. ProcessSongList();
  808. RefreshSongUI();
  809. }
  810. else
  811. {
  812. Logger.Debug("No playlist selected");
  813. }
  814. }
  815. /// <summary>
  816. /// Display the search keyboard
  817. /// </summary>
  818. void ShowSearchKeyboard()
  819. {
  820. if (_searchViewController == null)
  821. {
  822. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  823. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  824. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  825. }
  826. Logger.Debug("Presenting search keyboard");
  827. _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentViewController", new object[] { _searchViewController, null, false });
  828. }
  829. /// <summary>
  830. /// Handle back button event from search keyboard.
  831. /// </summary>
  832. private void SearchViewControllerbackButtonPressed()
  833. {
  834. _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
  835. this._model.Settings.filterMode = SongFilterMode.None;
  836. this._model.Settings.Save();
  837. RefreshSongUI();
  838. }
  839. /// <summary>
  840. /// Handle search.
  841. /// </summary>
  842. /// <param name="searchFor"></param>
  843. private void SearchViewControllerSearchButtonPressed(string searchFor)
  844. {
  845. _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("DismissViewController", new object[] { _searchViewController, null, false });
  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.GetCurrentLevelPackLevels();
  861. if (levels == null)
  862. {
  863. return;
  864. }
  865. int totalSize = _beatUi.GetLevelPackLevelCount();
  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. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_beatUi.LevelPackLevelsTableView, "_tableView");
  873. int currentRow = _beatUi.LevelPackLevelsTableView.GetPrivateField<int>("_selectedRow");
  874. int jumpDirection = Math.Sign(numJumps);
  875. int newRow = currentRow + (jumpDirection * segmentSize);
  876. if (newRow <= 0)
  877. {
  878. newRow = 0;
  879. }
  880. else if (newRow >= totalSize)
  881. {
  882. newRow = totalSize - 1;
  883. }
  884. Logger.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
  885. _beatUi.SelectAndScrollToLevel(_beatUi.LevelPackLevelsTableView, levels[newRow].levelID);
  886. RefreshQuickScrollButtons();
  887. }
  888. /// <summary>
  889. /// Add/Remove song from favorites depending on if it already exists.
  890. /// </summary>
  891. private void ToggleSongInPlaylist()
  892. {
  893. IBeatmapLevel songInfo = _beatUi.LevelDetailViewController.selectedDifficultyBeatmap.level;
  894. if (_model.CurrentEditingPlaylist != null)
  895. {
  896. if (_model.CurrentEditingPlaylistLevelIds.Contains(songInfo.levelID))
  897. {
  898. Logger.Info("Remove {0} from editing playlist", songInfo.songName);
  899. _model.RemoveSongFromEditingPlaylist(songInfo);
  900. if (_model.Settings.filterMode == SongFilterMode.Favorites)
  901. {
  902. this._model.ProcessSongList(_beatUi.GetCurrentSelectedLevelPack());
  903. this.RefreshSongList();
  904. }
  905. }
  906. else
  907. {
  908. Logger.Info("Add {0} to editing playlist", songInfo.songName);
  909. _model.AddSongToEditingPlaylist(songInfo);
  910. }
  911. }
  912. RefreshAddFavoriteButton(songInfo.levelID);
  913. _model.Settings.Save();
  914. }
  915. /// <summary>
  916. /// Update GUI elements that show score saber data.
  917. /// </summary>
  918. public void RefreshScoreSaberData(IPreviewBeatmapLevel level, BeatmapDifficulty vdifficulty)
  919. {
  920. Logger.Trace("RefreshScoreSaberData({0})", level.levelID);
  921. if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile == null)
  922. {
  923. return;
  924. }
  925. BeatmapDifficulty difficulty = _beatUi.LevelDifficultyViewController.selectedDifficulty;
  926. string difficultyString = difficulty.ToString();
  927. if (difficultyString.Equals("ExpertPlus"))
  928. {
  929. difficultyString = "Expert+";
  930. }
  931. Logger.Debug(difficultyString);
  932. // Check if we have data for this song
  933. Logger.Debug("Checking if have info for song {0}", level.songName);
  934. var hash = CustomHelpers.GetSongHash(level.levelID);
  935. if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData.ContainsKey(hash))
  936. {
  937. Logger.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
  938. ScoreSaberSong scoreSaberSong = ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData[hash];
  939. ScoreSaberSongDifficultyStats scoreSaberSongDifficulty = scoreSaberSong.diffs.FirstOrDefault(x => String.Equals(x.diff, difficultyString));
  940. if (scoreSaberSongDifficulty != null)
  941. {
  942. Logger.Debug("Display pp for song.");
  943. double pp = scoreSaberSongDifficulty.pp;
  944. double star = scoreSaberSongDifficulty.star;
  945. UIBuilder.SetStatButtonText(_ppStatButton, String.Format("{0:0.#}", pp));
  946. UIBuilder.SetStatButtonText(_starStatButton, String.Format("{0:0.#}", star));
  947. }
  948. else
  949. {
  950. UIBuilder.SetStatButtonText(_ppStatButton, "NA");
  951. UIBuilder.SetStatButtonText(_starStatButton, "NA");
  952. }
  953. }
  954. else
  955. {
  956. UIBuilder.SetStatButtonText(_ppStatButton, "NA");
  957. UIBuilder.SetStatButtonText(_starStatButton, "NA");
  958. }
  959. Logger.Debug("Done refreshing score saber stats.");
  960. }
  961. /// <summary>
  962. /// Helper to refresh the NJS widget.
  963. /// </summary>
  964. /// <param name="beatmap"></param>
  965. private void RefreshNoteJumpSpeed(BeatmapDifficulty beatmap)
  966. {
  967. UIBuilder.SetStatButtonText(_njsStatButton, String.Format("{0}", beatmap.NoteJumpMovementSpeed()));
  968. }
  969. /// <summary>
  970. /// Update interactive state of the quick scroll buttons.
  971. /// </summary>
  972. private void RefreshQuickScrollButtons()
  973. {
  974. if (!_uiCreated)
  975. {
  976. return;
  977. }
  978. _pageUpFastButton.interactable = _beatUi.TableViewPageUpButton.interactable;
  979. _pageUpFastButton.gameObject.SetActive(_beatUi.TableViewPageUpButton.IsActive());
  980. _pageDownFastButton.interactable = _beatUi.TableViewPageDownButton.interactable;
  981. _pageDownFastButton.gameObject.SetActive(_beatUi.TableViewPageDownButton.IsActive());
  982. }
  983. /// <summary>
  984. /// TODO - evaluate this sillyness...
  985. /// </summary>
  986. /// <returns></returns>
  987. public IEnumerator RefreshQuickScrollButtonsAsync()
  988. {
  989. yield return new WaitForEndOfFrame();
  990. RefreshQuickScrollButtons();
  991. }
  992. /// <summary>
  993. /// Show the UI.
  994. /// </summary>
  995. public void Show()
  996. {
  997. Logger.Trace("Show SongBrowserUI()");
  998. this.SetVisibility(true);
  999. }
  1000. /// <summary>
  1001. /// Hide the UI.
  1002. /// </summary>
  1003. public void Hide()
  1004. {
  1005. Logger.Trace("Hide SongBrowserUI()");
  1006. this.SetVisibility(false);
  1007. }
  1008. /// <summary>
  1009. /// Handle showing or hiding UI logic.
  1010. /// </summary>
  1011. /// <param name="visible"></param>
  1012. private void SetVisibility(bool visible)
  1013. {
  1014. // UI not created, nothing visible to hide...
  1015. if (!_uiCreated)
  1016. {
  1017. return;
  1018. }
  1019. _ppStatButton.gameObject.SetActive(visible);
  1020. _starStatButton.gameObject.SetActive(visible);
  1021. _njsStatButton.gameObject.SetActive(visible);
  1022. RefreshOuterUIState(visible == true ? UIState.Main : UIState.Disabled);
  1023. _addFavoriteButton.gameObject.SetActive(visible);
  1024. _deleteButton.gameObject.SetActive(visible);
  1025. _pageUpFastButton.gameObject.SetActive(visible);
  1026. _pageDownFastButton.gameObject.SetActive(visible);
  1027. }
  1028. /// <summary>
  1029. /// Update the top UI state.
  1030. /// Hides the outer ui, sort, and filter buttons depending on the state.
  1031. /// </summary>
  1032. private void RefreshOuterUIState(UIState state)
  1033. {
  1034. bool sortButtons = false;
  1035. bool filterButtons = false;
  1036. bool outerButtons = false;
  1037. bool clearButton = true;
  1038. if (state == UIState.SortBy)
  1039. {
  1040. sortButtons = true;
  1041. }
  1042. else if (state == UIState.FilterBy)
  1043. {
  1044. filterButtons = true;
  1045. }
  1046. else if (state == UIState.Main)
  1047. {
  1048. outerButtons = true;
  1049. }
  1050. else
  1051. {
  1052. clearButton = false;
  1053. }
  1054. _sortButtonGroup.ForEach(x => x.Button.gameObject.SetActive(sortButtons));
  1055. _filterButtonGroup.ForEach(x => x.Button.gameObject.SetActive(filterButtons));
  1056. _sortByButton.gameObject.SetActive(outerButtons);
  1057. _sortByDisplay.gameObject.SetActive(outerButtons);
  1058. _filterByButton.gameObject.SetActive(outerButtons);
  1059. _filterByDisplay.gameObject.SetActive(outerButtons);
  1060. _clearSortFilterButton.gameObject.SetActive(clearButton);
  1061. _randomButton.gameObject.SetActive(outerButtons);
  1062. RefreshCurrentSelectionDisplay();
  1063. _currentUiState = state;
  1064. }
  1065. /// <summary>
  1066. /// Adjust the text field of the sort by and filter by displays.
  1067. /// </summary>
  1068. private void RefreshCurrentSelectionDisplay()
  1069. {
  1070. string sortByDisplay = null;
  1071. if (_model.Settings.sortMode == SongSortMode.Default)
  1072. {
  1073. sortByDisplay = "Title";
  1074. }
  1075. else
  1076. {
  1077. sortByDisplay = _model.Settings.sortMode.ToString();
  1078. }
  1079. _sortByDisplay.SetButtonText(sortByDisplay);
  1080. _filterByDisplay.SetButtonText(_model.Settings.filterMode.ToString());
  1081. }
  1082. /// <summary>
  1083. /// Helper to quickly refresh add to favorites button
  1084. /// </summary>
  1085. /// <param name="levelId"></param>
  1086. private void RefreshAddFavoriteButton(String levelId)
  1087. {
  1088. if (levelId == null)
  1089. {
  1090. _currentAddFavoriteButtonSprite = null;
  1091. }
  1092. else
  1093. {
  1094. if (_model.CurrentEditingPlaylistLevelIds.Contains(levelId))
  1095. {
  1096. _currentAddFavoriteButtonSprite = Base64Sprites.RemoveFromFavoritesIcon;
  1097. }
  1098. else
  1099. {
  1100. _currentAddFavoriteButtonSprite = Base64Sprites.AddToFavoritesIcon;
  1101. }
  1102. }
  1103. _addFavoriteButton.SetButtonIcon(_currentAddFavoriteButtonSprite);
  1104. }
  1105. /// <summary>
  1106. /// Adjust the UI colors.
  1107. /// </summary>
  1108. public void RefreshSortButtonUI()
  1109. {
  1110. // So far all we need to refresh is the sort buttons.
  1111. foreach (SongSortButton sortButton in _sortButtonGroup)
  1112. {
  1113. UIBuilder.SetButtonBorder(sortButton.Button, Color.white);
  1114. if (sortButton.SortMode == _model.Settings.sortMode)
  1115. {
  1116. if (this._model.Settings.invertSortResults)
  1117. {
  1118. UIBuilder.SetButtonBorder(sortButton.Button, Color.red);
  1119. }
  1120. else
  1121. {
  1122. UIBuilder.SetButtonBorder(sortButton.Button, Color.green);
  1123. }
  1124. }
  1125. }
  1126. // refresh filter buttons
  1127. foreach (SongFilterButton filterButton in _filterButtonGroup)
  1128. {
  1129. UIBuilder.SetButtonBorder(filterButton.Button, Color.white);
  1130. if (filterButton.FilterMode == _model.Settings.filterMode)
  1131. {
  1132. UIBuilder.SetButtonBorder(filterButton.Button, Color.green);
  1133. }
  1134. }
  1135. if (this._model.Settings.invertSortResults)
  1136. {
  1137. UIBuilder.SetButtonBorder(_sortByDisplay, Color.red);
  1138. }
  1139. else
  1140. {
  1141. UIBuilder.SetButtonBorder(_sortByDisplay, Color.green);
  1142. }
  1143. if (this._model.Settings.filterMode != SongFilterMode.None)
  1144. {
  1145. UIBuilder.SetButtonBorder(_filterByDisplay, Color.green);
  1146. }
  1147. else
  1148. {
  1149. UIBuilder.SetButtonBorder(_filterByDisplay, Color.white);
  1150. }
  1151. }
  1152. /// <summary>
  1153. ///
  1154. /// </summary>
  1155. public void RefreshSongList(bool scrollToLevel = true)
  1156. {
  1157. if (!_uiCreated)
  1158. {
  1159. return;
  1160. }
  1161. _beatUi.RefreshSongList(_model.LastSelectedLevelId);
  1162. }
  1163. /// <summary>
  1164. /// Helper for updating the model (which updates the song list)
  1165. /// </summary>
  1166. public void UpdateLevelDataModel()
  1167. {
  1168. try
  1169. {
  1170. Logger.Trace("UpdateLevelDataModel()");
  1171. // get a current beatmap characteristic...
  1172. if (_model.CurrentBeatmapCharacteristicSO == null && _uiCreated)
  1173. {
  1174. _model.CurrentBeatmapCharacteristicSO = _beatUi.BeatmapCharacteristicSelectionViewController.GetPrivateField<BeatmapCharacteristicSO>("_selectedBeatmapCharacteristic");
  1175. }
  1176. _model.UpdateLevelRecords();
  1177. bool didUpdateLevelPack = UpdateLevelPackSelection();
  1178. if (didUpdateLevelPack)
  1179. {
  1180. ProcessSongList();
  1181. }
  1182. }
  1183. catch (Exception e)
  1184. {
  1185. Logger.Exception("SongBrowser UI crashed trying to update the internal song lists: ", e);
  1186. }
  1187. }
  1188. /// <summary>
  1189. /// Logic for fixing BeatSaber's level pack selection bugs.
  1190. /// </summary>
  1191. public bool UpdateLevelPackSelection()
  1192. {
  1193. if (_uiCreated)
  1194. {
  1195. IBeatmapLevelPack currentSelected = _beatUi.GetCurrentSelectedLevelPack();
  1196. Logger.Debug("Current selected level pack: {0}", currentSelected);
  1197. if (String.IsNullOrEmpty(_model.Settings.currentLevelPackId))
  1198. {
  1199. if (currentSelected == null)
  1200. {
  1201. Logger.Debug("No level pack selected, acquiring the first available...");
  1202. var levelPackCollection = _beatUi.LevelPackViewController.GetPrivateField<IBeatmapLevelPackCollection>("_levelPackCollection");
  1203. currentSelected = levelPackCollection.beatmapLevelPacks[0];
  1204. }
  1205. }
  1206. else if (currentSelected == null || (currentSelected.packID != _model.Settings.currentLevelPackId))
  1207. {
  1208. Logger.Debug("Automatically selecting level pack: {0}", _model.Settings.currentLevelPackId);
  1209. // HACK - BeatSaber seems to always go back to OST1 internally.
  1210. // - Lets force it to the last pack id but not have SongBrowser functions fire.
  1211. // Turn off our event processing
  1212. _beatUi.LevelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
  1213. _beatUi.LevelPacksTableView.didSelectPackEvent -= _levelPacksTableView_didSelectPackEvent;
  1214. var levelPack = _beatUi.GetLevelPackByPackId(_model.Settings.currentLevelPackId);
  1215. _beatUi.SelectLevelPack(_model.Settings.currentLevelPackId);
  1216. ProcessSongList();
  1217. _beatUi.LevelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
  1218. _beatUi.LevelPacksTableView.didSelectPackEvent += _levelPacksTableView_didSelectPackEvent;
  1219. return true;
  1220. }
  1221. }
  1222. return false;
  1223. }
  1224. }
  1225. }