Browse Source

Revamping UI and Code clean up (#75)

* Enable sorting by beat saver rating.
* Remove some dead code.
* Create BeatSaberUiController.
* UI has a submenu now for sort and filter modes.
* Fix fast scroll buttons.
* Large organization of code.  Should be easier to refactor later.
* Move random button to outer ui.
* Migrate PlayCount to YourPlayCount.
* Added sorting by BeatSaver PlayCount.
Halsafar 5 years ago
parent
commit
e806808a33

+ 2 - 0
SongBrowserPlugin/DataAccess/ScrappedData.cs

@@ -21,6 +21,8 @@ namespace SongBrowser.DataAccess
         public long PlayedCount { get; set; }
         public long PlayedCount { get; set; }
         public long Upvotes { get; set; }
         public long Upvotes { get; set; }
         public long Downvotes { get; set; }
         public long Downvotes { get; set; }
+        public float Heat { get; set; }
+        public float Rating { get; set; }
     }
     }
 
 
     public class DifficultyStats
     public class DifficultyStats

+ 70 - 4
SongBrowserPlugin/DataAccess/SongBrowserModel.cs

@@ -273,7 +273,7 @@ namespace SongBrowser
                     playlistTitle = "Song Browser Favorites",
                     playlistTitle = "Song Browser Favorites",
                     playlistAuthor = "SongBrowser",
                     playlistAuthor = "SongBrowser",
                     fileLoc = this.Settings.currentEditingPlaylistFile,
                     fileLoc = this.Settings.currentEditingPlaylistFile,
-                    image = Base64Sprites.PlaylistIconB64,
+                    image = Base64Sprites.SpriteToBase64(Base64Sprites.BeastSaberLogo),
                     songs = new List<PlaylistSong>(),
                     songs = new List<PlaylistSong>(),
                 };
                 };
             }
             }
@@ -333,13 +333,12 @@ namespace SongBrowser
             PlayerDataModelSO playerData = Resources.FindObjectsOfTypeAll<PlayerDataModelSO>().FirstOrDefault();            
             PlayerDataModelSO playerData = Resources.FindObjectsOfTypeAll<PlayerDataModelSO>().FirstOrDefault();            
             foreach (var levelData in playerData.currentLocalPlayer.levelsStatsData)
             foreach (var levelData in playerData.currentLocalPlayer.levelsStatsData)
             {
             {
-                int currentCount = 0;
                 if (!_levelIdToPlayCount.ContainsKey(levelData.levelID))
                 if (!_levelIdToPlayCount.ContainsKey(levelData.levelID))
                 {
                 {
-                    _levelIdToPlayCount.Add(levelData.levelID, currentCount);
+                    _levelIdToPlayCount.Add(levelData.levelID, 0);
                 }
                 }
 
 
-                _levelIdToPlayCount[levelData.levelID] += (currentCount + levelData.playCount);
+                _levelIdToPlayCount[levelData.levelID] += levelData.playCount;
             }
             }
         }
         }
 
 
@@ -565,6 +564,12 @@ namespace SongBrowser
                     sortedSongs = SortUpVotes(filteredSongs);
                     sortedSongs = SortUpVotes(filteredSongs);
                     break;
                     break;
                 case SongSortMode.PlayCount:
                 case SongSortMode.PlayCount:
+                    sortedSongs = SortBeatSaverPlayCount(filteredSongs);
+                    break;
+                case SongSortMode.Rating:
+                    sortedSongs = SortBeatSaverRating(filteredSongs);
+                    break;
+                case SongSortMode.YourPlayCount:
                     sortedSongs = SortPlayCount(filteredSongs);
                     sortedSongs = SortPlayCount(filteredSongs);
                     break;
                     break;
                 case SongSortMode.PP:
                 case SongSortMode.PP:
@@ -858,5 +863,66 @@ namespace SongBrowser
                 })
                 })
                 .ToList();
                 .ToList();
         }
         }
+
+
+        /// <summary>
+        /// Sorting by BeatSaver rating stat.
+        /// </summary>
+        /// <param name="levelIds"></param>
+        /// <returns></returns>
+        private List<IPreviewBeatmapLevel> SortBeatSaverPlayCount(List<IPreviewBeatmapLevel> levelIds)
+        {
+            Logger.Info("Sorting song list by BeatSaver PlayCount");
+
+            // Do not always have data when trying to sort by UpVotes
+            if (_levelHashToDownloaderData == null)
+            {
+                return levelIds;
+            }
+
+            return levelIds
+                .OrderByDescending(x => {
+                    var hash = x.levelID.Split('_')[2];
+                    if (_levelHashToDownloaderData.ContainsKey(hash))
+                    {
+                        return _levelHashToDownloaderData[hash].PlayedCount;
+                    }
+                    else
+                    {
+                        return int.MinValue;
+                    }
+                })
+                .ToList();
+        }
+
+        /// <summary>
+        /// Sorting by BeatSaver rating stat.
+        /// </summary>
+        /// <param name="levelIds"></param>
+        /// <returns></returns>
+        private List<IPreviewBeatmapLevel> SortBeatSaverRating(List<IPreviewBeatmapLevel> levelIds)
+        {
+            Logger.Info("Sorting song list by BeatSaver Rating");
+
+            // Do not always have data when trying to sort by UpVotes
+            if (_levelHashToDownloaderData == null)
+            {
+                return levelIds;
+            }
+
+            return levelIds
+                .OrderByDescending(x => {
+                    var hash = x.levelID.Split('_')[2];
+                    if (_levelHashToDownloaderData.ContainsKey(hash))
+                    {
+                        return _levelHashToDownloaderData[hash].Rating;
+                    }
+                    else
+                    {
+                        return int.MinValue;
+                    }
+                })
+                .ToList();
+        }
     }
     }
 }
 }

+ 5 - 3
SongBrowserPlugin/DataAccess/SongBrowserSettings.cs

@@ -17,11 +17,13 @@ namespace SongBrowser.DataAccess
         Author,
         Author,
         Original,
         Original,
         Newest,        
         Newest,        
-        PlayCount,
+        YourPlayCount,
         Difficulty,
         Difficulty,
         Random,
         Random,
         PP,
         PP,
         UpVotes,
         UpVotes,
+        Rating,
+        PlayCount,
 
 
         // Deprecated
         // Deprecated
         Favorites,
         Favorites,
@@ -178,7 +180,7 @@ namespace SongBrowser.DataAccess
                     playlistTitle = "Song Browser Favorites",
                     playlistTitle = "Song Browser Favorites",
                     playlistAuthor = "SongBrowser",
                     playlistAuthor = "SongBrowser",
                     fileLoc = "",
                     fileLoc = "",
-                    image = Base64Sprites.PlaylistIconB64,
+                    image = Base64Sprites.SpriteToBase64(Base64Sprites.BeastSaberLogo),
                     songs = new List<PlaylistSong>(),
                     songs = new List<PlaylistSong>(),
                 };
                 };
                 p.CreateNew(playlistPath);
                 p.CreateNew(playlistPath);
@@ -255,7 +257,7 @@ namespace SongBrowser.DataAccess
                     playlistTitle = "Song Browser Favorites",
                     playlistTitle = "Song Browser Favorites",
                     playlistAuthor = "SongBrowser",
                     playlistAuthor = "SongBrowser",
                     fileLoc = "",
                     fileLoc = "",
-                    image = Base64Sprites.PlaylistIconB64,
+                    image = Base64Sprites.SpriteToBase64(Base64Sprites.BeastSaberLogo),
                     songs = new List<PlaylistSong>(),
                     songs = new List<PlaylistSong>(),
                 };
                 };
             }
             }

+ 1 - 1
SongBrowserPlugin/Logging/Logger.cs

@@ -14,7 +14,7 @@ namespace SongBrowser.Logging
     public class Logger
     public class Logger
     {
     {
         private static readonly string LoggerName = "SongBrowser";
         private static readonly string LoggerName = "SongBrowser";
-        private static readonly LogLevel LogLevel = LogLevel.Info;
+        private static readonly LogLevel LogLevel = LogLevel.Debug;
         private static readonly ConsoleColor DefaultFgColor = ConsoleColor.Gray;
         private static readonly ConsoleColor DefaultFgColor = ConsoleColor.Gray;
 
 
         private static void ResetForegroundColor()
         private static void ResetForegroundColor()

+ 1 - 0
SongBrowserPlugin/SongBrowser.csproj

@@ -124,6 +124,7 @@
     </Reference>
     </Reference>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+    <Compile Include="UI\Browser\BeatSaberUIController.cs" />
     <Compile Include="DataAccess\BeatSaverApi\BeatSaverApiResults.cs" />
     <Compile Include="DataAccess\BeatSaverApi\BeatSaverApiResults.cs" />
     <Compile Include="DataAccess\FileOperationApiWrapper.cs" />
     <Compile Include="DataAccess\FileOperationApiWrapper.cs" />
     <Compile Include="DataAccess\LoadScripts.cs" />
     <Compile Include="DataAccess\LoadScripts.cs" />

+ 7 - 7
SongBrowserPlugin/SongBrowserApplication.cs

@@ -174,7 +174,7 @@ namespace SongBrowser
         {
         {
             Logger.Trace("HandleSoloModeSelection()");
             Logger.Trace("HandleSoloModeSelection()");
             HandleModeSelection(MainMenuViewController.MenuButton.SoloFreePlay);
             HandleModeSelection(MainMenuViewController.MenuButton.SoloFreePlay);
-            this._songBrowserUI.Show();
+            _songBrowserUI.Show();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -186,7 +186,7 @@ namespace SongBrowser
         {
         {
             Logger.Trace("HandlePartyModeSelection()");
             Logger.Trace("HandlePartyModeSelection()");
             HandleModeSelection(MainMenuViewController.MenuButton.Party);
             HandleModeSelection(MainMenuViewController.MenuButton.Party);
-            this._songBrowserUI.Show();
+            _songBrowserUI.Show();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -198,7 +198,7 @@ namespace SongBrowser
         {
         {
             Logger.Trace("HandleCampaignModeSelection()");
             Logger.Trace("HandleCampaignModeSelection()");
             HandleModeSelection(MainMenuViewController.MenuButton.SoloCampaign);
             HandleModeSelection(MainMenuViewController.MenuButton.SoloCampaign);
-            this._songBrowserUI.Hide();
+            _songBrowserUI.Hide();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -209,8 +209,8 @@ namespace SongBrowser
         private void HandleModeSelection(MainMenuViewController.MenuButton mode)
         private void HandleModeSelection(MainMenuViewController.MenuButton mode)
         {
         {
             Logger.Trace("HandleModeSelection()");
             Logger.Trace("HandleModeSelection()");
-            this._songBrowserUI.CreateUI(mode);
-            StartCoroutine(this.UpdateBrowserUI());
+            _songBrowserUI.CreateUI(mode);
+            StartCoroutine(UpdateBrowserUI());
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -221,8 +221,8 @@ namespace SongBrowser
         {
         {
             yield return new WaitForEndOfFrame();
             yield return new WaitForEndOfFrame();
 
 
-            this._songBrowserUI.UpdateLevelDataModel();
-            this._songBrowserUI.RefreshSongList();
+            _songBrowserUI.UpdateLevelDataModel();
+            _songBrowserUI.RefreshSongList();
         }
         }
 
 
         /// <summary>
         /// <summary>

File diff suppressed because it is too large
+ 12 - 24
SongBrowserPlugin/UI/Base64Sprites.cs


+ 354 - 0
SongBrowserPlugin/UI/Browser/BeatSaberUIController.cs

@@ -0,0 +1,354 @@
+using CustomUI.Utilities;
+using HMUI;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+using UnityEngine.UI;
+using VRUI;
+using Logger = SongBrowser.Logging.Logger;
+
+namespace SongBrowser.DataAccess
+{
+    public class BeatSaberUIController
+    {
+        // Beat Saber UI Elements
+        public FlowCoordinator LevelSelectionFlowCoordinator;
+
+        public LevelPacksViewController LevelPackViewController;
+        public LevelPacksTableView LevelPacksTableView;
+        public LevelPackDetailViewController LevelPackDetailViewController;
+
+        public LevelPackLevelsViewController LevelPackLevelsViewController;
+        public LevelPackLevelsTableView LevelPackLevelsTableView;
+        public StandardLevelDetailViewController LevelDetailViewController;
+        public StandardLevelDetailView StandardLevelDetailView;
+
+        public BeatmapDifficultySegmentedControlController LevelDifficultyViewController;
+        public BeatmapCharacteristicSegmentedControlController BeatmapCharacteristicSelectionViewController;
+
+        public DismissableNavigationController LevelSelectionNavigationController;
+
+        public RectTransform LevelPackLevelsTableViewRectTransform;
+
+        public Button TableViewPageUpButton;
+        public Button TableViewPageDownButton;
+
+        public RectTransform PlayContainer;
+        public RectTransform PlayButtons;
+
+        public Button PlayButton;
+        public Button PracticeButton;
+
+        public SimpleDialogPromptViewController SimpleDialogPromptViewControllerPrefab;
+
+        // Plugin Compat checks
+        private bool _detectedTwitchPluginQueue = false;
+        private bool _checkedForTwitchPlugin = false;
+
+        /// <summary>
+        /// Constructor.  Acquire all necessary BeatSaberUi elements.
+        /// </summary>
+        /// <param name="flowCoordinator"></param>
+        public BeatSaberUIController(FlowCoordinator flowCoordinator)
+        {
+            LevelSelectionFlowCoordinator = flowCoordinator;
+
+            // gather controllers and ui elements.
+            LevelPackViewController = LevelSelectionFlowCoordinator.GetPrivateField<LevelPacksViewController>("_levelPacksViewController");
+            Logger.Debug("Acquired LevelPacksViewController [{0}]", LevelPackViewController.GetInstanceID());
+
+            LevelPackDetailViewController = LevelSelectionFlowCoordinator.GetPrivateField<LevelPackDetailViewController>("_levelPackDetailViewController");
+            Logger.Debug("Acquired LevelPackDetailViewController [{0}]", LevelPackDetailViewController.GetInstanceID());
+
+            LevelPacksTableView = LevelPackViewController.GetPrivateField<LevelPacksTableView>("_levelPacksTableView");
+            Logger.Debug("Acquired LevelPacksTableView [{0}]", LevelPacksTableView.GetInstanceID());
+
+            LevelPackLevelsViewController = LevelSelectionFlowCoordinator.GetPrivateField<LevelPackLevelsViewController>("_levelPackLevelsViewController");
+            Logger.Debug("Acquired LevelPackLevelsViewController [{0}]", LevelPackLevelsViewController.GetInstanceID());
+
+            LevelPackLevelsTableView = this.LevelPackLevelsViewController.GetPrivateField<LevelPackLevelsTableView>("_levelPackLevelsTableView");
+            Logger.Debug("Acquired LevelPackLevelsTableView [{0}]", LevelPackLevelsTableView.GetInstanceID());
+
+            LevelDetailViewController = LevelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
+            Logger.Debug("Acquired StandardLevelDetailViewController [{0}]", LevelDetailViewController.GetInstanceID());
+
+            StandardLevelDetailView = LevelDetailViewController.GetPrivateField<StandardLevelDetailView>("_standardLevelDetailView");
+            Logger.Debug("Acquired StandardLevelDetailView [{0}]", StandardLevelDetailView.GetInstanceID());
+
+            BeatmapCharacteristicSelectionViewController = Resources.FindObjectsOfTypeAll<BeatmapCharacteristicSegmentedControlController>().First();
+            Logger.Debug("Acquired BeatmapCharacteristicSegmentedControlController [{0}]", BeatmapCharacteristicSelectionViewController.GetInstanceID());
+
+            LevelSelectionNavigationController = LevelSelectionFlowCoordinator.GetPrivateField<DismissableNavigationController>("_navigationController");
+            Logger.Debug("Acquired DismissableNavigationController [{0}]", LevelSelectionNavigationController.GetInstanceID());
+
+            LevelDifficultyViewController = StandardLevelDetailView.GetPrivateField<BeatmapDifficultySegmentedControlController>("_beatmapDifficultySegmentedControlController");
+            Logger.Debug("Acquired BeatmapDifficultySegmentedControlController [{0}]", LevelDifficultyViewController.GetInstanceID());
+
+            LevelPackLevelsTableViewRectTransform = LevelPackLevelsTableView.transform as RectTransform;
+            Logger.Debug("Acquired TableViewRectTransform from LevelPackLevelsTableView [{0}]", LevelPackLevelsTableViewRectTransform.GetInstanceID());
+
+            TableView tableView = ReflectionUtil.GetPrivateField<TableView>(LevelPackLevelsTableView, "_tableView");
+            TableViewPageUpButton = tableView.GetPrivateField<Button>("_pageUpButton");
+            TableViewPageDownButton = tableView.GetPrivateField<Button>("_pageDownButton");
+            Logger.Debug("Acquired Page Up and Down buttons...");
+
+            PlayContainer = StandardLevelDetailView.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayContainer");
+            PlayButtons = PlayContainer.GetComponentsInChildren<RectTransform>().First(x => x.name == "PlayButtons");
+
+            PlayButton = Resources.FindObjectsOfTypeAll<Button>().First(x => x.name == "PlayButton");
+            PracticeButton = PlayButtons.GetComponentsInChildren<Button>().First(x => x.name == "PracticeButton");
+
+            SimpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
+        }
+
+
+        /// <summary>
+        /// Acquire the level pack collection.
+        /// </summary>
+        /// <returns></returns>
+        public IBeatmapLevelPackCollection GetLevelPackCollection()
+        {
+            if (LevelPackViewController == null)
+            {
+                return null;
+            }
+
+            IBeatmapLevelPackCollection levelPackCollection = LevelPackViewController.GetPrivateField<IBeatmapLevelPackCollection>("_levelPackCollection");
+            return levelPackCollection;
+        }
+
+        /// <summary>
+        /// Get the currently selected level pack within the LevelPackLevelViewController hierarchy.
+        /// </summary>
+        /// <returns></returns>
+        public IBeatmapLevelPack GetCurrentSelectedLevelPack()
+        {
+            if (LevelPackLevelsTableView == null)
+            {
+                return null;
+            }
+
+            var pack = LevelPackLevelsTableView.GetPrivateField<IBeatmapLevelPack>("_pack");
+            return pack;
+        }
+
+        /// <summary>
+        /// Get level pack by level pack id.
+        /// </summary>
+        /// <param name="levelPackId"></param>
+        /// <returns></returns>
+        public IBeatmapLevelPack GetLevelPackByPackId(String levelPackId)
+        {
+            IBeatmapLevelPackCollection levelPackCollection = GetLevelPackCollection();
+            if (levelPackCollection == null)
+            {
+                return null;
+            }
+
+            IBeatmapLevelPack levelPack = levelPackCollection.beatmapLevelPacks.ToList().FirstOrDefault(x => x.packID == levelPackId);
+            return levelPack;
+        }
+
+        /// <summary>
+        /// Get level pack index by level pack id.
+        /// </summary>
+        /// <param name="levelPackId"></param>
+        /// <returns></returns>
+        public int GetLevelPackIndexByPackId(String levelPackId)
+        {
+            IBeatmapLevelPackCollection levelPackCollection = GetLevelPackCollection();
+            if (levelPackCollection == null)
+            {
+                return -1;
+            }
+
+            int index = levelPackCollection.beatmapLevelPacks.ToList().FindIndex(x => x.packID == levelPackId);
+            return index;
+        }
+
+
+        /// <summary>
+        /// Get Current levels from current level pack.
+        /// </summary>
+        /// <returns></returns>
+        public IPreviewBeatmapLevel[] GetCurrentLevelPackLevels()
+        {
+            var levelPack = GetCurrentSelectedLevelPack();
+            if (levelPack == null)
+            {
+                return null;
+            }
+
+            return levelPack.beatmapLevelCollection.beatmapLevels;
+        }
+
+        /// <summary>
+        /// Get level count helper.
+        /// </summary>
+        /// <returns></returns>
+        public int GetLevelPackLevelCount()
+        {
+            var levels = GetCurrentLevelPackLevels();
+            if (levels == null)
+            {
+                return 0;
+            }
+
+            return levels.Length;
+        }
+
+        /// <summary>
+        /// Select a level pack.
+        /// </summary>
+        /// <param name="levelPackId"></param>
+        public void SelectLevelPack(String levelPackId)
+        {
+            Logger.Trace("SelectLevelPack({0})", levelPackId);
+
+            try
+            {
+                var levelPacks = GetLevelPackCollection();
+                var index = GetLevelPackIndexByPackId(levelPackId);
+                var pack = GetLevelPackByPackId(levelPackId);
+
+                if (index < 0)
+                {
+                    Logger.Debug("Cannot select level packs yet...");
+                    return;
+                }
+
+                Logger.Info("Selecting level pack index: {0}", pack.packName);
+                var tableView = LevelPacksTableView.GetPrivateField<TableView>("_tableView");
+
+                LevelPacksTableView.SelectCellWithIdx(index);
+                tableView.SelectCellWithIdx(index, true);
+                tableView.ScrollToCellWithIdx(0, TableView.ScrollPositionType.Beginning, false);
+                for (int i = 0; i < index; i++)
+                {
+                    tableView.PageScrollDown();
+                }
+
+                Logger.Debug("Done selecting level pack!");
+            }
+            catch (Exception e)
+            {
+                Logger.Exception(e);
+            }
+        }
+
+        /// <summary>
+        /// Scroll TableView to proper row, fire events.
+        /// </summary>
+        /// <param name="table"></param>
+        /// <param name="levelID"></param>
+        public void SelectAndScrollToLevel(LevelPackLevelsTableView table, string levelID)
+        {
+            Logger.Debug("Scrolling to LevelID: {0}", levelID);
+
+            // Check once per load
+            if (!_checkedForTwitchPlugin)
+            {
+                Logger.Info("Checking for BeatSaber Twitch Integration Plugin...");
+                _detectedTwitchPluginQueue = Resources.FindObjectsOfTypeAll<VRUIViewController>().Any(x => x.name == "RequestInfo");
+                Logger.Info("BeatSaber Twitch Integration plugin detected: " + _detectedTwitchPluginQueue);
+
+                _checkedForTwitchPlugin = true;
+            }
+
+            // Skip scrolling to level if twitch plugin has queue active.
+            if (_detectedTwitchPluginQueue)
+            {
+                Logger.Debug("Skipping SelectAndScrollToLevel() because we detected Twitch Integration Plugin has a Queue active...");
+                return;
+            }
+
+            // try to find the index and scroll to it
+            int selectedIndex = 0;
+            List<IPreviewBeatmapLevel> levels = table.GetPrivateField<IBeatmapLevelPack>("_pack").beatmapLevelCollection.beatmapLevels.ToList();
+            if (levels.Count <= 0)
+            {
+                return;
+            }
+
+            // acquire the index or try the last row
+            selectedIndex = levels.FindIndex(x => x.levelID == levelID);
+            if (selectedIndex < 0)
+            {
+                // this might look like an off by one error but the _level list we keep is missing the header entry BeatSaber.
+                // so the last row is +1 the max index, the count.
+                int maxCount = GetLevelPackLevelCount();
+
+                int selectedRow = table.GetPrivateField<int>("_selectedRow");
+
+                Logger.Debug("Song is not in the level pack, cannot scroll to it...  Using last known row {0}/{1}", selectedRow, maxCount);
+                selectedIndex = Math.Min(maxCount, selectedRow);
+            }
+            else
+            {
+                // the header counts as an index, so if the index came from the level array we have to add 1.
+                selectedIndex += 1;
+            }
+
+            ScrollToLevelByRow(selectedIndex);
+        }
+
+        /// <summary>
+        /// Scroll to a level by Row
+        /// </summary>
+        /// <param name="selectedIndex"></param>
+        public void ScrollToLevelByRow(int selectedIndex)
+        {
+            Logger.Debug("Scrolling level list to idx: {0}", selectedIndex);
+
+            TableView tableView = LevelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
+            LevelPackLevelsTableView.HandleDidSelectRowEvent(tableView, selectedIndex);
+            tableView.ScrollToCellWithIdx(selectedIndex, TableView.ScrollPositionType.Beginning, true);
+            tableView.SelectCellWithIdx(selectedIndex);            
+        }
+
+        /// <summary>
+        /// Try to refresh the song list.  Broken for now.
+        /// </summary>
+        public void RefreshSongList(string currentSelectedLevelId, bool scrollToLevel = true)
+        {
+            Logger.Info("Refreshing the song list view.");
+            try
+            {
+                var levels = GetCurrentLevelPackLevels();
+
+                Logger.Debug("Checking if TableView is initialized...");
+                TableView tableView = ReflectionUtil.GetPrivateField<TableView>(LevelPackLevelsTableView, "_tableView");
+                bool tableViewInit = ReflectionUtil.GetPrivateField<bool>(tableView, "_isInitialized");
+
+                Logger.Debug("Reloading SongList TableView");
+                tableView.ReloadData();
+
+                Logger.Debug("Attempting to scroll to level...");
+                String selectedLevelID = currentSelectedLevelId;
+                if (!String.IsNullOrEmpty(currentSelectedLevelId))
+                {
+                    selectedLevelID = currentSelectedLevelId;
+                }
+                else
+                {
+                    if (levels.Length > 0)
+                    {
+                        selectedLevelID = levels.FirstOrDefault().levelID;
+                    }
+                }
+
+                if (scrollToLevel)
+                {
+                    SelectAndScrollToLevel(LevelPackLevelsTableView, selectedLevelID);
+                }
+            }
+            catch (Exception e)
+            {
+                Logger.Exception("Exception refreshing song list:", e);
+            }
+        }
+    }
+}

File diff suppressed because it is too large
+ 450 - 703
SongBrowserPlugin/UI/Browser/SongBrowserUI.cs


+ 10 - 205
SongBrowserPlugin/UI/UIBuilder.cs

@@ -1,4 +1,5 @@
-using SongBrowser.DataAccess;
+using CustomUI.BeatSaber;
+using SongBrowser.DataAccess;
 using System;
 using System;
 using System.Linq;
 using System.Linq;
 using TMPro;
 using TMPro;
@@ -44,18 +45,6 @@ namespace SongBrowser.UI
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Helper, create a UI button from template name.
-        /// </summary>
-        /// <param name="parent"></param>
-        /// <param name="buttonTemplateName"></param>
-        /// <returns></returns>
-        static public Button CreateUIButton(RectTransform parent, String buttonTemplateName)
-        {
-            Button b = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == buttonTemplateName));
-            return CreateUIButton(parent, b);
-        }
-
-        /// <summary>
         /// Clone a Unity Button into a Button we control.
         /// Clone a Unity Button into a Button we control.
         /// </summary>
         /// </summary>
         /// <param name="parent"></param>
         /// <param name="parent"></param>
@@ -73,76 +62,6 @@ namespace SongBrowser.UI
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Very generic helper create button method.
-        /// </summary>
-        /// <param name="parent"></param>
-        /// <param name="buttonTemplate"></param>
-        /// <param name="buttonText"></param>
-        /// <param name="x"></param>
-        /// <param name="y"></param>
-        /// <param name="w"></param>
-        /// <param name="h"></param>
-        /// <returns></returns>
-        static public Button CreateButton(RectTransform parent, Button buttonTemplate, String buttonText, float fontSize, float x, float y, float w, float h)
-        {
-            Button newButton = UIBuilder.CreateUIButton(parent, buttonTemplate);
-
-            newButton.interactable = true;
-            (newButton.transform as RectTransform).anchoredPosition = new Vector2(x, y);
-            (newButton.transform as RectTransform).sizeDelta = new Vector2(w, h);
-
-            UIBuilder.SetButtonText(newButton, buttonText);
-            UIBuilder.SetButtonIconEnabled(newButton, false);
-            UIBuilder.SetButtonTextSize(newButton, fontSize);
-
-            return newButton;
-        }
-
-        /// <summary>
-        /// Generic create sort button.
-        /// </summary>
-        /// <param name="rect"></param>
-        /// <param name="templateButtonName"></param>
-        /// <param name="buttonText"></param>
-        /// <param name="iconName"></param>
-        /// <param name="x"></param>
-        /// <param name="y"></param>
-        /// <param name="w"></param>
-        /// <param name="h"></param>
-        /// <param name="action"></param>
-        public static SongSortButton CreateSortButton(RectTransform parent, Button buttonTemplate, Sprite iconSprite, Sprite borderSprite, string buttonText, float fontSize, float x, float y, float w, float h, SongSortMode sortMode, System.Action<SongSortMode> onClickEvent)
-        {
-            SongSortButton sortButton = new SongSortButton();
-            Button newButton = UIBuilder.CreateUIButton(parent, buttonTemplate);
-
-            newButton.interactable = true;
-            (newButton.transform as RectTransform).anchoredPosition = new Vector2(x, y);
-            (newButton.transform as RectTransform).sizeDelta = new Vector2(w, h);
-
-            UIBuilder.SetButtonText(newButton, buttonText);
-            UIBuilder.SetButtonIconEnabled(newButton, false);
-            UIBuilder.SetButtonIcon(newButton, iconSprite);
-            UIBuilder.SetButtonTextSize(newButton, fontSize);
-
-            Image stroke = newButton.GetComponentsInChildren<Image>().First(btn => btn.name == "Stroke");
-            stroke.sprite = borderSprite;
-            //stroke.rectTransform.localScale = new Vector2(0.9f, 0.9f);
-
-            newButton.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(2, 2, 2, 2);
-
-            newButton.onClick.RemoveAllListeners();
-            newButton.onClick.AddListener(delegate ()
-            {
-                onClickEvent(sortMode);
-            });
-
-            sortButton.Button = newButton;
-            sortButton.SortMode = sortMode;
-
-            return sortButton;
-        }
-
-        /// <summary>
         /// Create an icon button, simple.
         /// Create an icon button, simple.
         /// </summary>
         /// </summary>
         /// <param name="parent"></param>
         /// <param name="parent"></param>
@@ -160,79 +79,10 @@ namespace SongBrowser.UI
                 UnityEngine.Object.Destroy(textRect.gameObject);
                 UnityEngine.Object.Destroy(textRect.gameObject);
             }
             }
 
 
-            UIBuilder.SetButtonIcon(newButton, iconSprite);
-
-            return newButton;
-        }
-
-        /// <summary>
-        /// Create an icon button, advanced.
-        /// Currently has some weird logic in it just for the filter buttons.  Use simple method instead.
-        /// </summary>
-        /// <param name="parent"></param>
-        /// <param name="buttonTemplate"></param>
-        /// <param name="iconSprite"></param>
-        /// <param name="x"></param>
-        /// <param name="y"></param>
-        /// <param name="w"></param>
-        /// <param name="h"></param>
-        /// <param name="iconWidth"></param>
-        /// <param name="iconHeight"></param>
-        /// <param name="iconRotation"></param>
-        /// <returns></returns>
-        public static Button CreateIconButton(RectTransform parent, Button buttonTemplate, Sprite iconSprite, Vector2 pos, Vector2 size, Vector2 iconSize, Vector2 iconScale, float iconRotation)
-        {            
-            Button newButton = UIBuilder.CreateIconButton(parent, buttonTemplate, iconSprite);
-
-            (newButton.transform as RectTransform).anchoredPosition = new Vector2(pos.x, pos.y);
-            (newButton.transform as RectTransform).sizeDelta = new Vector2(size.x, size.y);
-
-            RectTransform iconTransform = newButton.GetComponentsInChildren<RectTransform>(true).First(c => c.name == "Icon");
-            iconTransform.gameObject.SetActive(true);
-            
-            HorizontalLayoutGroup hgroup = iconTransform.parent.GetComponent<HorizontalLayoutGroup>();
-            hgroup.padding = new RectOffset(1, 1, 0, 0);
-
-            iconTransform.sizeDelta = new Vector2(iconSize.x, iconSize.y);
-            iconTransform.localScale = new Vector2(iconScale.x, iconScale.y);
-            iconTransform.Rotate(0, 0, iconRotation);
+            newButton.SetButtonIcon(iconSprite);
+            newButton.onClick.RemoveAllListeners();
 
 
             return newButton;
             return newButton;
-        }   
-
-        /// <summary>
-        /// Adjust a Button text.
-        /// </summary>
-        /// <param name="button"></param>
-        /// <param name="text"></param>
-        static public void SetButtonText(Button button, string text)
-        {
-            Polyglot.LocalizedTextMeshProUGUI localizer = button.GetComponentInChildren<Polyglot.LocalizedTextMeshProUGUI>();
-            if (localizer != null)
-            {
-                GameObject.Destroy(localizer);
-            }
-
-            TextMeshProUGUI txt = button.GetComponentsInChildren<TextMeshProUGUI>().FirstOrDefault(x => x.name == "Text");
-            if (txt != null)
-            {
-                txt.text = text;
-                txt.verticalMapping = TextureMappingOptions.Line;
-            }
-        }
-
-        /// <summary>
-        /// Adjust button text size.
-        /// </summary>
-        /// <param name="button"></param>
-        /// <param name="fontSize"></param>
-        static public void SetButtonTextSize(Button button, float fontSize)
-        {
-            TextMeshProUGUI txt = button.GetComponentsInChildren<TextMeshProUGUI>().FirstOrDefault(x => x.name == "Text");
-            if (txt != null)
-            {
-                txt.fontSize = fontSize;                
-            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -250,56 +100,6 @@ namespace SongBrowser.UI
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Set a button icon.
-        /// </summary>
-        /// <param name="button"></param>
-        /// <param name="icon"></param>
-        static public void SetButtonIcon(Button button, Sprite icon)
-        {
-            if (button.GetComponentsInChildren<UnityEngine.UI.Image>().Count() > 1)
-            {
-                Image img = button.GetComponentsInChildren<Image>().FirstOrDefault(x => x.name == "Icon");
-                if (img != null)
-                {
-                    img.sprite = icon;
-                }
-            }            
-        }
-
-        /// <summary>
-        /// Disable a button icon.
-        /// </summary>
-        /// <param name="button"></param>
-        /// <param name="enabled"></param>
-        static public void SetButtonIconEnabled(Button button, bool enabled)
-        {
-            Image img = button.GetComponentsInChildren<Image>(true).FirstOrDefault(x => x.name == "Icon");
-            if (img != null)
-            {
-                img.enabled = enabled;
-                UnityEngine.Object.DestroyImmediate(img.gameObject);
-            }
-        }
-
-        /// <summary>
-        /// Adjust button background color.
-        /// </summary>
-        /// <param name="button"></param>
-        /// <param name="background"></param>
-        static public void SetButtonBackground(Button button, Sprite background)
-        {
-            Image img = button.GetComponentsInChildren<Image>().FirstOrDefault(x => x.name == "BG");
-            if (img != null)
-            {
-                img.sprite = background;
-            }
-            else
-            {
-                Logger.Debug("NULL BG");
-            }
-        }
-
-        /// <summary>
         /// Adjust button border.
         /// Adjust button border.
         /// </summary>
         /// </summary>
         /// <param name="button"></param>
         /// <param name="button"></param>
@@ -314,7 +114,7 @@ namespace SongBrowser.UI
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// 
+        /// Find and adjust a stat panel item text fields.
         /// </summary>
         /// </summary>
         /// <param name="rect"></param>
         /// <param name="rect"></param>
         /// <param name="text"></param>
         /// <param name="text"></param>
@@ -327,6 +127,11 @@ namespace SongBrowser.UI
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Find and adjust a stat panel item icon.
+        /// </summary>
+        /// <param name="rect"></param>
+        /// <param name="icon"></param>
         static public void SetStatButtonIcon(RectTransform rect, Sprite icon)
         static public void SetStatButtonIcon(RectTransform rect, Sprite icon)
         {
         {
             Image img = rect.GetComponentsInChildren<Image>().FirstOrDefault(x => x.name == "Icon");
             Image img = rect.GetComponentsInChildren<Image>().FirstOrDefault(x => x.name == "Icon");