Procházet zdrojové kódy

Merge pull request #5 from halsafar/devel

Merging devel into production
Stephen Damm před 6 roky
rodič
revize
45e762f9c7

+ 5 - 4
README.md

@@ -10,18 +10,19 @@ A plugin for customizing the in-game song browser.
 ## Features
 - Marking a song as favorite
 - Currently supports these sorting methods:
-  - Default: By song name.
-  - Author: By song author name then by song name.
   - Favorite: Anything marked favorite followed by the Default method.
+  - Song: By song name (default).
+  - Author: By song author name then by song name.  
   - Original: Match the original sorting you would normally get after SongLoaderPlugin.
   - Newest: Sort songs by their last write time.
+  - PlayCount: Sort by playcount (sum of play counts across all difficulties for a given song).
 - Clicking a sorting method will resort the song list immediately.
+- Sort buttons can be pressed a second time to invert the sorting.
 
 ## Status
-- Mostly Working!
+- Working!
 
 ### Known Issues
 - Might be some issues with game modes other than SoloStandard.
 - Some issues might occur if a new song is added in-game.
-- Add to favorites button shows wrong text sometimes in `not favorite` sort mode.
 

binární
Screenshot.png


+ 1 - 0
SongBrowserPlugin/DataAccess/SongBrowserSettings.cs

@@ -14,6 +14,7 @@ namespace SongBrowserPlugin.DataAccess
         Favorites,
         Original,
         Newest,
+        PlayCount,
     }
 
     [Serializable]

+ 2 - 2
SongBrowserPlugin/SongBrowserApplication.cs

@@ -22,7 +22,7 @@ namespace SongBrowserPlugin
         // Song Browser UI Elements
         private SongBrowserUI _songBrowserUI;
         public Dictionary<String, Sprite> CachedIcons;
-        public Button ButtonTemplate;
+        //public Button PlayButtonTemplate;
 
         /// <summary>
         /// 
@@ -122,7 +122,7 @@ namespace SongBrowserPlugin
                     CachedIcons.Add(sprite.name, sprite);
                 }
 
-                ButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "PlayButton"));
+                //PlayButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "PlayButton"));
 
                 // Append our own event to appropriate events so we can refresh the song list before the user sees it.
                 MainFlowCoordinator mainFlow = Resources.FindObjectsOfTypeAll<MainFlowCoordinator>().First();

+ 31 - 3
SongBrowserPlugin/SongBrowserModel.cs

@@ -11,6 +11,8 @@ namespace SongBrowserPlugin
 {
     public class SongBrowserModel
     {
+        public static String LastSelectedLevelId { get; set; }
+
         private Logger _log = new Logger("SongBrowserModel");
         
         private SongBrowserSettings _settings;
@@ -21,7 +23,7 @@ namespace SongBrowserPlugin
         private SongLoaderPlugin.OverrideClasses.CustomLevelCollectionSO _gameplayModeCollection;    
         private Dictionary<String, double> _cachedLastWriteTimes;
 
-        public static String LastSelectedLevelId { get; set; }
+        public bool InvertingResults { get; private set; }
 
         public SongBrowserSettings Settings
         {
@@ -67,6 +69,14 @@ namespace SongBrowserPlugin
         }
 
         /// <summary>
+        /// 
+        /// </summary>
+        public void ToggleInverting()
+        {
+            this.InvertingResults = !this.InvertingResults;
+        }
+
+        /// <summary>
         /// Get the song cache from the game.
         /// TODO: This might not even be necessary anymore.  Need to test interactions with BeatSaverDownloader.
         /// </summary>
@@ -91,7 +101,7 @@ namespace SongBrowserPlugin
             // Update song Infos
             this.UpdateSongInfos(gameplayMode);
                                 
-            this.ProcessSongList();                       
+            this.ProcessSongList(gameplayMode);                       
         }
 
         /// <summary>
@@ -113,7 +123,7 @@ namespace SongBrowserPlugin
         /// <summary>
         /// Sort the song list based on the settings.
         /// </summary>
-        private void ProcessSongList()
+        private void ProcessSongList(GameplayMode gameplayMode)
         {
             _log.Trace("ProcessSongList()");
 
@@ -148,6 +158,8 @@ namespace SongBrowserPlugin
                 }
             }*/
 
+            PlayerDynamicData playerData = GameDataModel.instance.gameDynamicData.GetCurrentPlayerDynamicData();
+
             Stopwatch stopwatch = Stopwatch.StartNew();
 
             switch (_settings.sortMode)
@@ -185,6 +197,17 @@ namespace SongBrowserPlugin
                         .ThenBy(x => x.songName)
                         .ToList();
                     break;
+                case SongSortMode.PlayCount:
+                    _log.Info("Sorting song list by playcount");
+                    // Build a map of levelId to sum of all playcounts and sort.
+                    IEnumerable<LevelDifficulty> difficultyIterator = Enum.GetValues(typeof(LevelDifficulty)).Cast<LevelDifficulty>();
+                    Dictionary<string, int> _levelIdToPlayCount = _originalSongs.ToDictionary(x => x.levelID, x => difficultyIterator.Sum(difficulty => playerData.GetPlayerLevelStatsData(x.levelID, difficulty, gameplayMode).playCount));
+                    _sortedSongs = _originalSongs
+                        .AsQueryable()
+                        .OrderByDescending(x => _levelIdToPlayCount[x.levelID])
+                        .ThenBy(x => x.songName)
+                        .ToList();
+                    break;
                 case SongSortMode.Default:
                 default:
                     _log.Info("Sorting song list as default (songName)");
@@ -196,6 +219,11 @@ namespace SongBrowserPlugin
                     break;
             }
 
+            if (this.InvertingResults)
+            {
+                _sortedSongs.Reverse();
+            }
+
             stopwatch.Stop();
             _log.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds);
         }        

+ 4 - 0
SongBrowserPlugin/SongBrowserPlugin.csproj

@@ -59,6 +59,10 @@
     <Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
       <HintPath>D:\Games\Steam\SteamApps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
     </Reference>
+    <Reference Include="UnityEngine.ImageConversionModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>D:\Games\Steam\SteamApps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.ImageConversionModule.dll</HintPath>
+    </Reference>
     <Reference Include="UnityEngine.JSONSerializeModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
       <HintPath>D:\Games\Steam\SteamApps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.JSONSerializeModule.dll</HintPath>
     </Reference>

+ 6 - 0
SongBrowserPlugin/SongBrowserPlugin.sln

@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SongBrowserPlugin", "SongBr
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SongBrowserPluginTest", "..\SongBrowserPluginTest\SongBrowserPluginTest.csproj", "{E5465EF3-B227-47BE-B7D1-624E0DAC275D}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SongBrowserUnitTests", "..\SongBrowserUnitTests\SongBrowserUnitTests.csproj", "{E99F8C53-715A-4F2F-910F-5AF26587E5A6}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
 		{E5465EF3-B227-47BE-B7D1-624E0DAC275D}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E5465EF3-B227-47BE-B7D1-624E0DAC275D}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E5465EF3-B227-47BE-B7D1-624E0DAC275D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E99F8C53-715A-4F2F-910F-5AF26587E5A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E99F8C53-715A-4F2F-910F-5AF26587E5A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E99F8C53-715A-4F2F-910F-5AF26587E5A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E99F8C53-715A-4F2F-910F-5AF26587E5A6}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 70 - 17
SongBrowserPlugin/UI/SongBrowserUI.cs

@@ -21,12 +21,13 @@ namespace SongBrowserPlugin.UI
         private Logger _log = new Logger(Name);
 
         // Beat Saber UI Elements
-        StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
-        StandardLevelListViewController _levelListViewController;
-        StandardLevelDetailViewController _levelDetailViewController;
-        StandardLevelDifficultyViewController _levelDifficultyViewController;
-        StandardLevelSelectionNavigationController _levelSelectionNavigationController;
-        StandardLevelListTableView _levelListTableView;
+        private StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
+        private StandardLevelListViewController _levelListViewController;
+        private StandardLevelDetailViewController _levelDetailViewController;
+        private StandardLevelDifficultyViewController _levelDifficultyViewController;
+        private StandardLevelSelectionNavigationController _levelSelectionNavigationController;
+        private StandardLevelListTableView _levelListTableView;
+        private RectTransform _tableViewRectTransform;
 
         // New UI Elements
         private List<SongSortButton> _sortButtonGroup;
@@ -117,36 +118,71 @@ namespace SongBrowserPlugin.UI
             _log.Trace("CreateUIElements");
 
             try
-            {                               
-                RectTransform rect = this._levelSelectionNavigationController.transform as RectTransform;
+            {
+                // Resize some of the UI
+                _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
+                _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
+                _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
 
+                RectTransform _pageUp = _tableViewRectTransform.GetComponentsInChildren<RectTransform>().First(x => x.name == "PageUpButton");
+                _pageUp.anchoredPosition = new Vector2(0f, -1f);
+
+                RectTransform _pageDown = _tableViewRectTransform.GetComponentsInChildren<RectTransform>().First(x => x.name == "PageDownButton");
+                _pageDown.anchoredPosition = new Vector2(0f, 1f);
+                
                 // Create Sorting Songs By-Buttons
                 _log.Debug("Creating sort by buttons...");
 
+                RectTransform rect = this._levelSelectionNavigationController.transform as RectTransform;
+                Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
+
                 System.Action<SongSortMode> onSortButtonClickEvent = delegate (SongSortMode sortMode) {
                     _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
                     SongBrowserModel.LastSelectedLevelId = null;
 
+                    if (_model.Settings.sortMode == sortMode)
+                    {
+                        _model.ToggleInverting();
+                    }
+
                     _model.Settings.sortMode = sortMode;
                     _model.Settings.Save();
+
                     UpdateSongList();
                     RefreshSongList();
                 };
 
-                _sortButtonGroup = new List<SongSortButton>
+                Button sortButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "PlayButton"));
+                Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "QuitButton"));
+
+                float fontSize = 2.75f;
+                float buttonWidth = 17.0f;
+                float buttonHeight = 5.0f;
+                float buttonX = 68.0f;
+                float buttonY = 74.5f;
+
+                string[] buttonNames = new string[]
                 {
-                    UIBuilder.CreateSortButton(rect, "PlayButton", "Favorite", 3, "AllDirectionsIcon", 66, 74.5f, 16f, 5f, SongSortMode.Favorites, onSortButtonClickEvent),
-                    UIBuilder.CreateSortButton(rect, "PlayButton", "Song", 3, "AllDirectionsIcon", 50f, 74.5f, 16f, 5f, SongSortMode.Default, onSortButtonClickEvent),
-                    UIBuilder.CreateSortButton(rect, "PlayButton", "Author", 3, "AllDirectionsIcon", 34f, 74.5f, 16f, 5f, SongSortMode.Author, onSortButtonClickEvent),
-                    UIBuilder.CreateSortButton(rect, "PlayButton", "Original", 3, "AllDirectionsIcon", 18f, 74.5f, 16f, 5f, SongSortMode.Original, onSortButtonClickEvent),
-                    UIBuilder.CreateSortButton(rect, "PlayButton", "Newest", 3, "AllDirectionsIcon", 2f, 74.5f, 16f, 5f, SongSortMode.Newest, onSortButtonClickEvent),
+                    "Favorite", "Song", "Author", "Original", "Newest", "PlayCount"
                 };
 
+                SongSortMode[] sortModes = new SongSortMode[]
+                {
+                    SongSortMode.Favorites, SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount
+                };
+
+                _sortButtonGroup = new List<SongSortButton>();
+                for (int i = 0; i < buttonNames.Length; i++)
+                {
+                    _sortButtonGroup.Add(UIBuilder.CreateSortButton(rect, sortButtonTemplate, arrowIcon, buttonNames[i], fontSize, buttonX, buttonY, buttonWidth, buttonHeight, sortModes[i], onSortButtonClickEvent));
+                    buttonX -= buttonWidth;
+                }
+
                 // Creaate Add to Favorites Button
                 _log.Debug("Creating add to favorites button...");
 
                 RectTransform transform = this._levelDetailViewController.transform as RectTransform;
-                _addFavoriteButton = UIBuilder.CreateUIButton(transform, "QuitButton", SongBrowserApplication.Instance.ButtonTemplate);
+                _addFavoriteButton = UIBuilder.CreateUIButton(transform, otherButtonTemplate);
                 (_addFavoriteButton.transform as RectTransform).anchoredPosition = new Vector2(40f, 5.75f);
                 (_addFavoriteButton.transform as RectTransform).sizeDelta = new Vector2(10f, 10f);
                 UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
@@ -170,7 +206,7 @@ namespace SongBrowserPlugin.UI
                 _log.Debug("Creating delete button...");
 
                 transform = this._levelDetailViewController.transform as RectTransform;
-                _deleteButton = UIBuilder.CreateUIButton(transform, "QuitButton", SongBrowserApplication.Instance.ButtonTemplate);
+                _deleteButton = UIBuilder.CreateUIButton(transform, otherButtonTemplate);
                 (_deleteButton.transform as RectTransform).anchoredPosition = new Vector2(46f, 0f);
                 (_deleteButton.transform as RectTransform).sizeDelta = new Vector2(15f, 5f);
                 UIBuilder.SetButtonText(ref _deleteButton, "Delete");
@@ -326,9 +362,20 @@ namespace SongBrowserPlugin.UI
             foreach (SongSortButton sortButton in _sortButtonGroup)
             {
                 UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
+                //UIBuilder.SetButtonIconEnabled(ref sortButton.Button, false);
                 if (sortButton.SortMode == _model.Settings.sortMode)
                 {
-                    UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
+                    //UIBuilder.SetButtonIcon(ref sortButton.Button, SongBrowserApplication.Instance.CachedIcons["ArrowIcon"]);
+                    //UIBuilder.SetButtonIconEnabled(ref sortButton.Button, true);
+
+                    if (_model.InvertingResults)
+                    {
+                        UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
+                    }
+                    else
+                    {
+                        UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
+                    }
                 }
             }            
         }
@@ -434,6 +481,12 @@ namespace SongBrowserPlugin.UI
                     _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
                 }
 
+                // select current sort mode again (toggle inverting)
+                if (Input.GetKeyDown(KeyCode.Y))
+                {
+                    _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
+                }
+
                 // delete
                 if (Input.GetKeyDown(KeyCode.D))
                 {

+ 33 - 16
SongBrowserPlugin/UI/UIBuilder.cs

@@ -1,9 +1,18 @@
-using UnityEngine;
+using HMUI;
+using SongBrowserPlugin.DataAccess;
+using System;
+using System.Collections;
+using System.Collections.Generic;
 using System.Linq;
-using UnityEngine.UI;
+using System.Text;
 using TMPro;
+using UnityEngine;
+using UnityEngine.Events;
+using UnityEngine.SceneManagement;
+using UnityEngine.UI;
 using VRUI;
-using SongBrowserPlugin.DataAccess;
+using Image = UnityEngine.UI.Image;
+
 
 namespace SongBrowserPlugin.UI
 {
@@ -47,16 +56,12 @@ namespace SongBrowserPlugin.UI
         /// <param name="buttonTemplate"></param>
         /// <param name="buttonInstance"></param>
         /// <returns></returns>
-        static public Button CreateUIButton(RectTransform parent, string buttonTemplate, Button buttonInstance)
+        static public Button CreateUIButton(RectTransform parent, Button buttonTemplate)
         {
-            if (buttonInstance == null)
-            {
-                return null;
-            }
-
-            Button btn = UnityEngine.Object.Instantiate(Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == buttonTemplate)), parent, false);
+            Button btn = UnityEngine.Object.Instantiate(buttonTemplate, parent, false);
             UnityEngine.Object.DestroyImmediate(btn.GetComponent<GameEventOnUIButtonClick>());
             btn.onClick = new Button.ButtonClickedEvent();
+            btn.name = "CustomUIButton";
 
             return btn;
         }
@@ -73,18 +78,29 @@ namespace SongBrowserPlugin.UI
         /// <param name="w"></param>
         /// <param name="h"></param>
         /// <param name="action"></param>
-        public static SongSortButton CreateSortButton(RectTransform rect, string templateButtonName, string buttonText, float fontSize, string iconName, float x, float y, float w, float h, SongSortMode sortMode, System.Action<SongSortMode> onClickEvent)
+        public static SongSortButton CreateSortButton(RectTransform rect, Button buttonTemplate, Sprite iconSprite, 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(rect, templateButtonName, SongBrowserApplication.Instance.ButtonTemplate);
+            Button newButton = UIBuilder.CreateUIButton(rect, buttonTemplate);
 
             newButton.interactable = true;
             (newButton.transform as RectTransform).anchoredPosition = new Vector2(x, y);
             (newButton.transform as RectTransform).sizeDelta = new Vector2(w, h);
 
+            /*RectTransform iconTransform = newButton.GetComponentsInChildren<RectTransform>(true).First(c => c.name == "Icon");
+            iconTransform.gameObject.SetActive(false);
+
+            HorizontalLayoutGroup hgroup = iconTransform.parent.GetComponent<HorizontalLayoutGroup>();
+            hgroup.padding = new RectOffset();
+            hgroup.childForceExpandWidth = true;
+            hgroup.childForceExpandHeight = true;
+            iconTransform.sizeDelta = new Vector2(5f, 5f);
+            iconTransform.localScale = new Vector2(1f, 1f);
+            iconTransform.anchoredPosition = new Vector2(x, y);*/
+
             UIBuilder.SetButtonText(ref newButton, buttonText);
-            //UIBuilder.SetButtonIconEnabled(ref _originalButton, false);
-            UIBuilder.SetButtonIcon(ref newButton, SongBrowserApplication.Instance.CachedIcons[iconName]);
+            UIBuilder.SetButtonIconEnabled(ref newButton, false);
+            UIBuilder.SetButtonIcon(ref newButton, iconSprite);
             UIBuilder.SetButtonTextSize(ref newButton, fontSize);
 
             newButton.onClick.RemoveAllListeners();
@@ -148,8 +164,6 @@ namespace SongBrowserPlugin.UI
             {
                 button.GetComponentInChildren<TextMeshProUGUI>().fontSize = fontSize;
             }
-
-
         }
 
         /// <summary>
@@ -161,7 +175,9 @@ namespace SongBrowserPlugin.UI
         {
             if (button.GetComponentsInChildren<UnityEngine.UI.Image>().Count() > 1)
             {
+                Console.WriteLine("SETTING ICONS");
                 button.GetComponentsInChildren<Image>().First(x => x.name == "Icon").sprite = icon;
+                //button.GetComponentsInChildren<Image>().First(x => x.name == "Icon").transform.Rotate(0, 0, 90);
             }            
         }
 
@@ -174,6 +190,7 @@ namespace SongBrowserPlugin.UI
         {
             if (button.GetComponentsInChildren<UnityEngine.UI.Image>().Count() > 1)
             {
+                //button.GetComponentsInChildren<Image>().First(x => x.name == "Icon").gameObject.SetActive(enabled);
                 button.GetComponentsInChildren<UnityEngine.UI.Image>()[1].enabled = enabled;
             }
         }

+ 20 - 0
SongBrowserUnitTests/Properties/AssemblyInfo.cs

@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("SongBrowserUnitTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SongBrowserUnitTests")]
+[assembly: AssemblyCopyright("Copyright ©  2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("e99f8c53-715a-4f2f-910f-5af26587e5a6")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 76 - 0
SongBrowserUnitTests/SongBrowserUnitTests.csproj

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{E99F8C53-715A-4F2F-910F-5AF26587E5A6}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>SongBrowserUnitTests</RootNamespace>
+    <AssemblyName>SongBrowserUnitTests</AssemblyName>
+    <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
+    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
+    <IsCodedUITest>False</IsCodedUITest>
+    <TestProjectType>UnitTest</TestProjectType>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\SongBrowserPlugin\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\SongBrowserPlugin\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\SongBrowserPlugin\SongBrowserPlugin.csproj">
+      <Project>{6f9b6801-9f4b-4d1f-805d-271c95733814}</Project>
+      <Name>SongBrowserPlugin</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Test\Data\" />
+  </ItemGroup>
+  <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props'))" />
+    <Error Condition="!Exists('..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets'))" />
+  </Target>
+  <Import Project="..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\SongBrowserPlugin\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets')" />
+</Project>

+ 5 - 0
SongBrowserUnitTests/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="MSTest.TestAdapter" version="1.2.1" targetFramework="net45" />
+  <package id="MSTest.TestFramework" version="1.2.1" targetFramework="net45" />
+</packages>