diff --git a/.gitattributes b/.gitattributes index f9cb7c5c9a..baf69b41d1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,19 +1,23 @@ -# This won't normalise line endings, but it will ensure that merge drivers use CRLF -* -text eol=crlf - -# Currently in-use binary file extensions -*.blend binary -*.bmp binary -*.dll binary -*.exe binary -*.icns binary -*.ico binary -*.jpg binary -*.osz2 binary -*.pdn binary -*.psd binary -*.PSD binary -*.tga binary -*.ttf binary -*.wav binary -*.xnb binary +# Autodetect text files and ensure that we normalise their +# line endings to lf internally. When checked out they may +# use different line endings. +* text=auto + +# Check out with crlf (Windows) line endings +*.sln text eol=crlf +*.csproj text eol=crlf +*.cs text diff=csharp eol=crlf +*.resx text eol=crlf +*.vsixmanifest text eol=crlf +packages.config text eol=crlf +App.config text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf +*.snippet text eol=crlf + +# Check out with lf (UNIX) line endings +*.sh text eol=lf +.gitignore text eol=lf +.gitattributes text eol=lf +*.md text eol=lf +.travis.yml text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore index 33ff47e0df..5138e940ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,259 +1,259 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -bin/[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -project.fragment.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc -Staging/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +bin/[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +Staging/ diff --git a/.travis.yml b/.travis.yml index cb2b6d92db..ce353d9b27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,2 @@ -language: csharp +language: csharp solution: osu.sln \ No newline at end of file diff --git a/osu-framework b/osu-framework index 02d7a0fa47..eb6362eaf1 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 02d7a0fa4798d197cd08570ee48951edbb7c7860 +Subproject commit eb6362eaf1317b0fa27b2c9e559bd9a0f1ce357c diff --git a/osu.Desktop.Deploy/App.config b/osu.Desktop.Deploy/App.config index f6673fef1a..9ec53d5a31 100644 --- a/osu.Desktop.Deploy/App.config +++ b/osu.Desktop.Deploy/App.config @@ -1,21 +1,21 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Desktop.Deploy/GitHubObject.cs b/osu.Desktop.Deploy/GitHubObject.cs index 59c15f8015..02ff16fa69 100644 --- a/osu.Desktop.Deploy/GitHubObject.cs +++ b/osu.Desktop.Deploy/GitHubObject.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; - -namespace osu.Desktop.Deploy -{ - public class GitHubObject - { - [JsonProperty(@"id")] - public int Id; - - [JsonProperty(@"name")] - public string Name; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; + +namespace osu.Desktop.Deploy +{ + public class GitHubObject + { + [JsonProperty(@"id")] + public int Id; + + [JsonProperty(@"name")] + public string Name; + } +} diff --git a/osu.Desktop.Deploy/GitHubRelease.cs b/osu.Desktop.Deploy/GitHubRelease.cs index 22820b047a..faf312d4f7 100644 --- a/osu.Desktop.Deploy/GitHubRelease.cs +++ b/osu.Desktop.Deploy/GitHubRelease.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; - -namespace osu.Desktop.Deploy -{ - public class GitHubRelease - { - [JsonProperty(@"id")] - public int Id; - - [JsonProperty(@"tag_name")] - public string TagName => $"v{Name}"; - - [JsonProperty(@"name")] - public string Name; - - [JsonProperty(@"draft")] - public bool Draft; - - [JsonProperty(@"prerelease")] - public bool PreRelease; - - [JsonProperty(@"upload_url")] - public string UploadUrl; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; + +namespace osu.Desktop.Deploy +{ + public class GitHubRelease + { + [JsonProperty(@"id")] + public int Id; + + [JsonProperty(@"tag_name")] + public string TagName => $"v{Name}"; + + [JsonProperty(@"name")] + public string Name; + + [JsonProperty(@"draft")] + public bool Draft; + + [JsonProperty(@"prerelease")] + public bool PreRelease; + + [JsonProperty(@"upload_url")] + public string UploadUrl; + } +} diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs index 3c1451d555..6095ce062d 100644 --- a/osu.Desktop.Deploy/Program.cs +++ b/osu.Desktop.Deploy/Program.cs @@ -1,438 +1,438 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Diagnostics; -using System.IO; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.IO.Network; -using FileWebRequest = osu.Framework.IO.Network.FileWebRequest; -using WebRequest = osu.Framework.IO.Network.WebRequest; - -namespace osu.Desktop.Deploy -{ - internal static class Program - { - private static string packages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); - private static string nugetPath => Path.Combine(packages, @"nuget.commandline\4.5.1\tools\NuGet.exe"); - private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.7.8\tools\Squirrel.exe"); - private const string msbuild_path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"; - - public static string StagingFolder = ConfigurationManager.AppSettings["StagingFolder"]; - public static string ReleasesFolder = ConfigurationManager.AppSettings["ReleasesFolder"]; - public static string GitHubAccessToken = ConfigurationManager.AppSettings["GitHubAccessToken"]; - public static string GitHubUsername = ConfigurationManager.AppSettings["GitHubUsername"]; - public static string GitHubRepoName = ConfigurationManager.AppSettings["GitHubRepoName"]; - public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"]; - public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"]; - public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"]; - public static string TargetNames = ConfigurationManager.AppSettings["TargetName"]; - public static string PackageName = ConfigurationManager.AppSettings["PackageName"]; - public static string IconName = ConfigurationManager.AppSettings["IconName"]; - public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"]; - - public static string GitHubApiEndpoint => $"https://api.github.com/repos/{GitHubUsername}/{GitHubRepoName}/releases"; - public static string GitHubReleasePage => $"https://github.com/{GitHubUsername}/{GitHubRepoName}/releases"; - - /// - /// How many previous build deltas we want to keep when publishing. - /// - private const int keep_delta_count = 4; - - private static string codeSigningCmd => string.IsNullOrEmpty(codeSigningPassword) ? "" : $"-n \"/a /f {codeSigningCertPath} /p {codeSigningPassword} /t http://timestamp.comodoca.com/authenticode\""; - - private static string homeDir => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - private static string codeSigningCertPath => Path.Combine(homeDir, CodeSigningCertificate); - private static string solutionPath => Environment.CurrentDirectory; - private static string stagingPath => Path.Combine(solutionPath, StagingFolder); - private static string iconPath => Path.Combine(solutionPath, ProjectName, IconName); - - private static string nupkgFilename(string ver) => $"{PackageName}.{ver}.nupkg"; - private static string nupkgDistroFilename(string ver) => $"{PackageName}-{ver}-full.nupkg"; - - private static readonly Stopwatch sw = new Stopwatch(); - - private static string codeSigningPassword; - - public static void Main(string[] args) - { - displayHeader(); - - findSolutionPath(); - - if (!Directory.Exists(ReleasesFolder)) - { - write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow); - Directory.CreateDirectory(ReleasesFolder); - } - - checkGitHubReleases(); - - refreshDirectory(StagingFolder); - - //increment build number until we have a unique one. - string verBase = DateTime.Now.ToString("yyyy.Mdd."); - int increment = 0; - while (Directory.GetFiles(ReleasesFolder, $"*{verBase}{increment}*").Any()) - increment++; - - string version = $"{verBase}{increment}"; - - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Ready to deploy {version}: "); - Console.ReadLine(); - - sw.Start(); - - if (!string.IsNullOrEmpty(CodeSigningCertificate)) - { - Console.Write("Enter code signing password: "); - codeSigningPassword = readLineMasked(); - } - - write("Updating AssemblyInfo..."); - updateCsprojVersion(version); - - write("Running build process..."); - foreach (string targetName in TargetNames.Split(',')) - runCommand(msbuild_path, $"/v:quiet /m /t:{targetName.Replace('.', '_')} /p:OutputPath={stagingPath};Targets=\"Clean;Build\";Configuration=Release {SolutionName}.sln"); - - write("Creating NuGet deployment package..."); - runCommand(nugetPath, $"pack {NuSpecName} -Version {version} -Properties Configuration=Deploy -OutputDirectory {stagingPath} -BasePath {stagingPath}"); - - //prune once before checking for files so we can avoid erroring on files which aren't even needed for this build. - pruneReleases(); - - checkReleaseFiles(); - - write("Running squirrel build..."); - runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi"); - - //prune again to clean up before upload. - pruneReleases(); - - //rename setup to install. - File.Copy(Path.Combine(ReleasesFolder, "Setup.exe"), Path.Combine(ReleasesFolder, "install.exe"), true); - File.Delete(Path.Combine(ReleasesFolder, "Setup.exe")); - - uploadBuild(version); - - //reset assemblyinfo. - updateCsprojVersion("0.0.0"); - - write("Done!", ConsoleColor.White); - Console.ReadLine(); - } - - private static void displayHeader() - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(); - Console.WriteLine(" Please note that OSU! and PPY are registered trademarks and as such covered by trademark law."); - Console.WriteLine(" Do not distribute builds of this project publicly that make use of these."); - Console.ResetColor(); - Console.WriteLine(); - } - - /// - /// Ensure we have all the files in the release directory which are expected to be there. - /// This should have been accounted for in earlier steps, and just serves as a verification step. - /// - private static void checkReleaseFiles() - { - if (!canGitHub) return; - - var releaseLines = getReleaseLines(); - - //ensure we have all files necessary - foreach (var l in releaseLines) - if (!File.Exists(Path.Combine(ReleasesFolder, l.Filename))) - error($"Local file missing {l.Filename}"); - } - - private static IEnumerable getReleaseLines() => File.ReadAllLines(Path.Combine(ReleasesFolder, "RELEASES")).Select(l => new ReleaseLine(l)); - - private static void pruneReleases() - { - if (!canGitHub) return; - - write("Pruning RELEASES..."); - - var releaseLines = getReleaseLines().ToList(); - - var fulls = releaseLines.Where(l => l.Filename.Contains("-full")).Reverse().Skip(1); - - //remove any FULL releases (except most recent) - foreach (var l in fulls) - { - write($"- Removing old release {l.Filename}", ConsoleColor.Yellow); - File.Delete(Path.Combine(ReleasesFolder, l.Filename)); - releaseLines.Remove(l); - } - - //remove excess deltas - var deltas = releaseLines.Where(l => l.Filename.Contains("-delta")).ToArray(); - if (deltas.Length > keep_delta_count) - { - foreach (var l in deltas.Take(deltas.Length - keep_delta_count)) - { - write($"- Removing old delta {l.Filename}", ConsoleColor.Yellow); - File.Delete(Path.Combine(ReleasesFolder, l.Filename)); - releaseLines.Remove(l); - } - } - - var lines = new List(); - releaseLines.ForEach(l => lines.Add(l.ToString())); - File.WriteAllLines(Path.Combine(ReleasesFolder, "RELEASES"), lines); - } - - private static void uploadBuild(string version) - { - if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate)) - return; - - write("Publishing to GitHub..."); - - write($"- Creating release {version}...", ConsoleColor.Yellow); - var req = new JsonWebRequest($"{GitHubApiEndpoint}") - { - Method = HttpMethod.POST, - }; - req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease - { - Name = version, - Draft = true, - PreRelease = true - })); - req.AuthenticatedBlockingPerform(); - - var assetUploadUrl = req.ResponseObject.UploadUrl.Replace("{?name,label}", "?name={0}"); - foreach (var a in Directory.GetFiles(ReleasesFolder).Reverse()) //reverse to upload RELEASES first. - { - write($"- Adding asset {a}...", ConsoleColor.Yellow); - var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a)) - { - Method = HttpMethod.POST, - Timeout = 240000, - ContentType = "application/octet-stream", - }; - - upload.AddRaw(File.ReadAllBytes(a)); - upload.AuthenticatedBlockingPerform(); - } - - openGitHubReleasePage(); - } - - private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage); - - private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken); - - private static void checkGitHubReleases() - { - if (!canGitHub) return; - - write("Checking GitHub releases..."); - var req = new JsonWebRequest>($"{GitHubApiEndpoint}"); - req.AuthenticatedBlockingPerform(); - - var lastRelease = req.ResponseObject.FirstOrDefault(); - - if (lastRelease == null) - return; - - if (lastRelease.Draft) - { - openGitHubReleasePage(); - error("There's a pending draft release! You probably don't want to push a build with this present."); - } - - //there's a previous release for this project. - var assetReq = new JsonWebRequest>($"{GitHubApiEndpoint}/{lastRelease.Id}/assets"); - assetReq.AuthenticatedBlockingPerform(); - var assets = assetReq.ResponseObject; - - //make sure our RELEASES file is the same as the last build on the server. - var releaseAsset = assets.FirstOrDefault(a => a.Name == "RELEASES"); - - //if we don't have a RELEASES asset then the previous release likely wasn't a Squirrel one. - if (releaseAsset == null) return; - - write($"Last GitHub release was {lastRelease.Name}."); - - bool requireDownload = false; - - if (!File.Exists(Path.Combine(ReleasesFolder, nupkgDistroFilename(lastRelease.Name)))) - { - write("Last version's package not found locally.", ConsoleColor.Red); - requireDownload = true; - } - else - { - var lastReleases = new RawFileWebRequest($"{GitHubApiEndpoint}/assets/{releaseAsset.Id}"); - lastReleases.AuthenticatedBlockingPerform(); - if (File.ReadAllText(Path.Combine(ReleasesFolder, "RELEASES")) != lastReleases.ResponseString) - { - write("Server's RELEASES differed from ours.", ConsoleColor.Red); - requireDownload = true; - } - } - - if (!requireDownload) return; - - write("Refreshing local releases directory..."); - refreshDirectory(ReleasesFolder); - - foreach (var a in assets) - { - if (a.Name.EndsWith(".exe")) continue; - - write($"- Downloading {a.Name}...", ConsoleColor.Yellow); - new FileWebRequest(Path.Combine(ReleasesFolder, a.Name), $"{GitHubApiEndpoint}/assets/{a.Id}").AuthenticatedBlockingPerform(); - } - } - - private static void refreshDirectory(string directory) - { - if (Directory.Exists(directory)) - Directory.Delete(directory, true); - Directory.CreateDirectory(directory); - } - - private static void updateCsprojVersion(string version) - { - var toUpdate = new[] { "", "" }; - string file = Path.Combine(ProjectName, $"{ProjectName}.csproj"); - - var l1 = File.ReadAllLines(file); - List l2 = new List(); - foreach (var l in l1) - { - string line = l; - - foreach (var tag in toUpdate) - { - int startIndex = l.IndexOf(tag, StringComparison.InvariantCulture); - if (startIndex == -1) - continue; - startIndex += tag.Length; - - int endIndex = l.IndexOf("<", startIndex, StringComparison.InvariantCulture); - line = $"{l.Substring(0, startIndex)}{version}{l.Substring(endIndex)}"; - } - - l2.Add(line); - } - - File.WriteAllLines(file, l2); - } - - /// - /// Find the base path of the active solution (git checkout location) - /// - private static void findSolutionPath() - { - string path = Path.GetDirectoryName(Environment.CommandLine.Replace("\"", "").Trim()); - - if (string.IsNullOrEmpty(path)) - path = Environment.CurrentDirectory; - - while (!File.Exists(Path.Combine(path, $"{SolutionName}.sln"))) - path = path.Remove(path.LastIndexOf(Path.DirectorySeparatorChar)); - path += Path.DirectorySeparatorChar; - - Environment.CurrentDirectory = path; - } - - private static bool runCommand(string command, string args) - { - var psi = new ProcessStartInfo(command, args) - { - WorkingDirectory = solutionPath, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden - }; - - Process p = Process.Start(psi); - if (p == null) return false; - - string output = p.StandardOutput.ReadToEnd(); - output += p.StandardError.ReadToEnd(); - - if (p.ExitCode == 0) return true; - - write(output); - error($"Command {command} {args} failed!"); - return false; - } - - private static string readLineMasked() - { - var fg = Console.ForegroundColor; - Console.ForegroundColor = Console.BackgroundColor; - var ret = Console.ReadLine(); - Console.ForegroundColor = fg; - - return ret; - } - - private static void error(string message) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"FATAL ERROR: {message}"); - - Console.ReadLine(); - Environment.Exit(-1); - } - - private static void write(string message, ConsoleColor col = ConsoleColor.Gray) - { - if (sw.ElapsedMilliseconds > 0) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.Write(sw.ElapsedMilliseconds.ToString().PadRight(8)); - } - Console.ForegroundColor = col; - Console.WriteLine(message); - } - - public static void AuthenticatedBlockingPerform(this WebRequest r) - { - r.AddHeader("Authorization", $"token {GitHubAccessToken}"); - r.Perform(); - } - } - - internal class RawFileWebRequest : WebRequest - { - public RawFileWebRequest(string url) : base(url) - { - } - - protected override string Accept => "application/octet-stream"; - } - - internal class ReleaseLine - { - public string Hash; - public string Filename; - public int Filesize; - - public ReleaseLine(string line) - { - var split = line.Split(' '); - Hash = split[0]; - Filename = split[1]; - Filesize = int.Parse(split[2]); - } - - public override string ToString() => $"{Hash} {Filename} {Filesize}"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.IO.Network; +using FileWebRequest = osu.Framework.IO.Network.FileWebRequest; +using WebRequest = osu.Framework.IO.Network.WebRequest; + +namespace osu.Desktop.Deploy +{ + internal static class Program + { + private static string packages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + private static string nugetPath => Path.Combine(packages, @"nuget.commandline\4.5.1\tools\NuGet.exe"); + private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.7.8\tools\Squirrel.exe"); + private const string msbuild_path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"; + + public static string StagingFolder = ConfigurationManager.AppSettings["StagingFolder"]; + public static string ReleasesFolder = ConfigurationManager.AppSettings["ReleasesFolder"]; + public static string GitHubAccessToken = ConfigurationManager.AppSettings["GitHubAccessToken"]; + public static string GitHubUsername = ConfigurationManager.AppSettings["GitHubUsername"]; + public static string GitHubRepoName = ConfigurationManager.AppSettings["GitHubRepoName"]; + public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"]; + public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"]; + public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"]; + public static string TargetNames = ConfigurationManager.AppSettings["TargetName"]; + public static string PackageName = ConfigurationManager.AppSettings["PackageName"]; + public static string IconName = ConfigurationManager.AppSettings["IconName"]; + public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"]; + + public static string GitHubApiEndpoint => $"https://api.github.com/repos/{GitHubUsername}/{GitHubRepoName}/releases"; + public static string GitHubReleasePage => $"https://github.com/{GitHubUsername}/{GitHubRepoName}/releases"; + + /// + /// How many previous build deltas we want to keep when publishing. + /// + private const int keep_delta_count = 4; + + private static string codeSigningCmd => string.IsNullOrEmpty(codeSigningPassword) ? "" : $"-n \"/a /f {codeSigningCertPath} /p {codeSigningPassword} /t http://timestamp.comodoca.com/authenticode\""; + + private static string homeDir => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + private static string codeSigningCertPath => Path.Combine(homeDir, CodeSigningCertificate); + private static string solutionPath => Environment.CurrentDirectory; + private static string stagingPath => Path.Combine(solutionPath, StagingFolder); + private static string iconPath => Path.Combine(solutionPath, ProjectName, IconName); + + private static string nupkgFilename(string ver) => $"{PackageName}.{ver}.nupkg"; + private static string nupkgDistroFilename(string ver) => $"{PackageName}-{ver}-full.nupkg"; + + private static readonly Stopwatch sw = new Stopwatch(); + + private static string codeSigningPassword; + + public static void Main(string[] args) + { + displayHeader(); + + findSolutionPath(); + + if (!Directory.Exists(ReleasesFolder)) + { + write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow); + Directory.CreateDirectory(ReleasesFolder); + } + + checkGitHubReleases(); + + refreshDirectory(StagingFolder); + + //increment build number until we have a unique one. + string verBase = DateTime.Now.ToString("yyyy.Mdd."); + int increment = 0; + while (Directory.GetFiles(ReleasesFolder, $"*{verBase}{increment}*").Any()) + increment++; + + string version = $"{verBase}{increment}"; + + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Ready to deploy {version}: "); + Console.ReadLine(); + + sw.Start(); + + if (!string.IsNullOrEmpty(CodeSigningCertificate)) + { + Console.Write("Enter code signing password: "); + codeSigningPassword = readLineMasked(); + } + + write("Updating AssemblyInfo..."); + updateCsprojVersion(version); + + write("Running build process..."); + foreach (string targetName in TargetNames.Split(',')) + runCommand(msbuild_path, $"/v:quiet /m /t:{targetName.Replace('.', '_')} /p:OutputPath={stagingPath};Targets=\"Clean;Build\";Configuration=Release {SolutionName}.sln"); + + write("Creating NuGet deployment package..."); + runCommand(nugetPath, $"pack {NuSpecName} -Version {version} -Properties Configuration=Deploy -OutputDirectory {stagingPath} -BasePath {stagingPath}"); + + //prune once before checking for files so we can avoid erroring on files which aren't even needed for this build. + pruneReleases(); + + checkReleaseFiles(); + + write("Running squirrel build..."); + runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi"); + + //prune again to clean up before upload. + pruneReleases(); + + //rename setup to install. + File.Copy(Path.Combine(ReleasesFolder, "Setup.exe"), Path.Combine(ReleasesFolder, "install.exe"), true); + File.Delete(Path.Combine(ReleasesFolder, "Setup.exe")); + + uploadBuild(version); + + //reset assemblyinfo. + updateCsprojVersion("0.0.0"); + + write("Done!", ConsoleColor.White); + Console.ReadLine(); + } + + private static void displayHeader() + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(); + Console.WriteLine(" Please note that OSU! and PPY are registered trademarks and as such covered by trademark law."); + Console.WriteLine(" Do not distribute builds of this project publicly that make use of these."); + Console.ResetColor(); + Console.WriteLine(); + } + + /// + /// Ensure we have all the files in the release directory which are expected to be there. + /// This should have been accounted for in earlier steps, and just serves as a verification step. + /// + private static void checkReleaseFiles() + { + if (!canGitHub) return; + + var releaseLines = getReleaseLines(); + + //ensure we have all files necessary + foreach (var l in releaseLines) + if (!File.Exists(Path.Combine(ReleasesFolder, l.Filename))) + error($"Local file missing {l.Filename}"); + } + + private static IEnumerable getReleaseLines() => File.ReadAllLines(Path.Combine(ReleasesFolder, "RELEASES")).Select(l => new ReleaseLine(l)); + + private static void pruneReleases() + { + if (!canGitHub) return; + + write("Pruning RELEASES..."); + + var releaseLines = getReleaseLines().ToList(); + + var fulls = releaseLines.Where(l => l.Filename.Contains("-full")).Reverse().Skip(1); + + //remove any FULL releases (except most recent) + foreach (var l in fulls) + { + write($"- Removing old release {l.Filename}", ConsoleColor.Yellow); + File.Delete(Path.Combine(ReleasesFolder, l.Filename)); + releaseLines.Remove(l); + } + + //remove excess deltas + var deltas = releaseLines.Where(l => l.Filename.Contains("-delta")).ToArray(); + if (deltas.Length > keep_delta_count) + { + foreach (var l in deltas.Take(deltas.Length - keep_delta_count)) + { + write($"- Removing old delta {l.Filename}", ConsoleColor.Yellow); + File.Delete(Path.Combine(ReleasesFolder, l.Filename)); + releaseLines.Remove(l); + } + } + + var lines = new List(); + releaseLines.ForEach(l => lines.Add(l.ToString())); + File.WriteAllLines(Path.Combine(ReleasesFolder, "RELEASES"), lines); + } + + private static void uploadBuild(string version) + { + if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate)) + return; + + write("Publishing to GitHub..."); + + write($"- Creating release {version}...", ConsoleColor.Yellow); + var req = new JsonWebRequest($"{GitHubApiEndpoint}") + { + Method = HttpMethod.POST, + }; + req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease + { + Name = version, + Draft = true, + PreRelease = true + })); + req.AuthenticatedBlockingPerform(); + + var assetUploadUrl = req.ResponseObject.UploadUrl.Replace("{?name,label}", "?name={0}"); + foreach (var a in Directory.GetFiles(ReleasesFolder).Reverse()) //reverse to upload RELEASES first. + { + write($"- Adding asset {a}...", ConsoleColor.Yellow); + var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a)) + { + Method = HttpMethod.POST, + Timeout = 240000, + ContentType = "application/octet-stream", + }; + + upload.AddRaw(File.ReadAllBytes(a)); + upload.AuthenticatedBlockingPerform(); + } + + openGitHubReleasePage(); + } + + private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage); + + private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken); + + private static void checkGitHubReleases() + { + if (!canGitHub) return; + + write("Checking GitHub releases..."); + var req = new JsonWebRequest>($"{GitHubApiEndpoint}"); + req.AuthenticatedBlockingPerform(); + + var lastRelease = req.ResponseObject.FirstOrDefault(); + + if (lastRelease == null) + return; + + if (lastRelease.Draft) + { + openGitHubReleasePage(); + error("There's a pending draft release! You probably don't want to push a build with this present."); + } + + //there's a previous release for this project. + var assetReq = new JsonWebRequest>($"{GitHubApiEndpoint}/{lastRelease.Id}/assets"); + assetReq.AuthenticatedBlockingPerform(); + var assets = assetReq.ResponseObject; + + //make sure our RELEASES file is the same as the last build on the server. + var releaseAsset = assets.FirstOrDefault(a => a.Name == "RELEASES"); + + //if we don't have a RELEASES asset then the previous release likely wasn't a Squirrel one. + if (releaseAsset == null) return; + + write($"Last GitHub release was {lastRelease.Name}."); + + bool requireDownload = false; + + if (!File.Exists(Path.Combine(ReleasesFolder, nupkgDistroFilename(lastRelease.Name)))) + { + write("Last version's package not found locally.", ConsoleColor.Red); + requireDownload = true; + } + else + { + var lastReleases = new RawFileWebRequest($"{GitHubApiEndpoint}/assets/{releaseAsset.Id}"); + lastReleases.AuthenticatedBlockingPerform(); + if (File.ReadAllText(Path.Combine(ReleasesFolder, "RELEASES")) != lastReleases.ResponseString) + { + write("Server's RELEASES differed from ours.", ConsoleColor.Red); + requireDownload = true; + } + } + + if (!requireDownload) return; + + write("Refreshing local releases directory..."); + refreshDirectory(ReleasesFolder); + + foreach (var a in assets) + { + if (a.Name.EndsWith(".exe")) continue; + + write($"- Downloading {a.Name}...", ConsoleColor.Yellow); + new FileWebRequest(Path.Combine(ReleasesFolder, a.Name), $"{GitHubApiEndpoint}/assets/{a.Id}").AuthenticatedBlockingPerform(); + } + } + + private static void refreshDirectory(string directory) + { + if (Directory.Exists(directory)) + Directory.Delete(directory, true); + Directory.CreateDirectory(directory); + } + + private static void updateCsprojVersion(string version) + { + var toUpdate = new[] { "", "" }; + string file = Path.Combine(ProjectName, $"{ProjectName}.csproj"); + + var l1 = File.ReadAllLines(file); + List l2 = new List(); + foreach (var l in l1) + { + string line = l; + + foreach (var tag in toUpdate) + { + int startIndex = l.IndexOf(tag, StringComparison.InvariantCulture); + if (startIndex == -1) + continue; + startIndex += tag.Length; + + int endIndex = l.IndexOf("<", startIndex, StringComparison.InvariantCulture); + line = $"{l.Substring(0, startIndex)}{version}{l.Substring(endIndex)}"; + } + + l2.Add(line); + } + + File.WriteAllLines(file, l2); + } + + /// + /// Find the base path of the active solution (git checkout location) + /// + private static void findSolutionPath() + { + string path = Path.GetDirectoryName(Environment.CommandLine.Replace("\"", "").Trim()); + + if (string.IsNullOrEmpty(path)) + path = Environment.CurrentDirectory; + + while (!File.Exists(Path.Combine(path, $"{SolutionName}.sln"))) + path = path.Remove(path.LastIndexOf(Path.DirectorySeparatorChar)); + path += Path.DirectorySeparatorChar; + + Environment.CurrentDirectory = path; + } + + private static bool runCommand(string command, string args) + { + var psi = new ProcessStartInfo(command, args) + { + WorkingDirectory = solutionPath, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden + }; + + Process p = Process.Start(psi); + if (p == null) return false; + + string output = p.StandardOutput.ReadToEnd(); + output += p.StandardError.ReadToEnd(); + + if (p.ExitCode == 0) return true; + + write(output); + error($"Command {command} {args} failed!"); + return false; + } + + private static string readLineMasked() + { + var fg = Console.ForegroundColor; + Console.ForegroundColor = Console.BackgroundColor; + var ret = Console.ReadLine(); + Console.ForegroundColor = fg; + + return ret; + } + + private static void error(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"FATAL ERROR: {message}"); + + Console.ReadLine(); + Environment.Exit(-1); + } + + private static void write(string message, ConsoleColor col = ConsoleColor.Gray) + { + if (sw.ElapsedMilliseconds > 0) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write(sw.ElapsedMilliseconds.ToString().PadRight(8)); + } + Console.ForegroundColor = col; + Console.WriteLine(message); + } + + public static void AuthenticatedBlockingPerform(this WebRequest r) + { + r.AddHeader("Authorization", $"token {GitHubAccessToken}"); + r.Perform(); + } + } + + internal class RawFileWebRequest : WebRequest + { + public RawFileWebRequest(string url) : base(url) + { + } + + protected override string Accept => "application/octet-stream"; + } + + internal class ReleaseLine + { + public string Hash; + public string Filename; + public int Filesize; + + public ReleaseLine(string line) + { + var split = line.Split(' '); + Hash = split[0]; + Filename = split[1]; + Filesize = int.Parse(split[2]); + } + + public override string ToString() => $"{Hash} {Filename} {Filesize}"; + } +} diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index a18db9477a..6b7509a381 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -11,7 +11,7 @@ - + diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 3704d2d5d8..a495d7048d 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -1,120 +1,120 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using osu.Desktop.Overlays; -using osu.Framework.Graphics.Containers; -using osu.Framework.Platform; -using osu.Game; -using OpenTK.Input; -using Microsoft.Win32; - -namespace osu.Desktop -{ - internal class OsuGameDesktop : OsuGame - { - private readonly bool noVersionOverlay; - - public OsuGameDesktop(string[] args = null) - : base(args) - { - noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; - } - - public override Storage GetStorageForStableInstall() - { - try - { - return new StableStorage(); - } - catch - { - return null; - } - } - - /// - /// A method of accessing an osu-stable install in a controlled fashion. - /// - private class StableStorage : DesktopStorage - { - protected override string LocateBasePath() - { - bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")); - - string stableInstallPath; - - try - { - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - - if (checkExists(stableInstallPath)) - return stableInstallPath; - } - catch - { - } - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); - if (checkExists(stableInstallPath)) - return stableInstallPath; - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); - if (checkExists(stableInstallPath)) - return stableInstallPath; - - return null; - } - - public StableStorage() - : base(string.Empty) - { - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (!noVersionOverlay) - { - LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => - { - Add(v); - v.State = Visibility.Visible; - }); - } - } - - public override void SetHost(GameHost host) - { - base.SetHost(host); - var desktopWindow = host.Window as DesktopGameWindow; - if (desktopWindow != null) - { - desktopWindow.CursorState |= CursorState.Hidden; - - desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); - desktopWindow.Title = Name; - - desktopWindow.FileDrop += fileDrop; - } - } - - private void fileDrop(object sender, FileDropEventArgs e) - { - var filePaths = new[] { e.FileName }; - - var firstExtension = Path.GetExtension(filePaths.First()); - - if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; - - Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using osu.Desktop.Overlays; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Game; +using OpenTK.Input; +using Microsoft.Win32; + +namespace osu.Desktop +{ + internal class OsuGameDesktop : OsuGame + { + private readonly bool noVersionOverlay; + + public OsuGameDesktop(string[] args = null) + : base(args) + { + noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; + } + + public override Storage GetStorageForStableInstall() + { + try + { + return new StableStorage(); + } + catch + { + return null; + } + } + + /// + /// A method of accessing an osu-stable install in a controlled fashion. + /// + private class StableStorage : DesktopStorage + { + protected override string LocateBasePath() + { + bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")); + + string stableInstallPath; + + try + { + using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + + if (checkExists(stableInstallPath)) + return stableInstallPath; + } + catch + { + } + + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + return null; + } + + public StableStorage() + : base(string.Empty) + { + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!noVersionOverlay) + { + LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => + { + Add(v); + v.State = Visibility.Visible; + }); + } + } + + public override void SetHost(GameHost host) + { + base.SetHost(host); + var desktopWindow = host.Window as DesktopGameWindow; + if (desktopWindow != null) + { + desktopWindow.CursorState |= CursorState.Hidden; + + desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); + desktopWindow.Title = Name; + + desktopWindow.FileDrop += fileDrop; + } + } + + private void fileDrop(object sender, FileDropEventArgs e) + { + var filePaths = new[] { e.FileName }; + + var firstExtension = Path.GetExtension(filePaths.First()); + + if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; + + Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning); + } + } +} diff --git a/osu.Desktop/Overlays/SquirrelUpdateManager.cs b/osu.Desktop/Overlays/SquirrelUpdateManager.cs index 61d8a75ae3..ea86d2f028 100644 --- a/osu.Desktop/Overlays/SquirrelUpdateManager.cs +++ b/osu.Desktop/Overlays/SquirrelUpdateManager.cs @@ -1,164 +1,164 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -#if NET_FRAMEWORK -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; -using osu.Game; -using osu.Game.Graphics; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; -using OpenTK; -using OpenTK.Graphics; -using Squirrel; - -namespace osu.Desktop.Overlays -{ - public class SquirrelUpdateManager : Component - { - private UpdateManager updateManager; - private NotificationOverlay notificationOverlay; - - public void PrepareUpdate() - { - // Squirrel returns execution to us after the update process is started, so it's safe to use Wait() here - UpdateManager.RestartAppWhenExited().Wait(); - } - - [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuGameBase game) - { - notificationOverlay = notification; - - if (game.IsDeployedBuild) - Schedule(() => checkForUpdateAsync()); - } - - private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) - { - //should we schedule a retry on completion of this check? - bool scheduleRetry = true; - - try - { - if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); - - var info = await updateManager.CheckForUpdate(!useDeltaPatching); - if (info.ReleasesToApply.Count == 0) - //no updates available. bail and retry later. - return; - - if (notification == null) - { - notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active }; - Schedule(() => notificationOverlay.Post(notification)); - } - - notification.Progress = 0; - notification.Text = @"Downloading update..."; - - try - { - await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f); - - notification.Progress = 0; - notification.Text = @"Installing update..."; - - await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); - - notification.State = ProgressNotificationState.Completed; - } - catch (Exception e) - { - if (useDeltaPatching) - { - Logger.Error(e, @"delta patching failed!"); - - //could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) - //try again without deltas. - checkForUpdateAsync(false, notification); - scheduleRetry = false; - } - else - { - Logger.Error(e, @"update failed!"); - } - } - } - catch (Exception) - { - // we'll ignore this and retry later. can be triggered by no internet connection or thread abortion. - } - finally - { - if (scheduleRetry) - { - if (notification != null) - notification.State = ProgressNotificationState.Cancelled; - - //check again in 30 minutes. - Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30); - } - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - updateManager?.Dispose(); - } - - private class UpdateProgressNotification : ProgressNotification - { - private readonly SquirrelUpdateManager updateManager; - private OsuGame game; - - public UpdateProgressNotification(SquirrelUpdateManager updateManager) - { - this.updateManager = updateManager; - } - - protected override Notification CreateCompletionNotification() - { - return new ProgressCompletionNotification - { - Text = @"Update ready to install. Click to restart!", - Activated = () => - { - updateManager.PrepareUpdate(); - game.GracefullyExit(); - return true; - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuGame game) - { - this.game = game; - - IconContent.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.YellowDark, colours.Yellow) - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.fa_upload, - Colour = Color4.White, - Size = new Vector2(20), - } - }); - } - } - } -} -#endif +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +#if NET_FRAMEWORK +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Game; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using OpenTK; +using OpenTK.Graphics; +using Squirrel; + +namespace osu.Desktop.Overlays +{ + public class SquirrelUpdateManager : Component + { + private UpdateManager updateManager; + private NotificationOverlay notificationOverlay; + + public void PrepareUpdate() + { + // Squirrel returns execution to us after the update process is started, so it's safe to use Wait() here + UpdateManager.RestartAppWhenExited().Wait(); + } + + [BackgroundDependencyLoader] + private void load(NotificationOverlay notification, OsuGameBase game) + { + notificationOverlay = notification; + + if (game.IsDeployedBuild) + Schedule(() => checkForUpdateAsync()); + } + + private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) + { + //should we schedule a retry on completion of this check? + bool scheduleRetry = true; + + try + { + if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); + + var info = await updateManager.CheckForUpdate(!useDeltaPatching); + if (info.ReleasesToApply.Count == 0) + //no updates available. bail and retry later. + return; + + if (notification == null) + { + notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active }; + Schedule(() => notificationOverlay.Post(notification)); + } + + notification.Progress = 0; + notification.Text = @"Downloading update..."; + + try + { + await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f); + + notification.Progress = 0; + notification.Text = @"Installing update..."; + + await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); + + notification.State = ProgressNotificationState.Completed; + } + catch (Exception e) + { + if (useDeltaPatching) + { + Logger.Error(e, @"delta patching failed!"); + + //could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) + //try again without deltas. + checkForUpdateAsync(false, notification); + scheduleRetry = false; + } + else + { + Logger.Error(e, @"update failed!"); + } + } + } + catch (Exception) + { + // we'll ignore this and retry later. can be triggered by no internet connection or thread abortion. + } + finally + { + if (scheduleRetry) + { + if (notification != null) + notification.State = ProgressNotificationState.Cancelled; + + //check again in 30 minutes. + Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30); + } + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + updateManager?.Dispose(); + } + + private class UpdateProgressNotification : ProgressNotification + { + private readonly SquirrelUpdateManager updateManager; + private OsuGame game; + + public UpdateProgressNotification(SquirrelUpdateManager updateManager) + { + this.updateManager = updateManager; + } + + protected override Notification CreateCompletionNotification() + { + return new ProgressCompletionNotification + { + Text = @"Update ready to install. Click to restart!", + Activated = () => + { + updateManager.PrepareUpdate(); + game.GracefullyExit(); + return true; + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OsuGame game) + { + this.game = game; + + IconContent.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.YellowDark, colours.Yellow) + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_upload, + Colour = Color4.White, + Size = new Vector2(20), + } + }); + } + } + } +} +#endif diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index b61603dcc5..b984c0bbba 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -1,142 +1,142 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Diagnostics; -using osu.Framework.Allocation; -using osu.Framework.Development; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Desktop.Overlays -{ - public class VersionManager : OverlayContainer - { - private OsuConfigManager config; - private OsuGameBase game; - private NotificationOverlay notificationOverlay; - - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - - [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) - { - notificationOverlay = notification; - this.config = config; - this.game = game; - - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - - Alpha = 0; - - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = @"Exo2.0-Bold", - Text = game.Name - }, - new OsuSpriteText - { - Colour = DebugUtils.IsDebug ? colours.Red : Color4.White, - Text = game.Version - }, - } - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextSize = 12, - Colour = colours.Yellow, - Font = @"Venera", - Text = @"Development Build" - }, - new Sprite - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Texture = textures.Get(@"Menu/dev-build-footer"), - }, - } - } - }; - -#if NET_FRAMEWORK - Add(new SquirrelUpdateManager()); -#endif - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - var version = game.Version; - var lastVersion = config.Get(OsuSetting.Version); - if (game.IsDeployedBuild && version != lastVersion) - { - config.Set(OsuSetting.Version, version); - - // only show a notification if we've previously saved a version to the config file (ie. not the first run). - if (!string.IsNullOrEmpty(lastVersion)) - notificationOverlay.Post(new UpdateCompleteNotification(version)); - } - } - - private class UpdateCompleteNotification : SimpleNotification - { - public UpdateCompleteNotification(string version) - { - Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - Icon = FontAwesome.fa_check_square; - Activated = delegate - { - Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}"); - return true; - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IconBackgound.Colour = colours.BlueDark; - } - } - - protected override void PopIn() - { - this.FadeIn(1000); - } - - protected override void PopOut() - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Development; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Desktop.Overlays +{ + public class VersionManager : OverlayContainer + { + private OsuConfigManager config; + private OsuGameBase game; + private NotificationOverlay notificationOverlay; + + public override bool HandleKeyboardInput => false; + public override bool HandleMouseInput => false; + + [BackgroundDependencyLoader] + private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) + { + notificationOverlay = notification; + this.config = config; + this.game = game; + + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + Alpha = 0; + + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = game.Name + }, + new OsuSpriteText + { + Colour = DebugUtils.IsDebug ? colours.Red : Color4.White, + Text = game.Version + }, + } + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextSize = 12, + Colour = colours.Yellow, + Font = @"Venera", + Text = @"Development Build" + }, + new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Texture = textures.Get(@"Menu/dev-build-footer"), + }, + } + } + }; + +#if NET_FRAMEWORK + Add(new SquirrelUpdateManager()); +#endif + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var version = game.Version; + var lastVersion = config.Get(OsuSetting.Version); + if (game.IsDeployedBuild && version != lastVersion) + { + config.Set(OsuSetting.Version, version); + + // only show a notification if we've previously saved a version to the config file (ie. not the first run). + if (!string.IsNullOrEmpty(lastVersion)) + notificationOverlay.Post(new UpdateCompleteNotification(version)); + } + } + + private class UpdateCompleteNotification : SimpleNotification + { + public UpdateCompleteNotification(string version) + { + Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; + Icon = FontAwesome.fa_check_square; + Activated = delegate + { + Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}"); + return true; + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IconBackgound.Colour = colours.BlueDark; + } + } + + protected override void PopIn() + { + this.FadeIn(1000); + } + + protected override void PopOut() + { + } + } +} diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7258610f90..61d2006315 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -1,66 +1,66 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using System.Linq; -using osu.Framework; -using osu.Framework.Platform; -using osu.Game.IPC; -#if NET_FRAMEWORK -using System.Runtime; -#endif - -namespace osu.Desktop -{ - public static class Program - { - [STAThread] - public static int Main(string[] args) - { - // required to initialise native SQLite libraries on some platforms. - - if (!RuntimeInfo.IsMono) - useMulticoreJit(); - - // Back up the cwd before DesktopGameHost changes it - var cwd = Environment.CurrentDirectory; - - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) - { - if (!host.IsPrimaryInstance) - { - var importer = new ArchiveImportIPCChannel(host); - // Restore the cwd so relative paths given at the command line work correctly - Directory.SetCurrentDirectory(cwd); - foreach (var file in args) - { - Console.WriteLine(@"Importing {0}", file); - if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) - throw new TimeoutException(@"IPC took too long to send"); - } - } - else - { - switch (args.FirstOrDefault() ?? string.Empty) - { - default: - host.Run(new OsuGameDesktop(args)); - break; - } - } - - return 0; - } - } - - private static void useMulticoreJit() - { -#if NET_FRAMEWORK - var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles")); - ProfileOptimization.SetProfileRoot(directory.FullName); - ProfileOptimization.StartProfile("Startup.Profile"); -#endif - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Linq; +using osu.Framework; +using osu.Framework.Platform; +using osu.Game.IPC; +#if NET_FRAMEWORK +using System.Runtime; +#endif + +namespace osu.Desktop +{ + public static class Program + { + [STAThread] + public static int Main(string[] args) + { + // required to initialise native SQLite libraries on some platforms. + + if (!RuntimeInfo.IsMono) + useMulticoreJit(); + + // Back up the cwd before DesktopGameHost changes it + var cwd = Environment.CurrentDirectory; + + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + { + if (!host.IsPrimaryInstance) + { + var importer = new ArchiveImportIPCChannel(host); + // Restore the cwd so relative paths given at the command line work correctly + Directory.SetCurrentDirectory(cwd); + foreach (var file in args) + { + Console.WriteLine(@"Importing {0}", file); + if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) + throw new TimeoutException(@"IPC took too long to send"); + } + } + else + { + switch (args.FirstOrDefault() ?? string.Empty) + { + default: + host.Run(new OsuGameDesktop(args)); + break; + } + } + + return 0; + } + } + + private static void useMulticoreJit() + { +#if NET_FRAMEWORK + var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles")); + ProfileOptimization.SetProfileRoot(directory.FullName); + ProfileOptimization.StartProfile("Startup.Profile"); +#endif + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index e40510b71b..bd0cc209b6 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -1,67 +1,67 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Objects; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Rulesets.Catch.Tests -{ - public class CatchBeatmapConversionTest : BeatmapConversionTest - { - protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - - [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")] - public new void Test(string name) - { - base.Test(name); - } - - protected override IEnumerable CreateConvertValue(HitObject hitObject) - { - if (hitObject is JuiceStream stream) - { - foreach (var nested in stream.NestedHitObjects) - { - yield return new ConvertValue - { - StartTime = nested.StartTime, - Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH - }; - } - } - else - { - yield return new ConvertValue - { - StartTime = hitObject.StartTime, - Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH - }; - } - } - - protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter(); - } - - public struct ConvertValue : IEquatable - { - /// - /// A sane value to account for osu!stable using ints everwhere. - /// - private const float conversion_lenience = 2; - - public double StartTime; - public float Position; - - public bool Equals(ConvertValue other) - => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) - && Precision.AlmostEquals(Position, other.Position, conversion_lenience); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class CatchBeatmapConversionTest : BeatmapConversionTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; + + [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")] + public new void Test(string name) + { + base.Test(name); + } + + protected override IEnumerable CreateConvertValue(HitObject hitObject) + { + if (hitObject is JuiceStream stream) + { + foreach (var nested in stream.NestedHitObjects) + { + yield return new ConvertValue + { + StartTime = nested.StartTime, + Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH + }; + } + } + else + { + yield return new ConvertValue + { + StartTime = hitObject.StartTime, + Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH + }; + } + } + + protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter(); + } + + public struct ConvertValue : IEquatable + { + /// + /// A sane value to account for osu!stable using ints everwhere. + /// + private const float conversion_lenience = 2; + + public double StartTime; + public float Position; + + public bool Equals(ConvertValue other) + => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) + && Precision.AlmostEquals(Position, other.Position, conversion_lenience); + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 11a22c69f3..bce20520d3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -1,62 +1,62 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Play; -using osu.Game.Tests.Visual; -using OpenTK; - -namespace osu.Game.Rulesets.Catch.Tests -{ - public class TestCaseAutoJuiceStream : TestCasePlayer - { - public TestCaseAutoJuiceStream() - : base(new CatchRuleset()) - { - } - - protected override Beatmap CreateBeatmap(Ruleset ruleset) - { - var beatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, - Ruleset = ruleset.RulesetInfo - } - }; - - for (int i = 0; i < 100; i++) - { - float width = (i % 10 + 1) / 20f; - - beatmap.HitObjects.Add(new JuiceStream - { - X = 0.5f - width / 2, - ControlPoints = new List - { - Vector2.Zero, - new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) - }, - CurveType = CurveType.Linear, - Distance = width * CatchPlayfield.BASE_WIDTH, - StartTime = i * 2000, - NewCombo = i % 8 == 0 - }); - } - - return beatmap; - } - - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) - { - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - return base.CreatePlayer(beatmap, ruleset); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestCaseAutoJuiceStream : TestCasePlayer + { + public TestCaseAutoJuiceStream() + : base(new CatchRuleset()) + { + } + + protected override Beatmap CreateBeatmap(Ruleset ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, + Ruleset = ruleset.RulesetInfo + } + }; + + for (int i = 0; i < 100; i++) + { + float width = (i % 10 + 1) / 20f; + + beatmap.HitObjects.Add(new JuiceStream + { + X = 0.5f - width / 2, + ControlPoints = new List + { + Vector2.Zero, + new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) + }, + CurveType = CurveType.Linear, + Distance = width * CatchPlayfield.BASE_WIDTH, + StartTime = i * 2000, + NewCombo = i % 8 == 0 + }); + } + + return beatmap; + } + + protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + { + beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + return base.CreatePlayer(beatmap, ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs index ec9dd15673..d13a6bb860 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs @@ -1,47 +1,47 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; -using osu.Game.Rulesets.Catch.UI; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(BananaShower), - typeof(DrawableBananaShower), - - typeof(CatchRuleset), - typeof(CatchRulesetContainer), - }; - - public TestCaseBananaShower() - : base(new CatchRuleset()) - { - } - - protected override Beatmap CreateBeatmap(Ruleset ruleset) - { - var beatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, - Ruleset = ruleset.RulesetInfo - } - }; - - beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true }); - - return beatmap; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BananaShower), + typeof(DrawableBananaShower), + + typeof(CatchRuleset), + typeof(CatchRulesetContainer), + }; + + public TestCaseBananaShower() + : base(new CatchRuleset()) + { + } + + protected override Beatmap CreateBeatmap(Ruleset ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Ruleset = ruleset.RulesetInfo + } + }; + + beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs index efebfa9739..a2c886f1f3 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer - { - public TestCaseCatchPlayer() : base(new CatchRuleset()) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer + { + public TestCaseCatchPlayer() : base(new CatchRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs index 8e5843f40a..2b58fcc93c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer - { - public TestCaseCatchStacker() - : base(new CatchRuleset()) - { - } - - protected override Beatmap CreateBeatmap(Ruleset ruleset) - { - var beatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, - Ruleset = ruleset.RulesetInfo - } - }; - - - for (int i = 0; i < 512; i++) - beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); - - return beatmap; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + { + public TestCaseCatchStacker() + : base(new CatchRuleset()) + { + } + + protected override Beatmap CreateBeatmap(Ruleset ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Ruleset = ruleset.RulesetInfo + } + }; + + + for (int i = 0; i < 512; i++) + beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs index 0329921c92..f239290ed4 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs @@ -1,61 +1,61 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseCatcherArea : OsuTestCase - { - private RulesetInfo catchRuleset; - private TestCatcherArea catcherArea; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(CatcherArea), - }; - - public TestCaseCatcherArea() - { - AddSliderStep("CircleSize", 0, 8, 5, createCatcher); - AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t)); - } - - private void createCatcher(float size) - { - Child = new CatchInputManager(catchRuleset) - { - RelativeSizeAxes = Axes.Both, - Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft - }, - }; - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - catchRuleset = rulesets.GetRuleset(2); - } - - private class TestCatcherArea : CatcherArea - { - public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) - : base(beatmapDifficulty) - { - } - - public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseCatcherArea : OsuTestCase + { + private RulesetInfo catchRuleset; + private TestCatcherArea catcherArea; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CatcherArea), + }; + + public TestCaseCatcherArea() + { + AddSliderStep("CircleSize", 0, 8, 5, createCatcher); + AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t)); + } + + private void createCatcher(float size) + { + Child = new CatchInputManager(catchRuleset) + { + RelativeSizeAxes = Axes.Both, + Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomLeft + }, + }; + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + catchRuleset = rulesets.GetRuleset(2); + } + + private class TestCatcherArea : CatcherArea + { + public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) + : base(beatmapDifficulty) + { + } + + public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs index 595ca6cb24..275752523d 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs @@ -1,74 +1,74 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; -using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; -using osu.Game.Tests.Visual; -using OpenTK; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseFruitObjects : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(CatchHitObject), - typeof(Fruit), - typeof(Droplet), - typeof(DrawableCatchHitObject), - typeof(DrawableFruit), - typeof(DrawableDroplet), - typeof(Pulp), - }; - - public TestCaseFruitObjects() - { - Add(new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - createDrawable(0), - createDrawable(1), - createDrawable(2), - }, - new Drawable[] - { - createDrawable(3), - createDrawable(4), - createDrawable(5), - }, - } - }); - } - - private DrawableFruit createDrawable(int index) - { - var fruit = new Fruit - { - StartTime = 1000000000000, - IndexInBeatmap = index, - Scale = 1.5f, - }; - - return new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - RelativePositionAxes = Axes.Both, - Position = Vector2.Zero, - Alpha = 1, - LifetimeStart = double.NegativeInfinity, - LifetimeEnd = double.PositiveInfinity, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; +using osu.Game.Tests.Visual; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseFruitObjects : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CatchHitObject), + typeof(Fruit), + typeof(Droplet), + typeof(DrawableCatchHitObject), + typeof(DrawableFruit), + typeof(DrawableDroplet), + typeof(Pulp), + }; + + public TestCaseFruitObjects() + { + Add(new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + createDrawable(0), + createDrawable(1), + createDrawable(2), + }, + new Drawable[] + { + createDrawable(3), + createDrawable(4), + createDrawable(5), + }, + } + }); + } + + private DrawableFruit createDrawable(int index) + { + var fruit = new Fruit + { + StartTime = 1000000000000, + IndexInBeatmap = index, + Scale = 1.5f, + }; + + return new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + RelativePositionAxes = Axes.Both, + Position = Vector2.Zero, + Alpha = 1, + LifetimeStart = double.NegativeInfinity, + LifetimeEnd = double.PositiveInfinity, + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs index 7564adea8c..e7f936ca2a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs @@ -1,30 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer - { - public TestCaseHyperdash() - : base(new CatchRuleset()) - { - } - - protected override Beatmap CreateBeatmap(Ruleset ruleset) - { - var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; - - - for (int i = 0; i < 512; i++) - if (i % 5 < 3) - beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); - - return beatmap; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer + { + public TestCaseHyperdash() + : base(new CatchRuleset()) + { + } + + protected override Beatmap CreateBeatmap(Ruleset ruleset) + { + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; + + + for (int i = 0; i < 512; i++) + if (i % 5 < 3) + beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Catch.Tests/TestCasePerformancePoints.cs index 2be6dd005d..9512cf2061 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCasePerformancePoints.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCasePerformancePoints.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints - { - public TestCasePerformancePoints() - : base(new CatchRuleset()) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints + { + public TestCasePerformancePoints() + : base(new CatchRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 51c6d18f1f..7a4c7b3f1c 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -1,10 +1,10 @@ - - - - WinExe - netcoreapp2.0;net461 - - - - + + + + WinExe + netcoreapp2.0;net461 + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 01aa7abb9f..34e5f425fd 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -1,68 +1,68 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using System.Collections.Generic; -using System; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Catch.Beatmaps -{ - public class CatchBeatmapConverter : BeatmapConverter - { - protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; - - protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap) - { - var curveData = obj as IHasCurve; - var positionData = obj as IHasXPosition; - var comboData = obj as IHasCombo; - var endTime = obj as IHasEndTime; - - if (positionData == null) - yield break; - - if (curveData != null) - { - yield return new JuiceStream - { - StartTime = obj.StartTime, - Samples = obj.Samples, - ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, - Distance = curveData.Distance, - RepeatSamples = curveData.RepeatSamples, - RepeatCount = curveData.RepeatCount, - X = positionData.X / CatchPlayfield.BASE_WIDTH, - NewCombo = comboData?.NewCombo ?? false - }; - - yield break; - } - - if (endTime != null) - { - yield return new BananaShower - { - StartTime = obj.StartTime, - Samples = obj.Samples, - Duration = endTime.Duration, - NewCombo = comboData?.NewCombo ?? false - }; - - yield break; - } - - yield return new Fruit - { - StartTime = obj.StartTime, - Samples = obj.Samples, - NewCombo = comboData?.NewCombo ?? false, - X = positionData.X / CatchPlayfield.BASE_WIDTH - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using System.Collections.Generic; +using System; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Catch.Beatmaps +{ + public class CatchBeatmapConverter : BeatmapConverter + { + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; + + protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap) + { + var curveData = obj as IHasCurve; + var positionData = obj as IHasXPosition; + var comboData = obj as IHasCombo; + var endTime = obj as IHasEndTime; + + if (positionData == null) + yield break; + + if (curveData != null) + { + yield return new JuiceStream + { + StartTime = obj.StartTime, + Samples = obj.Samples, + ControlPoints = curveData.ControlPoints, + CurveType = curveData.CurveType, + Distance = curveData.Distance, + RepeatSamples = curveData.RepeatSamples, + RepeatCount = curveData.RepeatCount, + X = positionData.X / CatchPlayfield.BASE_WIDTH, + NewCombo = comboData?.NewCombo ?? false + }; + + yield break; + } + + if (endTime != null) + { + yield return new BananaShower + { + StartTime = obj.StartTime, + Samples = obj.Samples, + Duration = endTime.Duration, + NewCombo = comboData?.NewCombo ?? false + }; + + yield break; + } + + yield return new Fruit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + NewCombo = comboData?.NewCombo ?? false, + X = positionData.X / CatchPlayfield.BASE_WIDTH + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 1bebe9dae0..dfd10e0df7 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -1,72 +1,72 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Catch.Beatmaps -{ - public class CatchBeatmapProcessor : BeatmapProcessor - { - public override void PostProcess(Beatmap beatmap) - { - initialiseHyperDash(beatmap.HitObjects); - - base.PostProcess(beatmap); - - int index = 0; - foreach (var obj in beatmap.HitObjects) - obj.IndexInBeatmap = index++; - } - - private void initialiseHyperDash(List objects) - { - // todo: add difficulty adjust. - double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2; - - int lastDirection = 0; - double lastExcess = halfCatcherWidth; - - int objCount = objects.Count; - - for (int i = 0; i < objCount - 1; i++) - { - CatchHitObject currentObject = objects[i]; - - // not needed? - // if (currentObject is TinyDroplet) continue; - - CatchHitObject nextObject = objects[i + 1]; - - // while (nextObject is TinyDroplet) - // { - // if (++i == objCount - 1) break; - // nextObject = objects[i + 1]; - // } - - int thisDirection = nextObject.X > currentObject.X ? 1 : -1; - double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4; - double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); - - if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext) - { - currentObject.HyperDashTarget = nextObject; - lastExcess = halfCatcherWidth; - } - else - { - //currentObject.DistanceToHyperDash = timeToNext - distanceToNext; - lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth); - } - - lastDirection = thisDirection; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.Beatmaps +{ + public class CatchBeatmapProcessor : BeatmapProcessor + { + public override void PostProcess(Beatmap beatmap) + { + initialiseHyperDash(beatmap.HitObjects); + + base.PostProcess(beatmap); + + int index = 0; + foreach (var obj in beatmap.HitObjects) + obj.IndexInBeatmap = index++; + } + + private void initialiseHyperDash(List objects) + { + // todo: add difficulty adjust. + double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2; + + int lastDirection = 0; + double lastExcess = halfCatcherWidth; + + int objCount = objects.Count; + + for (int i = 0; i < objCount - 1; i++) + { + CatchHitObject currentObject = objects[i]; + + // not needed? + // if (currentObject is TinyDroplet) continue; + + CatchHitObject nextObject = objects[i + 1]; + + // while (nextObject is TinyDroplet) + // { + // if (++i == objCount - 1) break; + // nextObject = objects[i + 1]; + // } + + int thisDirection = nextObject.X > currentObject.X ? 1 : -1; + double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4; + double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); + + if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext) + { + currentObject.HyperDashTarget = nextObject; + lastExcess = halfCatcherWidth; + } + else + { + //currentObject.DistanceToHyperDash = timeToNext - distanceToNext; + lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth); + } + + lastDirection = thisDirection; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs index 917a076426..876b394da0 100644 --- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Catch -{ - public class CatchDifficultyCalculator : DifficultyCalculator - { - public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap) - { - } - - public override double Calculate(Dictionary categoryDifficulty = null) => 0; - - protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Catch +{ + public class CatchDifficultyCalculator : DifficultyCalculator + { + public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap) + { + } + + public override double Calculate(Dictionary categoryDifficulty = null) => 0; + + protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter(); + } +} diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index fa8958687c..4f976bb7b2 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Catch -{ - public class CatchInputManager : RulesetInputManager - { - public CatchInputManager(RulesetInfo ruleset) - : base(ruleset, 0, SimultaneousBindingMode.Unique) - { - } - } - - public enum CatchAction - { - [Description("Move left")] - MoveLeft, - [Description("Move right")] - MoveRight, - [Description("Engage dash")] - Dash, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch +{ + public class CatchInputManager : RulesetInputManager + { + public CatchInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum CatchAction + { + [Description("Move left")] + MoveLeft, + [Description("Move right")] + MoveRight, + [Description("Engage dash")] + Dash, + } +} diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 4dbe65b3ce..8930b09089 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -1,113 +1,113 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Catch.Mods; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Catch.Replays; -using osu.Game.Rulesets.Replays.Types; - -namespace osu.Game.Rulesets.Catch -{ - public class CatchRuleset : Ruleset - { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset); - - public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] - { - new KeyBinding(InputKey.Z, CatchAction.MoveLeft), - new KeyBinding(InputKey.Left, CatchAction.MoveLeft), - new KeyBinding(InputKey.X, CatchAction.MoveRight), - new KeyBinding(InputKey.Right, CatchAction.MoveRight), - new KeyBinding(InputKey.Shift, CatchAction.Dash), - new KeyBinding(InputKey.Shift, CatchAction.Dash), - }; - - public override IEnumerable GetModsFor(ModType type) - { - switch (type) - { - case ModType.DifficultyReduction: - return new Mod[] - { - new CatchModEasy(), - new CatchModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new CatchModHalfTime(), - new CatchModDaycore(), - }, - }, - }; - - case ModType.DifficultyIncrease: - return new Mod[] - { - new CatchModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new CatchModSuddenDeath(), - new CatchModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new CatchModDoubleTime(), - new CatchModNightcore(), - }, - }, - new CatchModHidden(), - new CatchModFlashlight(), - }; - - case ModType.Special: - return new Mod[] - { - new CatchModRelax(), - null, - null, - new MultiMod - { - Mods = new Mod[] - { - new CatchModAutoplay(), - new ModCinema(), - }, - }, - }; - - default: - return new Mod[] { }; - } - } - - public override string Description => "osu!catch"; - - public override string ShortName => "fruits"; - - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; - - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap); - - public override int? LegacyID => 2; - - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); - - public CatchRuleset(RulesetInfo rulesetInfo = null) - : base(rulesetInfo) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Replays.Types; + +namespace osu.Game.Rulesets.Catch +{ + public class CatchRuleset : Ruleset + { + public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset); + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.Z, CatchAction.MoveLeft), + new KeyBinding(InputKey.Left, CatchAction.MoveLeft), + new KeyBinding(InputKey.X, CatchAction.MoveRight), + new KeyBinding(InputKey.Right, CatchAction.MoveRight), + new KeyBinding(InputKey.Shift, CatchAction.Dash), + new KeyBinding(InputKey.Shift, CatchAction.Dash), + }; + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.DifficultyReduction: + return new Mod[] + { + new CatchModEasy(), + new CatchModNoFail(), + new MultiMod + { + Mods = new Mod[] + { + new CatchModHalfTime(), + new CatchModDaycore(), + }, + }, + }; + + case ModType.DifficultyIncrease: + return new Mod[] + { + new CatchModHardRock(), + new MultiMod + { + Mods = new Mod[] + { + new CatchModSuddenDeath(), + new CatchModPerfect(), + }, + }, + new MultiMod + { + Mods = new Mod[] + { + new CatchModDoubleTime(), + new CatchModNightcore(), + }, + }, + new CatchModHidden(), + new CatchModFlashlight(), + }; + + case ModType.Special: + return new Mod[] + { + new CatchModRelax(), + null, + null, + new MultiMod + { + Mods = new Mod[] + { + new CatchModAutoplay(), + new ModCinema(), + }, + }, + }; + + default: + return new Mod[] { }; + } + } + + public override string Description => "osu!catch"; + + public override string ShortName => "fruits"; + + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap); + + public override int? LegacyID => 2; + + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); + + public CatchRuleset(RulesetInfo rulesetInfo = null) + : base(rulesetInfo) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index d0e22bb04e..bb2786f14f 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Judgements; - -namespace osu.Game.Rulesets.Catch.Judgements -{ - public class CatchJudgement : Judgement - { - // todo: wangs - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Rulesets.Catch.Judgements +{ + public class CatchJudgement : Judgement + { + // todo: wangs + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index 8ff08ab825..5ed563614a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Replays; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModAutoplay : ModAutoplay - { - protected override Score CreateReplayScore(Beatmap beatmap) - { - return new Score - { - User = new User { Username = "osu!salad!" }, - Replay = new CatchAutoGenerator(beatmap).Generate(), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModAutoplay : ModAutoplay + { + protected override Score CreateReplayScore(Beatmap beatmap) + { + return new Score + { + User = new User { Username = "osu!salad!" }, + Replay = new CatchAutoGenerator(beatmap).Generate(), + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs index 8eb8fd8435..6d4caef8d2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModDaycore : ModDaycore - { - public override double ScoreMultiplier => 0.3; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModDaycore : ModDaycore + { + public override double ScoreMultiplier => 0.3; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs index 7a7eeed719..dcf417c405 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModDoubleTime : ModDoubleTime - { - public override double ScoreMultiplier => 1.06; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1.06; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index 07bc8b825a..d20a2ec727 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModEasy : ModEasy - { - public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModEasy : ModEasy + { + public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!"; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 424f14ad02..21e09f991c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModFlashlight : ModFlashlight - { - public override double ScoreMultiplier => 1.12; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModFlashlight : ModFlashlight + { + public override double ScoreMultiplier => 1.12; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs index 947990cce5..4e48de454f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModHalfTime : ModHalfTime - { - public override double ScoreMultiplier => 0.3; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModHalfTime : ModHalfTime + { + public override double ScoreMultiplier => 0.3; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index ed33bf7124..9e48e8de74 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModHardRock : ModHardRock - { - public override double ScoreMultiplier => 1.12; - public override bool Ranked => true; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1.12; + public override bool Ranked => true; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index 14291f744c..f2716f351e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModHidden : ModHidden - { - public override string Description => @"Play with fading fruits."; - public override double ScoreMultiplier => 1.06; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModHidden : ModHidden + { + public override string Description => @"Play with fading fruits."; + public override double ScoreMultiplier => 1.06; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs index b53cac0d7c..687db172cf 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModNightcore : ModNightcore - { - public override double ScoreMultiplier => 1.06; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModNightcore : ModNightcore + { + public override double ScoreMultiplier => 1.06; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs index afb2d83720..914419438d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModNoFail : ModNoFail - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModNoFail : ModNoFail + { + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs index bc0dd10f6c..de00ff31ce 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModPerfect : ModPerfect - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModPerfect : ModPerfect + { + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index de0b6bc614..8bf9f32572 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModRelax : ModRelax - { - public override string Description => @"Use the mouse to control the catcher."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModRelax : ModRelax + { + public override string Description => @"Use the mouse to control the catcher."; + } +} diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs index 1e778e5305..71461cfc40 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Catch.Mods -{ - public class CatchModSuddenDeath : ModSuddenDeath - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModSuddenDeath : ModSuddenDeath + { + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 487345019b..a6aba42f03 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -1,48 +1,48 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Catch.Objects -{ - public class BananaShower : CatchHitObject, IHasEndTime - { - public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; - - public override bool LastInCombo => true; - - protected override void CreateNestedHitObjects() - { - base.CreateNestedHitObjects(); - createBananas(); - } - - private void createBananas() - { - double spacing = Duration; - while (spacing > 100) - spacing /= 2; - - if (spacing <= 0) - return; - - for (double i = StartTime; i <= EndTime; i += spacing) - AddNested(new Banana - { - Samples = Samples, - StartTime = i, - X = RNG.NextSingle() - }); - } - - public double EndTime => StartTime + Duration; - - public double Duration { get; set; } - - public class Banana : Fruit - { - public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class BananaShower : CatchHitObject, IHasEndTime + { + public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; + + public override bool LastInCombo => true; + + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + createBananas(); + } + + private void createBananas() + { + double spacing = Duration; + while (spacing > 100) + spacing /= 2; + + if (spacing <= 0) + return; + + for (double i = StartTime; i <= EndTime; i += spacing) + AddNested(new Banana + { + Samples = Samples, + StartTime = i, + X = RNG.NextSingle() + }); + } + + public double EndTime => StartTime + Duration; + + public double Duration { get; set; } + + public class Banana : Fruit + { + public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 1a0ccc9b1e..95ffd41518 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -1,60 +1,60 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Catch.Objects -{ - public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation - { - public const double OBJECT_RADIUS = 44; - - public float X { get; set; } - - public int IndexInBeatmap { get; set; } - - public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4); - - public virtual bool NewCombo { get; set; } - - public int IndexInCurrentCombo { get; set; } - - public int ComboIndex { get; set; } - - /// - /// The next fruit starts a new combo. Used for explodey. - /// - public virtual bool LastInCombo { get; set; } - - public float Scale { get; set; } = 1; - - /// - /// Whether this fruit can initiate a hyperdash. - /// - public bool HyperDash => HyperDashTarget != null; - - /// - /// The target fruit if we are to initiate a hyperdash. - /// - public CatchHitObject HyperDashTarget; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; - } - } - - public enum FruitVisualRepresentation - { - Pear, - Grape, - Raspberry, - Pineapple, - Banana // banananananannaanana - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Catch.Objects +{ + public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation + { + public const double OBJECT_RADIUS = 44; + + public float X { get; set; } + + public int IndexInBeatmap { get; set; } + + public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4); + + public virtual bool NewCombo { get; set; } + + public int IndexInCurrentCombo { get; set; } + + public int ComboIndex { get; set; } + + /// + /// The next fruit starts a new combo. Used for explodey. + /// + public virtual bool LastInCombo { get; set; } + + public float Scale { get; set; } = 1; + + /// + /// Whether this fruit can initiate a hyperdash. + /// + public bool HyperDash => HyperDashTarget != null; + + /// + /// The target fruit if we are to initiate a hyperdash. + /// + public CatchHitObject HyperDashTarget; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; + } + } + + public enum FruitVisualRepresentation + { + Pear, + Grape, + Raspberry, + Pineapple, + Banana // banananananannaanana + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index 3c6ec0703d..739cc6a59b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -1,44 +1,44 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public class DrawableBananaShower : DrawableCatchHitObject - { - private readonly Container bananaContainer; - - public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null) - : base(s) - { - RelativeSizeAxes = Axes.X; - Origin = Anchor.BottomLeft; - X = 0; - - InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; - - foreach (var b in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(b)); - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (timeOffset >= 0) - AddJudgement(new Judgement { Result = NestedHitObjects.Cast().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss }); - } - - protected override void AddNested(DrawableHitObject h) - { - ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; - bananaContainer.Add(h); - base.AddNested(h); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableBananaShower : DrawableCatchHitObject + { + private readonly Container bananaContainer; + + public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null) + : base(s) + { + RelativeSizeAxes = Axes.X; + Origin = Anchor.BottomLeft; + X = 0; + + InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; + + foreach (var b in s.NestedHitObjects.Cast()) + AddNested(getVisualRepresentation?.Invoke(b)); + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + AddJudgement(new Judgement { Result = NestedHitObjects.Cast().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss }); + } + + protected override void AddNested(DrawableHitObject h) + { + ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; + bananaContainer.Add(h); + base.AddNested(h); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 582946ff00..3dbda708e5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -1,93 +1,93 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using OpenTK; -using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public abstract class PalpableCatchHitObject : DrawableCatchHitObject - where TObject : CatchHitObject - { - public override bool CanBePlated => true; - - protected PalpableCatchHitObject(TObject hitObject) - : base(hitObject) - { - Scale = new Vector2(HitObject.Scale); - } - } - - public abstract class DrawableCatchHitObject : DrawableCatchHitObject - where TObject : CatchHitObject - { - public new TObject HitObject; - - protected DrawableCatchHitObject(TObject hitObject) - : base(hitObject) - { - HitObject = hitObject; - Anchor = Anchor.BottomLeft; - } - } - - public abstract class DrawableCatchHitObject : DrawableHitObject - { - public virtual bool CanBePlated => false; - - protected DrawableCatchHitObject(CatchHitObject hitObject) - : base(hitObject) - { - RelativePositionAxes = Axes.X; - X = hitObject.X; - } - - public Func CheckPosition; - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (CheckPosition == null) return; - - if (timeOffset >= 0) - AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; - } - - private const float preempt = 1000; - - protected override void UpdateState(ArmedState state) - { - using (BeginAbsoluteSequence(HitObject.StartTime - preempt)) - this.FadeIn(200); - - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - - using (BeginAbsoluteSequence(endTime, true)) - { - switch (state) - { - case ArmedState.Miss: - this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); - break; - case ArmedState.Hit: - this.FadeOut().Expire(); - break; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public abstract class PalpableCatchHitObject : DrawableCatchHitObject + where TObject : CatchHitObject + { + public override bool CanBePlated => true; + + protected PalpableCatchHitObject(TObject hitObject) + : base(hitObject) + { + Scale = new Vector2(HitObject.Scale); + } + } + + public abstract class DrawableCatchHitObject : DrawableCatchHitObject + where TObject : CatchHitObject + { + public new TObject HitObject; + + protected DrawableCatchHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + Anchor = Anchor.BottomLeft; + } + } + + public abstract class DrawableCatchHitObject : DrawableHitObject + { + public virtual bool CanBePlated => false; + + protected DrawableCatchHitObject(CatchHitObject hitObject) + : base(hitObject) + { + RelativePositionAxes = Axes.X; + X = hitObject.X; + } + + public Func CheckPosition; + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (CheckPosition == null) return; + + if (timeOffset >= 0) + AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + if (HitObject is IHasComboInformation combo) + AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + } + + private const float preempt = 1000; + + protected override void UpdateState(ArmedState state) + { + using (BeginAbsoluteSequence(HitObject.StartTime - preempt)) + this.FadeIn(200); + + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + + using (BeginAbsoluteSequence(endTime, true)) + { + switch (state) + { + case ArmedState.Miss: + this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); + break; + case ArmedState.Hit: + this.FadeOut().Expire(); + break; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index 719cf0a110..a19d67ebbe 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public class DrawableDroplet : PalpableCatchHitObject - { - private Pulp pulp; - - public DrawableDroplet(Droplet h) - : base(h) - { - Origin = Anchor.Centre; - Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4; - Masking = false; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = pulp = new Pulp - { - Size = Size - }; - } - - public override Color4 AccentColour - { - get { return base.AccentColour; } - set - { - base.AccentColour = value; - pulp.AccentColour = AccentColour; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableDroplet : PalpableCatchHitObject + { + private Pulp pulp; + + public DrawableDroplet(Droplet h) + : base(h) + { + Origin = Anchor.Centre; + Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4; + Masking = false; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = pulp = new Pulp + { + Size = Size + }; + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + pulp.AccentColour = AccentColour; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 03c2444d8c..41792b10a4 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -1,305 +1,305 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public class DrawableFruit : PalpableCatchHitObject - { - private Circle border; - - public DrawableFruit(Fruit h) - : base(h) - { - Origin = Anchor.Centre; - - Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS); - Masking = false; - - Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; - } - - [BackgroundDependencyLoader] - private void load() - { - // todo: this should come from the skin. - AccentColour = colourForRrepesentation(HitObject.VisualRepresentation); - - InternalChildren = new[] - { - createPulp(HitObject.VisualRepresentation), - border = new Circle - { - EdgeEffect = new EdgeEffectParameters - { - Hollow = !HitObject.HyperDash, - Type = EdgeEffectType.Glow, - Radius = 4, - Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f) - }, - Size = new Vector2(Height * 1.5f), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BorderColour = Color4.White, - BorderThickness = 4f, - Children = new Framework.Graphics.Drawable[] - { - new Box - { - AlwaysPresent = true, - Colour = AccentColour, - Alpha = 0, - RelativeSizeAxes = Axes.Both - } - } - }, - }; - - if (HitObject.HyperDash) - { - AddInternal(new Pulp - { - RelativePositionAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AccentColour = Color4.Red, - Blending = BlendingMode.Additive, - Alpha = 0.5f, - Scale = new Vector2(1.333f) - }); - } - } - - private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation) - { - const float large_pulp_3 = 13f; - const float distance_from_centre_3 = 0.23f; - - const float large_pulp_4 = large_pulp_3 * 0.925f; - const float distance_from_centre_4 = distance_from_centre_3 / 0.925f; - - const float small_pulp = large_pulp_3 / 2; - - Vector2 positionAt(float angle, float distance) => new Vector2( - distance * (float)Math.Sin(angle * Math.PI / 180), - distance * (float)Math.Cos(angle * Math.PI / 180)); - - switch (representation) - { - default: - return new Container(); - case FruitVisualRepresentation.Raspberry: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - AccentColour = AccentColour, - Size = new Vector2(small_pulp), - Y = 0.05f, - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_4), - Position = positionAt(0, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_4), - Position = positionAt(90, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_4), - Position = positionAt(180, distance_from_centre_4), - }, - new Pulp - { - Size = new Vector2(large_pulp_4), - AccentColour = AccentColour, - Position = positionAt(270, distance_from_centre_4), - }, - } - }; - case FruitVisualRepresentation.Pineapple: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - AccentColour = AccentColour, - Size = new Vector2(small_pulp), - Y = 0.1f, - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_4), - Position = positionAt(45, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_4), - Position = positionAt(135, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_4), - Position = positionAt(225, distance_from_centre_4), - }, - new Pulp - { - Size = new Vector2(large_pulp_4), - AccentColour = AccentColour, - Position = positionAt(315, distance_from_centre_4), - }, - } - }; - case FruitVisualRepresentation.Pear: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AccentColour = AccentColour, - Size = new Vector2(small_pulp), - Y = -0.1f, - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_3), - Position = positionAt(60, distance_from_centre_3), - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_3), - Position = positionAt(180, distance_from_centre_3), - }, - new Pulp - { - Size = new Vector2(large_pulp_3), - AccentColour = AccentColour, - Position = positionAt(300, distance_from_centre_3), - }, - } - }; - case FruitVisualRepresentation.Grape: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AccentColour = AccentColour, - Size = new Vector2(small_pulp), - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_3), - Position = positionAt(0, distance_from_centre_3), - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_3), - Position = positionAt(120, distance_from_centre_3), - }, - new Pulp - { - Size = new Vector2(large_pulp_3), - AccentColour = AccentColour, - Position = positionAt(240, distance_from_centre_3), - }, - } - }; - case FruitVisualRepresentation.Banana: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AccentColour = AccentColour, - Size = new Vector2(small_pulp), - Y = -0.15f - }, - new Pulp - { - AccentColour = AccentColour, - Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3), - }, - } - }; - } - } - - protected override void Update() - { - base.Update(); - - border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); - } - - private Color4 colourForRrepesentation(FruitVisualRepresentation representation) - { - switch (representation) - { - default: - case FruitVisualRepresentation.Pear: - return new Color4(17, 136, 170, 255); - case FruitVisualRepresentation.Grape: - return new Color4(204, 102, 0, 255); - case FruitVisualRepresentation.Raspberry: - return new Color4(121, 9, 13, 255); - case FruitVisualRepresentation.Pineapple: - return new Color4(102, 136, 0, 255); - case FruitVisualRepresentation.Banana: - switch (RNG.Next(0, 3)) - { - default: - return new Color4(255, 240, 0, 255); - case 1: - return new Color4(255, 192, 0, 255); - case 2: - return new Color4(214, 221, 28, 255); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableFruit : PalpableCatchHitObject + { + private Circle border; + + public DrawableFruit(Fruit h) + : base(h) + { + Origin = Anchor.Centre; + + Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS); + Masking = false; + + Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; + } + + [BackgroundDependencyLoader] + private void load() + { + // todo: this should come from the skin. + AccentColour = colourForRrepesentation(HitObject.VisualRepresentation); + + InternalChildren = new[] + { + createPulp(HitObject.VisualRepresentation), + border = new Circle + { + EdgeEffect = new EdgeEffectParameters + { + Hollow = !HitObject.HyperDash, + Type = EdgeEffectType.Glow, + Radius = 4, + Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f) + }, + Size = new Vector2(Height * 1.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BorderColour = Color4.White, + BorderThickness = 4f, + Children = new Framework.Graphics.Drawable[] + { + new Box + { + AlwaysPresent = true, + Colour = AccentColour, + Alpha = 0, + RelativeSizeAxes = Axes.Both + } + } + }, + }; + + if (HitObject.HyperDash) + { + AddInternal(new Pulp + { + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.Red, + Blending = BlendingMode.Additive, + Alpha = 0.5f, + Scale = new Vector2(1.333f) + }); + } + } + + private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation) + { + const float large_pulp_3 = 13f; + const float distance_from_centre_3 = 0.23f; + + const float large_pulp_4 = large_pulp_3 * 0.925f; + const float distance_from_centre_4 = distance_from_centre_3 / 0.925f; + + const float small_pulp = large_pulp_3 / 2; + + Vector2 positionAt(float angle, float distance) => new Vector2( + distance * (float)Math.Sin(angle * Math.PI / 180), + distance * (float)Math.Cos(angle * Math.PI / 180)); + + switch (representation) + { + default: + return new Container(); + case FruitVisualRepresentation.Raspberry: + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Framework.Graphics.Drawable[] + { + new Pulp + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + AccentColour = AccentColour, + Size = new Vector2(small_pulp), + Y = 0.05f, + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4), + Position = positionAt(0, distance_from_centre_4), + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4), + Position = positionAt(90, distance_from_centre_4), + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4), + Position = positionAt(180, distance_from_centre_4), + }, + new Pulp + { + Size = new Vector2(large_pulp_4), + AccentColour = AccentColour, + Position = positionAt(270, distance_from_centre_4), + }, + } + }; + case FruitVisualRepresentation.Pineapple: + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Framework.Graphics.Drawable[] + { + new Pulp + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + AccentColour = AccentColour, + Size = new Vector2(small_pulp), + Y = 0.1f, + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4), + Position = positionAt(45, distance_from_centre_4), + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4), + Position = positionAt(135, distance_from_centre_4), + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4), + Position = positionAt(225, distance_from_centre_4), + }, + new Pulp + { + Size = new Vector2(large_pulp_4), + AccentColour = AccentColour, + Position = positionAt(315, distance_from_centre_4), + }, + } + }; + case FruitVisualRepresentation.Pear: + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Framework.Graphics.Drawable[] + { + new Pulp + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AccentColour = AccentColour, + Size = new Vector2(small_pulp), + Y = -0.1f, + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_3), + Position = positionAt(60, distance_from_centre_3), + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_3), + Position = positionAt(180, distance_from_centre_3), + }, + new Pulp + { + Size = new Vector2(large_pulp_3), + AccentColour = AccentColour, + Position = positionAt(300, distance_from_centre_3), + }, + } + }; + case FruitVisualRepresentation.Grape: + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Framework.Graphics.Drawable[] + { + new Pulp + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AccentColour = AccentColour, + Size = new Vector2(small_pulp), + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_3), + Position = positionAt(0, distance_from_centre_3), + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_3), + Position = positionAt(120, distance_from_centre_3), + }, + new Pulp + { + Size = new Vector2(large_pulp_3), + AccentColour = AccentColour, + Position = positionAt(240, distance_from_centre_3), + }, + } + }; + case FruitVisualRepresentation.Banana: + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Framework.Graphics.Drawable[] + { + new Pulp + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AccentColour = AccentColour, + Size = new Vector2(small_pulp), + Y = -0.15f + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3), + }, + } + }; + } + } + + protected override void Update() + { + base.Update(); + + border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); + } + + private Color4 colourForRrepesentation(FruitVisualRepresentation representation) + { + switch (representation) + { + default: + case FruitVisualRepresentation.Pear: + return new Color4(17, 136, 170, 255); + case FruitVisualRepresentation.Grape: + return new Color4(204, 102, 0, 255); + case FruitVisualRepresentation.Raspberry: + return new Color4(121, 9, 13, 255); + case FruitVisualRepresentation.Pineapple: + return new Color4(102, 136, 0, 255); + case FruitVisualRepresentation.Banana: + switch (RNG.Next(0, 3)) + { + default: + return new Color4(255, 240, 0, 255); + case 1: + return new Color4(255, 192, 0, 255); + case 2: + return new Color4(214, 221, 28, 255); + } + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index b3532e2473..854b63edeb 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public class DrawableJuiceStream : DrawableCatchHitObject - { - private readonly Container dropletContainer; - - public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null) - : base(s) - { - RelativeSizeAxes = Axes.Both; - Origin = Anchor.BottomLeft; - X = 0; - - InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; - - foreach (var o in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(o)); - } - - protected override bool ProvidesJudgement => false; - - protected override void AddNested(DrawableHitObject h) - { - var catchObject = (DrawableCatchHitObject)h; - - catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false; - - dropletContainer.Add(h); - base.AddNested(h); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableJuiceStream : DrawableCatchHitObject + { + private readonly Container dropletContainer; + + public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null) + : base(s) + { + RelativeSizeAxes = Axes.Both; + Origin = Anchor.BottomLeft; + X = 0; + + InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; + + foreach (var o in s.NestedHitObjects.Cast()) + AddNested(getVisualRepresentation?.Invoke(o)); + } + + protected override bool ProvidesJudgement => false; + + protected override void AddNested(DrawableHitObject h) + { + var catchObject = (DrawableCatchHitObject)h; + + catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false; + + dropletContainer.Add(h); + base.AddNested(h); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index 80520ea846..d17a72a165 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -1,42 +1,42 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces -{ - public class Pulp : Circle, IHasAccentColour - { - public Pulp() - { - RelativePositionAxes = Axes.Both; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Blending = BlendingMode.Additive; - Colour = Color4.White.Opacity(0.9f); - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 8, - Colour = accentColour.Darken(0.2f).Opacity(0.75f) - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces +{ + public class Pulp : Circle, IHasAccentColour + { + public Pulp() + { + RelativePositionAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Blending = BlendingMode.Additive; + Colour = Color4.White.Opacity(0.9f); + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 8, + Colour = accentColour.Darken(0.2f).Opacity(0.75f) + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs index 4f9636e110..f91a70c506 100644 --- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Catch.Objects -{ - public class Droplet : CatchHitObject - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class Droplet : CatchHitObject + { + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs index c738984667..fcbb339ffd 100644 --- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Catch.Objects -{ - public class Fruit : CatchHitObject - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class Fruit : CatchHitObject + { + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 29ad3c3956..ae799875a9 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -1,157 +1,157 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Catch.Objects -{ - public class JuiceStream : CatchHitObject, IHasCurve - { - /// - /// Positional distance that results in a duration of one second, before any speed adjustments. - /// - private const float base_scoring_distance = 100; - - public int RepeatCount { get; set; } - - public double Velocity; - public double TickDistance; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = scoringDistance / difficulty.SliderTickRate; - } - - protected override void CreateNestedHitObjects() - { - base.CreateNestedHitObjects(); - - createTicks(); - } - - private void createTicks() - { - if (TickDistance == 0) - return; - - var length = Curve.Distance; - var tickDistance = Math.Min(TickDistance, length); - var spanDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; - - AddNested(new Fruit - { - Samples = Samples, - StartTime = StartTime, - X = X - }); - - double lastDropletTime = StartTime; - - for (int span = 0; span < this.SpanCount(); span++) - { - var spanStartTime = StartTime + span * spanDuration; - var reversed = span % 2 == 1; - - for (double d = 0; d <= length; d += tickDistance) - { - var timeProgress = d / length; - var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - - double time = spanStartTime + timeProgress * spanDuration; - - double tinyTickInterval = time - lastDropletTime; - while (tinyTickInterval > 100) - tinyTickInterval /= 2; - - for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval) - { - double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration; - - AddNested(new TinyDroplet - { - StartTime = t, - X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = new List(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } - - if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd) - { - AddNested(new Droplet - { - StartTime = time, - X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = new List(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } - - lastDropletTime = time; - } - - AddNested(new Fruit - { - Samples = Samples, - StartTime = spanStartTime + spanDuration, - X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH - }); - } - } - - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; - - public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; - - public double Duration => EndTime - StartTime; - - public double Distance - { - get { return Curve.Distance; } - set { Curve.Distance = value; } - } - - public SliderCurve Curve { get; } = new SliderCurve(); - - public List ControlPoints - { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } - } - - public List> RepeatSamples { get; set; } = new List>(); - - public CurveType CurveType - { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class JuiceStream : CatchHitObject, IHasCurve + { + /// + /// Positional distance that results in a duration of one second, before any speed adjustments. + /// + private const float base_scoring_distance = 100; + + public int RepeatCount { get; set; } + + public double Velocity; + public double TickDistance; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + Velocity = scoringDistance / timingPoint.BeatLength; + TickDistance = scoringDistance / difficulty.SliderTickRate; + } + + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + + createTicks(); + } + + private void createTicks() + { + if (TickDistance == 0) + return; + + var length = Curve.Distance; + var tickDistance = Math.Min(TickDistance, length); + var spanDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + AddNested(new Fruit + { + Samples = Samples, + StartTime = StartTime, + X = X + }); + + double lastDropletTime = StartTime; + + for (int span = 0; span < this.SpanCount(); span++) + { + var spanStartTime = StartTime + span * spanDuration; + var reversed = span % 2 == 1; + + for (double d = 0; d <= length; d += tickDistance) + { + var timeProgress = d / length; + var distanceProgress = reversed ? 1 - timeProgress : timeProgress; + + double time = spanStartTime + timeProgress * spanDuration; + + double tinyTickInterval = time - lastDropletTime; + while (tinyTickInterval > 100) + tinyTickInterval /= 2; + + for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval) + { + double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration; + + AddNested(new TinyDroplet + { + StartTime = t, + X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + Samples = new List(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } + + if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd) + { + AddNested(new Droplet + { + StartTime = time, + X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + Samples = new List(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } + + lastDropletTime = time; + } + + AddNested(new Fruit + { + Samples = Samples, + StartTime = spanStartTime + spanDuration, + X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + }); + } + } + + public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + + public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; + + public double Duration => EndTime - StartTime; + + public double Distance + { + get { return Curve.Distance; } + set { Curve.Distance = value; } + } + + public SliderCurve Curve { get; } = new SliderCurve(); + + public List ControlPoints + { + get { return Curve.ControlPoints; } + set { Curve.ControlPoints = value; } + } + + public List> RepeatSamples { get; set; } = new List>(); + + public CurveType CurveType + { + get { return Curve.CurveType; } + set { Curve.CurveType = value; } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs index 23b8a7e02f..76cc8d9808 100644 --- a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Catch.Objects -{ - public class TinyDroplet : Droplet - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class TinyDroplet : Droplet + { + } +} diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs index fed1013ae1..045d0824c6 100644 --- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Runtime.CompilerServices; - -// We publish our internal attributes to other sub-projects of the framework. -// Note, that we omit visual tests as they are meant to test the framework -// behavior "in the wild". - -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")] -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")] +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Runtime.CompilerServices; + +// We publish our internal attributes to other sub-projects of the framework. +// Note, that we omit visual tests as they are meant to test the framework +// behavior "in the wild". + +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")] diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 244ab2b508..936ab6a9d3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -1,121 +1,121 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Replays; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Catch.Replays -{ - internal class CatchAutoGenerator : AutoGenerator - { - public const double RELEASE_DELAY = 20; - - public CatchAutoGenerator(Beatmap beatmap) - : base(beatmap) - { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; - } - - protected Replay Replay; - - public override Replay Generate() - { - // todo: add support for HT DT - const double dash_speed = CatcherArea.Catcher.BASE_SPEED; - const double movement_speed = dash_speed / 2; - float lastPosition = 0.5f; - double lastTime = 0; - - // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled - Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition)); - - void moveToNext(CatchHitObject h) - { - float positionChange = Math.Abs(lastPosition - h.X); - double timeAvailable = h.StartTime - lastTime; - - //So we can either make it there without a dash or not. - double speedRequired = positionChange / timeAvailable; - - bool dashRequired = speedRequired > movement_speed && h.StartTime != 0; - - // todo: get correct catcher size, based on difficulty CS. - const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f; - - if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) - { - //we are already in the correct range. - lastTime = h.StartTime; - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition)); - return; - } - - if (h is BananaShower.Banana) - { - // auto bananas unrealistically warp to catch 100% combo. - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); - } - else if (h.HyperDash) - { - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); - } - else if (dashRequired) - { - //we do a movement in two parts - the dash part then the normal part... - double timeAtNormalSpeed = positionChange / movement_speed; - double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; - double timeAtDashSpeed = timeWeNeedToSave / 2; - - float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); - - //dash movement - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); - } - else - { - double timeBefore = positionChange / movement_speed; - - Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition)); - Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); - } - - lastTime = h.StartTime; - lastPosition = h.X; - } - - foreach (var obj in Beatmap.HitObjects) - { - switch (obj) - { - case Fruit _: - moveToNext(obj); - break; - } - - foreach (var nestedObj in obj.NestedHitObjects.Cast()) - { - switch (nestedObj) - { - case BananaShower.Banana _: - case TinyDroplet _: - case Droplet _: - case Fruit _: - moveToNext(nestedObj); - break; - } - } - } - - return Replay; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Catch.Replays +{ + internal class CatchAutoGenerator : AutoGenerator + { + public const double RELEASE_DELAY = 20; + + public CatchAutoGenerator(Beatmap beatmap) + : base(beatmap) + { + Replay = new Replay { User = new User { Username = @"Autoplay" } }; + } + + protected Replay Replay; + + public override Replay Generate() + { + // todo: add support for HT DT + const double dash_speed = CatcherArea.Catcher.BASE_SPEED; + const double movement_speed = dash_speed / 2; + float lastPosition = 0.5f; + double lastTime = 0; + + // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled + Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition)); + + void moveToNext(CatchHitObject h) + { + float positionChange = Math.Abs(lastPosition - h.X); + double timeAvailable = h.StartTime - lastTime; + + //So we can either make it there without a dash or not. + double speedRequired = positionChange / timeAvailable; + + bool dashRequired = speedRequired > movement_speed && h.StartTime != 0; + + // todo: get correct catcher size, based on difficulty CS. + const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f; + + if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) + { + //we are already in the correct range. + lastTime = h.StartTime; + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition)); + return; + } + + if (h is BananaShower.Banana) + { + // auto bananas unrealistically warp to catch 100% combo. + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + else if (h.HyperDash) + { + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + else if (dashRequired) + { + //we do a movement in two parts - the dash part then the normal part... + double timeAtNormalSpeed = positionChange / movement_speed; + double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; + double timeAtDashSpeed = timeWeNeedToSave / 2; + + float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); + + //dash movement + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + else + { + double timeBefore = positionChange / movement_speed; + + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + + lastTime = h.StartTime; + lastPosition = h.X; + } + + foreach (var obj in Beatmap.HitObjects) + { + switch (obj) + { + case Fruit _: + moveToNext(obj); + break; + } + + foreach (var nestedObj in obj.NestedHitObjects.Cast()) + { + switch (nestedObj) + { + case BananaShower.Banana _: + case TinyDroplet _: + case Droplet _: + case Fruit _: + moveToNext(nestedObj); + break; + } + } + } + + return Replay; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 9c9b06fcea..6a9d1bdbc7 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -1,60 +1,60 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Catch.Replays -{ - public class CatchFramedReplayInputHandler : FramedReplayInputHandler - { - public CatchFramedReplayInputHandler(Replay replay) - : base(replay) - { - } - - protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0; - - protected float? Position - { - get - { - if (!HasFrames) - return null; - - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); - } - } - - public override List GetPendingStates() - { - if (!Position.HasValue) return new List(); - - var actions = new List(); - - if (CurrentFrame.Dashing) - actions.Add(CatchAction.Dash); - - if (Position.Value > CurrentFrame.Position) - actions.Add(CatchAction.MoveRight); - else if (Position.Value < CurrentFrame.Position) - actions.Add(CatchAction.MoveLeft); - - return new List - { - new CatchReplayState - { - PressedActions = actions, - CatcherX = Position.Value - }, - }; - } - - public class CatchReplayState : ReplayState - { - public float? CatcherX { get; set; } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Catch.Replays +{ + public class CatchFramedReplayInputHandler : FramedReplayInputHandler + { + public CatchFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0; + + protected float? Position + { + get + { + if (!HasFrames) + return null; + + return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + } + } + + public override List GetPendingStates() + { + if (!Position.HasValue) return new List(); + + var actions = new List(); + + if (CurrentFrame.Dashing) + actions.Add(CatchAction.Dash); + + if (Position.Value > CurrentFrame.Position) + actions.Add(CatchAction.MoveRight); + else if (Position.Value < CurrentFrame.Position) + actions.Add(CatchAction.MoveLeft); + + return new List + { + new CatchReplayState + { + PressedActions = actions, + CatcherX = Position.Value + }, + }; + } + + public class CatchReplayState : ReplayState + { + public float? CatcherX { get; set; } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index b444b0d7ba..d63d1bd331 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Rulesets.Replays.Types; - -namespace osu.Game.Rulesets.Catch.Replays -{ - public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame - { - public float Position; - public bool Dashing; - - public CatchReplayFrame() - { - } - - public CatchReplayFrame(double time, float? position = null, bool dashing = false) - : base(time) - { - Position = position ?? -1; - Dashing = dashing; - } - - public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) - { - Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH; - Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Rulesets.Replays.Types; + +namespace osu.Game.Rulesets.Catch.Replays +{ + public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame + { + public float Position; + public bool Dashing; + + public CatchReplayFrame() + { + } + + public CatchReplayFrame(double time, float? position = null, bool dashing = false) + : base(time) + { + Position = position ?? -1; + Dashing = dashing; + } + + public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) + { + Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH; + Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 0a8a53c6f1..ce1aee5c34 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -1,44 +1,44 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Judgements; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Catch.Scoring -{ - public class CatchScoreProcessor : ScoreProcessor - { - public CatchScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) - { - } - - protected override void SimulateAutoplay(Beatmap beatmap) - { - foreach (var obj in beatmap.HitObjects) - { - switch (obj) - { - case JuiceStream stream: - foreach (var _ in stream.NestedHitObjects.Cast()) - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - break; - case BananaShower shower: - foreach (var _ in shower.NestedHitObjects.Cast()) - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - break; - case Fruit _: - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - break; - } - } - - base.SimulateAutoplay(beatmap); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Judgements; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch.Scoring +{ + public class CatchScoreProcessor : ScoreProcessor + { + public CatchScoreProcessor(RulesetContainer rulesetContainer) + : base(rulesetContainer) + { + } + + protected override void SimulateAutoplay(Beatmap beatmap) + { + foreach (var obj in beatmap.HitObjects) + { + switch (obj) + { + case JuiceStream stream: + foreach (var _ in stream.NestedHitObjects.Cast()) + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + break; + case BananaShower shower: + foreach (var _ in shower.NestedHitObjects.Cast()) + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + break; + case Fruit _: + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + break; + } + } + + base.SimulateAutoplay(beatmap); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 2b6a7c41f4..9eca8f6871 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -1,70 +1,70 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Catch.UI -{ - public class CatchPlayfield : ScrollingPlayfield - { - public const float BASE_WIDTH = 512; - - protected override Container Content => content; - private readonly Container content; - - private readonly CatcherArea catcherArea; - - public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) - : base(ScrollingDirection.Down, BASE_WIDTH) - { - Container explodingFruitContainer; - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - base.Content.Anchor = Anchor.BottomLeft; - base.Content.Origin = Anchor.BottomLeft; - - base.Content.AddRange(new Drawable[] - { - explodingFruitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - catcherArea = new CatcherArea(difficulty) - { - GetVisualRepresentation = getVisualRepresentation, - ExplodingFruitTarget = explodingFruitContainer, - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }); - } - - public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj); - - public override void Add(DrawableHitObject h) - { - h.OnJudgement += onJudgement; - - base.Add(h); - - var fruit = (DrawableCatchHitObject)h; - fruit.CheckPosition = CheckIfWeCanCatch; - } - - private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) => catcherArea.OnJudgement((DrawableCatchHitObject)judgedObject, judgement); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatchPlayfield : ScrollingPlayfield + { + public const float BASE_WIDTH = 512; + + protected override Container Content => content; + private readonly Container content; + + private readonly CatcherArea catcherArea; + + public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) + : base(ScrollingDirection.Down, BASE_WIDTH) + { + Container explodingFruitContainer; + + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + base.Content.Anchor = Anchor.BottomLeft; + base.Content.Origin = Anchor.BottomLeft; + + base.Content.AddRange(new Drawable[] + { + explodingFruitContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + catcherArea = new CatcherArea(difficulty) + { + GetVisualRepresentation = getVisualRepresentation, + ExplodingFruitTarget = explodingFruitContainer, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + }, + }); + } + + public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj); + + public override void Add(DrawableHitObject h) + { + h.OnJudgement += onJudgement; + + base.Add(h); + + var fruit = (DrawableCatchHitObject)h; + fruit.CheckPosition = CheckIfWeCanCatch; + } + + private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) => catcherArea.OnJudgement((DrawableCatchHitObject)judgedObject, judgement); + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 41dd7fdf4e..022a8a8b43 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -1,59 +1,59 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Input.Handlers; -using osu.Game.Rulesets.Catch.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; -using osu.Game.Rulesets.Catch.Replays; -using osu.Game.Rulesets.Catch.Scoring; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; - -namespace osu.Game.Rulesets.Catch.UI -{ - public class CatchRulesetContainer : ScrollingRulesetContainer - { - public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(ruleset, beatmap, isForCurrentRuleset) - { - } - - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); - - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); - - protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor(); - - protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); - - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); - - public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); - - protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h) - { - switch (h) - { - case Fruit fruit: - return new DrawableFruit(fruit); - case JuiceStream stream: - return new DrawableJuiceStream(stream, GetVisualRepresentation); - case BananaShower banana: - return new DrawableBananaShower(banana, GetVisualRepresentation); - case TinyDroplet tiny: - return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; - case Droplet droplet: - return new DrawableDroplet(droplet); - } - - return null; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Input.Handlers; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatchRulesetContainer : ScrollingRulesetContainer + { + public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); + + protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor(); + + protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); + + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); + + public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + + protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h) + { + switch (h) + { + case Fruit fruit: + return new DrawableFruit(fruit); + case JuiceStream stream: + return new DrawableJuiceStream(stream, GetVisualRepresentation); + case BananaShower banana: + return new DrawableBananaShower(banana, GetVisualRepresentation); + case TinyDroplet tiny: + return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; + case Droplet droplet: + return new DrawableDroplet(droplet); + } + + return null; + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index bf2f9db4a8..181536a91e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -1,416 +1,416 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; -using osu.Game.Rulesets.Catch.Replays; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Catch.UI -{ - public class CatcherArea : Container - { - public const float CATCHER_SIZE = 172; - - protected readonly Catcher MovableCatcher; - - public Func> GetVisualRepresentation; - - public Container ExplodingFruitTarget - { - set { MovableCatcher.ExplodingFruitTarget = value; } - } - - public CatcherArea(BeatmapDifficulty difficulty = null) - { - RelativeSizeAxes = Axes.X; - Height = CATCHER_SIZE; - Child = MovableCatcher = new Catcher(difficulty) - { - AdditiveTarget = this, - }; - } - - private DrawableCatchHitObject lastPlateableFruit; - - public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) - { - if (judgement.IsHit && fruit.CanBePlated) - { - var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); - - if (caughtFruit == null) return; - - caughtFruit.RelativePositionAxes = Axes.None; - caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); - - caughtFruit.Anchor = Anchor.TopCentre; - caughtFruit.Origin = Anchor.Centre; - caughtFruit.Scale *= 0.7f; - caughtFruit.LifetimeEnd = double.MaxValue; - - MovableCatcher.Add(caughtFruit); - - lastPlateableFruit = caughtFruit; - } - - if (fruit.HitObject.LastInCombo) - { - if (judgement.IsHit) - { - // this is required to make this run after the last caught fruit runs UpdateState at least once. - // TODO: find a better alternative - if (lastPlateableFruit.IsLoaded) - MovableCatcher.Explode(); - else - lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); }; - } - else - MovableCatcher.Drop(); - } - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState; - - if (state?.CatcherX != null) - MovableCatcher.X = state.CatcherX.Value; - } - - public bool OnReleased(CatchAction action) => false; - - public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj); - - public class Catcher : Container, IKeyBindingHandler - { - private Texture texture; - - private Container caughtFruit; - - public Container ExplodingFruitTarget; - - public Container AdditiveTarget; - - public Catcher(BeatmapDifficulty difficulty = null) - { - RelativePositionAxes = Axes.X; - X = 0.5f; - - Origin = Anchor.TopCentre; - Anchor = Anchor.TopLeft; - - Size = new Vector2(CATCHER_SIZE); - if (difficulty != null) - Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); - - Children = new Drawable[] - { - caughtFruit = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - }, - createCatcherSprite(), - }; - } - - private int currentDirection; - - private bool dashing; - - protected bool Dashing - { - get { return dashing; } - set - { - if (value == dashing) return; - - dashing = value; - - Trail |= dashing; - } - } - - private bool trail; - - /// - /// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met. - /// - protected bool Trail - { - get { return trail; } - set - { - if (value == trail) return; - - trail = value; - - if (Trail) - beginTrail(); - } - } - - private void beginTrail() - { - Trail &= dashing || HyperDashing; - Trail &= AdditiveTarget != null; - - if (!Trail) return; - - var additive = createCatcherSprite(); - - additive.Anchor = Anchor; - additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. - additive.Position = Position; - additive.Scale = Scale; - additive.Colour = HyperDashing ? Color4.Red : Color4.White; - additive.RelativePositionAxes = RelativePositionAxes; - additive.Blending = BlendingMode.Additive; - - AdditiveTarget.Add(additive); - - additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); - - Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); - } - - private Sprite createCatcherSprite() => new Sprite - { - Size = new Vector2(CATCHER_SIZE), - FillMode = FillMode.Fill, - Texture = texture, - OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly. - }; - - /// - /// Add a caught fruit to the catcher's stack. - /// - /// The fruit that was caught. - public void Add(DrawableHitObject fruit) - { - float ourRadius = fruit.DrawSize.X / 2 * fruit.Scale.X; - float theirRadius = 0; - - const float allowance = 6; - - while (caughtFruit.Any(f => - f.LifetimeEnd == double.MaxValue && - Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) - { - float diff = (ourRadius + theirRadius) / allowance; - fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff; - fruit.Y -= RNG.NextSingle() * diff; - } - - fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); - - caughtFruit.Add(fruit); - } - - /// - /// Let the catcher attempt to catch a fruit. - /// - /// The fruit to catch. - /// Whether the catch is possible. - public bool AttemptCatch(CatchHitObject fruit) - { - double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f; - - // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. - var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; - var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; - - var validCatch = - catchObjectPosition >= catcherPosition - halfCatcherWidth && - catchObjectPosition <= catcherPosition + halfCatcherWidth; - - if (validCatch && fruit.HyperDash) - { - HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED; - HyperDashDirection = fruit.HyperDashTarget.X - fruit.X; - } - else - HyperDashModifier = 1; - - return validCatch; - } - - /// - /// Whether we are hypderdashing or not. - /// - public bool HyperDashing => hyperDashModifier != 1; - - private double hyperDashModifier = 1; - - /// - /// The direction in which hyperdash is allowed. 0 allows both directions. - /// - public double HyperDashDirection; - - /// - /// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1. - /// - public double HyperDashModifier - { - get { return hyperDashModifier; } - set - { - if (value == hyperDashModifier) return; - hyperDashModifier = value; - - const float transition_length = 180; - - if (HyperDashing) - { - this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint); - this.FadeTo(0.2f, transition_length, Easing.OutQuint); - Trail = true; - } - else - { - HyperDashDirection = 0; - this.FadeColour(Color4.White, transition_length, Easing.OutQuint); - this.FadeTo(1, transition_length, Easing.OutQuint); - } - } - } - - public bool OnPressed(CatchAction action) - { - switch (action) - { - case CatchAction.MoveLeft: - currentDirection--; - return true; - case CatchAction.MoveRight: - currentDirection++; - return true; - case CatchAction.Dash: - Dashing = true; - return true; - } - - return false; - } - - public bool OnReleased(CatchAction action) - { - switch (action) - { - case CatchAction.MoveLeft: - currentDirection++; - return true; - case CatchAction.MoveRight: - currentDirection--; - return true; - case CatchAction.Dash: - Dashing = false; - return true; - } - - return false; - } - - /// - /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. - /// - public const double BASE_SPEED = 1.0 / 512; - - protected override void Update() - { - base.Update(); - - if (currentDirection == 0) return; - - var direction = Math.Sign(currentDirection); - - double dashModifier = Dashing ? 1 : 0.5; - - if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection))) - dashModifier = hyperDashModifier; - - Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); - X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1); - } - - /// - /// Drop any fruit off the plate. - /// - public void Drop() - { - var fruit = caughtFruit.ToArray(); - - foreach (var f in fruit) - { - if (ExplodingFruitTarget != null) - { - f.Anchor = Anchor.TopLeft; - f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); - - caughtFruit.Remove(f); - - ExplodingFruitTarget.Add(f); - } - - f.MoveToY(f.Y + 75, 750, Easing.InSine); - f.FadeOut(750); - f.Expire(); - } - } - - /// - /// Explode any fruit off the plate. - /// - public void Explode() - { - var fruit = caughtFruit.ToArray(); - - foreach (var f in fruit) - { - var originalX = f.X * Scale.X; - - if (ExplodingFruitTarget != null) - { - f.Anchor = Anchor.TopLeft; - f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); - - caughtFruit.Remove(f); - - ExplodingFruitTarget.Add(f); - } - - f.MoveToY(f.Y - 50, 250, Easing.OutSine) - .Then() - .MoveToY(f.Y + 50, 500, Easing.InSine); - - f.MoveToX(f.X + originalX * 6, 1000); - f.FadeOut(750); - - f.Expire(); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatcherArea : Container + { + public const float CATCHER_SIZE = 172; + + protected readonly Catcher MovableCatcher; + + public Func> GetVisualRepresentation; + + public Container ExplodingFruitTarget + { + set { MovableCatcher.ExplodingFruitTarget = value; } + } + + public CatcherArea(BeatmapDifficulty difficulty = null) + { + RelativeSizeAxes = Axes.X; + Height = CATCHER_SIZE; + Child = MovableCatcher = new Catcher(difficulty) + { + AdditiveTarget = this, + }; + } + + private DrawableCatchHitObject lastPlateableFruit; + + public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) + { + if (judgement.IsHit && fruit.CanBePlated) + { + var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); + + if (caughtFruit == null) return; + + caughtFruit.RelativePositionAxes = Axes.None; + caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); + + caughtFruit.Anchor = Anchor.TopCentre; + caughtFruit.Origin = Anchor.Centre; + caughtFruit.Scale *= 0.7f; + caughtFruit.LifetimeEnd = double.MaxValue; + + MovableCatcher.Add(caughtFruit); + + lastPlateableFruit = caughtFruit; + } + + if (fruit.HitObject.LastInCombo) + { + if (judgement.IsHit) + { + // this is required to make this run after the last caught fruit runs UpdateState at least once. + // TODO: find a better alternative + if (lastPlateableFruit.IsLoaded) + MovableCatcher.Explode(); + else + lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); }; + } + else + MovableCatcher.Drop(); + } + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState; + + if (state?.CatcherX != null) + MovableCatcher.X = state.CatcherX.Value; + } + + public bool OnReleased(CatchAction action) => false; + + public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj); + + public class Catcher : Container, IKeyBindingHandler + { + private Texture texture; + + private Container caughtFruit; + + public Container ExplodingFruitTarget; + + public Container AdditiveTarget; + + public Catcher(BeatmapDifficulty difficulty = null) + { + RelativePositionAxes = Axes.X; + X = 0.5f; + + Origin = Anchor.TopCentre; + Anchor = Anchor.TopLeft; + + Size = new Vector2(CATCHER_SIZE); + if (difficulty != null) + Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); + + Children = new Drawable[] + { + caughtFruit = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, + createCatcherSprite(), + }; + } + + private int currentDirection; + + private bool dashing; + + protected bool Dashing + { + get { return dashing; } + set + { + if (value == dashing) return; + + dashing = value; + + Trail |= dashing; + } + } + + private bool trail; + + /// + /// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met. + /// + protected bool Trail + { + get { return trail; } + set + { + if (value == trail) return; + + trail = value; + + if (Trail) + beginTrail(); + } + } + + private void beginTrail() + { + Trail &= dashing || HyperDashing; + Trail &= AdditiveTarget != null; + + if (!Trail) return; + + var additive = createCatcherSprite(); + + additive.Anchor = Anchor; + additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. + additive.Position = Position; + additive.Scale = Scale; + additive.Colour = HyperDashing ? Color4.Red : Color4.White; + additive.RelativePositionAxes = RelativePositionAxes; + additive.Blending = BlendingMode.Additive; + + AdditiveTarget.Add(additive); + + additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); + + Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); + } + + private Sprite createCatcherSprite() => new Sprite + { + Size = new Vector2(CATCHER_SIZE), + FillMode = FillMode.Fill, + Texture = texture, + OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly. + }; + + /// + /// Add a caught fruit to the catcher's stack. + /// + /// The fruit that was caught. + public void Add(DrawableHitObject fruit) + { + float ourRadius = fruit.DrawSize.X / 2 * fruit.Scale.X; + float theirRadius = 0; + + const float allowance = 6; + + while (caughtFruit.Any(f => + f.LifetimeEnd == double.MaxValue && + Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) + { + float diff = (ourRadius + theirRadius) / allowance; + fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff; + fruit.Y -= RNG.NextSingle() * diff; + } + + fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); + + caughtFruit.Add(fruit); + } + + /// + /// Let the catcher attempt to catch a fruit. + /// + /// The fruit to catch. + /// Whether the catch is possible. + public bool AttemptCatch(CatchHitObject fruit) + { + double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f; + + // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. + var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; + var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; + + var validCatch = + catchObjectPosition >= catcherPosition - halfCatcherWidth && + catchObjectPosition <= catcherPosition + halfCatcherWidth; + + if (validCatch && fruit.HyperDash) + { + HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED; + HyperDashDirection = fruit.HyperDashTarget.X - fruit.X; + } + else + HyperDashModifier = 1; + + return validCatch; + } + + /// + /// Whether we are hypderdashing or not. + /// + public bool HyperDashing => hyperDashModifier != 1; + + private double hyperDashModifier = 1; + + /// + /// The direction in which hyperdash is allowed. 0 allows both directions. + /// + public double HyperDashDirection; + + /// + /// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1. + /// + public double HyperDashModifier + { + get { return hyperDashModifier; } + set + { + if (value == hyperDashModifier) return; + hyperDashModifier = value; + + const float transition_length = 180; + + if (HyperDashing) + { + this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint); + this.FadeTo(0.2f, transition_length, Easing.OutQuint); + Trail = true; + } + else + { + HyperDashDirection = 0; + this.FadeColour(Color4.White, transition_length, Easing.OutQuint); + this.FadeTo(1, transition_length, Easing.OutQuint); + } + } + } + + public bool OnPressed(CatchAction action) + { + switch (action) + { + case CatchAction.MoveLeft: + currentDirection--; + return true; + case CatchAction.MoveRight: + currentDirection++; + return true; + case CatchAction.Dash: + Dashing = true; + return true; + } + + return false; + } + + public bool OnReleased(CatchAction action) + { + switch (action) + { + case CatchAction.MoveLeft: + currentDirection++; + return true; + case CatchAction.MoveRight: + currentDirection--; + return true; + case CatchAction.Dash: + Dashing = false; + return true; + } + + return false; + } + + /// + /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. + /// + public const double BASE_SPEED = 1.0 / 512; + + protected override void Update() + { + base.Update(); + + if (currentDirection == 0) return; + + var direction = Math.Sign(currentDirection); + + double dashModifier = Dashing ? 1 : 0.5; + + if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection))) + dashModifier = hyperDashModifier; + + Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); + X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1); + } + + /// + /// Drop any fruit off the plate. + /// + public void Drop() + { + var fruit = caughtFruit.ToArray(); + + foreach (var f in fruit) + { + if (ExplodingFruitTarget != null) + { + f.Anchor = Anchor.TopLeft; + f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); + + caughtFruit.Remove(f); + + ExplodingFruitTarget.Add(f); + } + + f.MoveToY(f.Y + 75, 750, Easing.InSine); + f.FadeOut(750); + f.Expire(); + } + } + + /// + /// Explode any fruit off the plate. + /// + public void Explode() + { + var fruit = caughtFruit.ToArray(); + + foreach (var f in fruit) + { + var originalX = f.X * Scale.X; + + if (ExplodingFruitTarget != null) + { + f.Anchor = Anchor.TopLeft; + f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); + + caughtFruit.Remove(f); + + ExplodingFruitTarget.Add(f); + } + + f.MoveToY(f.Y - 50, 250, Easing.OutSine) + .Then() + .MoveToY(f.Y + 50, 500, Easing.InSine); + + f.MoveToX(f.X + originalX * 6, 1000); + f.FadeOut(750); + + f.Expire(); + } + } + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 9d55ab643d..81c537e53c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -1,60 +1,60 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Rulesets.Mania.Tests -{ - public class ManiaBeatmapConversionTest : BeatmapConversionTest - { - protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - - private bool isForCurrentRuleset; - - [NonParallelizable] - [TestCase("basic", false)] - public void Test(string name, bool isForCurrentRuleset) - { - this.isForCurrentRuleset = isForCurrentRuleset; - base.Test(name); - } - - protected override IEnumerable CreateConvertValue(HitObject hitObject) - { - yield return new ConvertValue - { - StartTime = hitObject.StartTime, - EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, - Column = ((ManiaHitObject)hitObject).Column - }; - } - - protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap); - } - - public struct ConvertValue : IEquatable - { - /// - /// A sane value to account for osu!stable using ints everwhere. - /// - private const float conversion_lenience = 2; - - public double StartTime; - public double EndTime; - public int Column; - - public bool Equals(ConvertValue other) - => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) - && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) - && Column == other.Column; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class ManiaBeatmapConversionTest : BeatmapConversionTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + + private bool isForCurrentRuleset; + + [NonParallelizable] + [TestCase("basic", false)] + public void Test(string name, bool isForCurrentRuleset) + { + this.isForCurrentRuleset = isForCurrentRuleset; + base.Test(name); + } + + protected override IEnumerable CreateConvertValue(HitObject hitObject) + { + yield return new ConvertValue + { + StartTime = hitObject.StartTime, + EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, + Column = ((ManiaHitObject)hitObject).Column + }; + } + + protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap); + } + + public struct ConvertValue : IEquatable + { + /// + /// A sane value to account for osu!stable using ints everwhere. + /// + private const float conversion_lenience = 2; + + public double StartTime; + public double EndTime; + public int Column; + + public bool Equals(ConvertValue other) + => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) + && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) + && Column == other.Column; + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs index 2453d8281a..bab3a4db32 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs @@ -1,179 +1,179 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Replays; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Mania.Tests -{ - [TestFixture] - public class TestCaseAutoGeneration : OsuTestCase - { - [Test] - public void TestSingleNote() - { - // | | - // | - | - // | | - - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - - var generated = new ManiaAutoGenerator(beatmap).Generate(); - - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); - } - - [Test] - public void TestSingleHoldNote() - { - // | | - // | * | - // | * | - // | * | - // | | - - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - - var generated = new ManiaAutoGenerator(beatmap).Generate(); - - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); - } - - [Test] - public void TestSingleNoteChord() - { - // | | | - // | - | - | - // | | | - - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); - - var generated = new ManiaAutoGenerator(beatmap).Generate(); - - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); - } - - [Test] - public void TestHoldNoteChord() - { - // | | | - // | * | * | - // | * | * | - // | * | * | - // | | | - - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); - - var generated = new ManiaAutoGenerator(beatmap).Generate(); - - Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); - } - - [Test] - public void TestSingleNoteStair() - { - // | | | - // | | - | - // | - | | - // | | | - - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); - - var generated = new ManiaAutoGenerator(beatmap).Generate(); - - Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); - Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time"); - Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time"); - Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); - Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); - } - - [Test] - public void TestHoldNoteStair() - { - // | | | - // | | * | - // | * | * | - // | * | * | - // | * | | - // | | | - - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); - - var generated = new ManiaAutoGenerator(beatmap).Generate(); - - Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time"); - Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time"); - Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); - Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released"); - Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released"); - Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); - } - - [Test] - public void TestHoldNoteWithReleasePress() - { - // | | | - // | * | - | - // | * | | - // | * | | - // | | | - - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY }); - beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); - - var generated = new ManiaAutoGenerator(beatmap).Generate(); - - Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames"); - Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); - Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time"); - Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); - Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released"); - } - - private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action)); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestCaseAutoGeneration : OsuTestCase + { + [Test] + public void TestSingleNote() + { + // | | + // | - | + // | | + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + + var generated = new ManiaAutoGenerator(beatmap).Generate(); + + Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); + Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); + Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); + Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); + } + + [Test] + public void TestSingleHoldNote() + { + // | | + // | * | + // | * | + // | * | + // | | + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + + var generated = new ManiaAutoGenerator(beatmap).Generate(); + + Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); + Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); + Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); + } + + [Test] + public void TestSingleNoteChord() + { + // | | | + // | - | - | + // | | | + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); + + var generated = new ManiaAutoGenerator(beatmap).Generate(); + + Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); + Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); + Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); + Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); + } + + [Test] + public void TestHoldNoteChord() + { + // | | | + // | * | * | + // | * | * | + // | * | * | + // | | | + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); + + var generated = new ManiaAutoGenerator(beatmap).Generate(); + + Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); + Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); + Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); + } + + [Test] + public void TestSingleNoteStair() + { + // | | | + // | | - | + // | - | | + // | | | + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); + + var generated = new ManiaAutoGenerator(beatmap).Generate(); + + Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); + Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); + Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time"); + Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time"); + Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); + Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); + } + + [Test] + public void TestHoldNoteStair() + { + // | | | + // | | * | + // | * | * | + // | * | * | + // | * | | + // | | | + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); + + var generated = new ManiaAutoGenerator(beatmap).Generate(); + + Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); + Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time"); + Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time"); + Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); + Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released"); + Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); + } + + [Test] + public void TestHoldNoteWithReleasePress() + { + // | | | + // | * | - | + // | * | | + // | * | | + // | | | + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY }); + beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); + + var generated = new ManiaAutoGenerator(beatmap).Generate(); + + Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames"); + Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); + Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time"); + Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time"); + Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released"); + } + + private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action)); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs index fe8749e830..281c2789af 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs @@ -1,96 +1,96 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Tests.Visual; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Mania.Tests -{ - [TestFixture] - public class TestCaseManiaHitObjects : OsuTestCase - { - public TestCaseManiaHitObjects() - { - Add(new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - // Imagine that the containers containing the drawable notes are the "columns" - Children = new Drawable[] - { - new Container - { - Name = "Normal note column", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = 50, - Children = new[] - { - new Container - { - Name = "Timing section", - RelativeSizeAxes = Axes.Both, - RelativeChildSize = new Vector2(1, 10000), - Children = new[] - { - new DrawableNote(new Note(), ManiaAction.Key1) - { - Y = 5000, - LifetimeStart = double.MinValue, - LifetimeEnd = double.MaxValue, - AccentColour = Color4.Red - }, - new DrawableNote(new Note(), ManiaAction.Key1) - { - Y = 6000, - LifetimeStart = double.MinValue, - LifetimeEnd = double.MaxValue, - AccentColour = Color4.Red - } - } - } - } - }, - new Container - { - Name = "Hold note column", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = 50, - Children = new[] - { - new Container - { - Name = "Timing section", - RelativeSizeAxes = Axes.Both, - RelativeChildSize = new Vector2(1, 10000), - Children = new[] - { - new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1) - { - Y = 5000, - Height = 1000, - LifetimeStart = double.MinValue, - LifetimeEnd = double.MaxValue, - AccentColour = Color4.Red - } - } - } - } - } - } - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestCaseManiaHitObjects : OsuTestCase + { + public TestCaseManiaHitObjects() + { + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + // Imagine that the containers containing the drawable notes are the "columns" + Children = new Drawable[] + { + new Container + { + Name = "Normal note column", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 50, + Children = new[] + { + new Container + { + Name = "Timing section", + RelativeSizeAxes = Axes.Both, + RelativeChildSize = new Vector2(1, 10000), + Children = new[] + { + new DrawableNote(new Note(), ManiaAction.Key1) + { + Y = 5000, + LifetimeStart = double.MinValue, + LifetimeEnd = double.MaxValue, + AccentColour = Color4.Red + }, + new DrawableNote(new Note(), ManiaAction.Key1) + { + Y = 6000, + LifetimeStart = double.MinValue, + LifetimeEnd = double.MaxValue, + AccentColour = Color4.Red + } + } + } + } + }, + new Container + { + Name = "Hold note column", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 50, + Children = new[] + { + new Container + { + Name = "Timing section", + RelativeSizeAxes = Axes.Both, + RelativeChildSize = new Vector2(1, 10000), + Children = new[] + { + new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1) + { + Y = 5000, + Height = 1000, + LifetimeStart = double.MinValue, + LifetimeEnd = double.MaxValue, + AccentColour = Color4.Red + } + } + } + } + } + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs index 4793b1ce94..053f478027 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs @@ -1,193 +1,193 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Timing; -using osu.Game.Configuration; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Mania.Tests -{ - [TestFixture] - public class TestCaseManiaPlayfield : OsuTestCase - { - private const double start_time = 500; - private const double duration = 500; - - protected override double TimePerAction => 200; - - private RulesetInfo maniaRuleset; - - public TestCaseManiaPlayfield() - { - var rng = new Random(1337); - - AddStep("1 column", () => createPlayfield(1)); - AddStep("4 columns", () => createPlayfield(4)); - AddStep("5 columns", () => createPlayfield(5)); - AddStep("8 columns", () => createPlayfield(8)); - AddStep("4 + 4 columns", () => - { - var stages = new List - { - new StageDefinition { Columns = 4 }, - new StageDefinition { Columns = 4 }, - }; - createPlayfield(stages); - }); - - AddStep("2 + 4 + 2 columns", () => - { - var stages = new List - { - new StageDefinition { Columns = 2 }, - new StageDefinition { Columns = 4 }, - new StageDefinition { Columns = 2 }, - }; - createPlayfield(stages); - }); - - AddStep("1 + 8 + 1 columns", () => - { - var stages = new List - { - new StageDefinition { Columns = 1 }, - new StageDefinition { Columns = 8 }, - new StageDefinition { Columns = 1 }, - }; - createPlayfield(stages); - }); - - AddStep("Reversed", () => createPlayfield(4, true)); - - AddStep("Notes with input", () => createPlayfieldWithNotes()); - AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true)); - AddStep("Notes with gravity", () => createPlayfieldWithNotes()); - AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true)); - - AddStep("Hit explosion", () => - { - var playfield = createPlayfield(4); - - int col = rng.Next(0, 4); - - var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1) - { - AccentColour = playfield.Columns.ElementAt(col).AccentColour - }; - - playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); - playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); - }); - } - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets, SettingsStore settings) - { - maniaRuleset = rulesets.GetRuleset(3); - - dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4)); - } - - private ManiaPlayfield createPlayfield(int cols, bool inverted = false) - { - var stages = new List - { - new StageDefinition { Columns = cols }, - }; - - return createPlayfield(stages, inverted); - } - - private ManiaPlayfield createPlayfield(List stages, bool inverted = false) - { - Clear(); - - var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both }; - Add(inputManager); - - ManiaPlayfield playfield; - - inputManager.Add(playfield = new ManiaPlayfield(stages) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - - playfield.Inverted.Value = inverted; - - return playfield; - } - - private void createPlayfieldWithNotes(bool inverted = false) - { - Clear(); - - var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; - - var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both }; - Add(inputManager); - - ManiaPlayfield playfield; - var stages = new List - { - new StageDefinition { Columns = 4 }, - }; - - inputManager.Add(playfield = new ManiaPlayfield(stages) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Clock = new FramedClock(rateAdjustClock) - }); - - playfield.Inverted.Value = inverted; - - for (double t = start_time; t <= start_time + duration; t += 100) - { - playfield.Add(new DrawableNote(new Note - { - StartTime = t, - Column = 0 - }, ManiaAction.Key1)); - - playfield.Add(new DrawableNote(new Note - { - StartTime = t, - Column = 3 - }, ManiaAction.Key4)); - } - - playfield.Add(new DrawableHoldNote(new HoldNote - { - StartTime = start_time, - Duration = duration, - Column = 1 - }, ManiaAction.Key2)); - - playfield.Add(new DrawableHoldNote(new HoldNote - { - StartTime = start_time, - Duration = duration, - Column = 2 - }, ManiaAction.Key3)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Scoring; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestCaseManiaPlayfield : OsuTestCase + { + private const double start_time = 500; + private const double duration = 500; + + protected override double TimePerAction => 200; + + private RulesetInfo maniaRuleset; + + public TestCaseManiaPlayfield() + { + var rng = new Random(1337); + + AddStep("1 column", () => createPlayfield(1)); + AddStep("4 columns", () => createPlayfield(4)); + AddStep("5 columns", () => createPlayfield(5)); + AddStep("8 columns", () => createPlayfield(8)); + AddStep("4 + 4 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 4 }, + new StageDefinition { Columns = 4 }, + }; + createPlayfield(stages); + }); + + AddStep("2 + 4 + 2 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 2 }, + new StageDefinition { Columns = 4 }, + new StageDefinition { Columns = 2 }, + }; + createPlayfield(stages); + }); + + AddStep("1 + 8 + 1 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 1 }, + new StageDefinition { Columns = 8 }, + new StageDefinition { Columns = 1 }, + }; + createPlayfield(stages); + }); + + AddStep("Reversed", () => createPlayfield(4, true)); + + AddStep("Notes with input", () => createPlayfieldWithNotes()); + AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true)); + AddStep("Notes with gravity", () => createPlayfieldWithNotes()); + AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true)); + + AddStep("Hit explosion", () => + { + var playfield = createPlayfield(4); + + int col = rng.Next(0, 4); + + var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1) + { + AccentColour = playfield.Columns.ElementAt(col).AccentColour + }; + + playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); + playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); + }); + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets, SettingsStore settings) + { + maniaRuleset = rulesets.GetRuleset(3); + + dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4)); + } + + private ManiaPlayfield createPlayfield(int cols, bool inverted = false) + { + var stages = new List + { + new StageDefinition { Columns = cols }, + }; + + return createPlayfield(stages, inverted); + } + + private ManiaPlayfield createPlayfield(List stages, bool inverted = false) + { + Clear(); + + var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both }; + Add(inputManager); + + ManiaPlayfield playfield; + + inputManager.Add(playfield = new ManiaPlayfield(stages) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + playfield.Inverted.Value = inverted; + + return playfield; + } + + private void createPlayfieldWithNotes(bool inverted = false) + { + Clear(); + + var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; + + var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both }; + Add(inputManager); + + ManiaPlayfield playfield; + var stages = new List + { + new StageDefinition { Columns = 4 }, + }; + + inputManager.Add(playfield = new ManiaPlayfield(stages) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Clock = new FramedClock(rateAdjustClock) + }); + + playfield.Inverted.Value = inverted; + + for (double t = start_time; t <= start_time + duration; t += 100) + { + playfield.Add(new DrawableNote(new Note + { + StartTime = t, + Column = 0 + }, ManiaAction.Key1)); + + playfield.Add(new DrawableNote(new Note + { + StartTime = t, + Column = 3 + }, ManiaAction.Key4)); + } + + playfield.Add(new DrawableHoldNote(new HoldNote + { + StartTime = start_time, + Duration = duration, + Column = 1 + }, ManiaAction.Key2)); + + playfield.Add(new DrawableHoldNote(new HoldNote + { + StartTime = start_time, + Duration = duration, + Column = 2 + }, ManiaAction.Key3)); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Mania.Tests/TestCasePerformancePoints.cs index 3c776a2f4c..c15a6dd688 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCasePerformancePoints.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCasePerformancePoints.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Rulesets.Mania.Tests -{ - [TestFixture] - public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints - { - public TestCasePerformancePoints() - : base(new ManiaRuleset()) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints + { + public TestCasePerformancePoints() + : base(new ManiaRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 187d5e47b9..02040fd23f 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -1,10 +1,10 @@ - - - - WinExe - netcoreapp2.0;net461 - - - - + + + + WinExe + netcoreapp2.0;net461 + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index 0a248658a8..6af3719f83 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; - -namespace osu.Game.Rulesets.Mania.Beatmaps -{ - public class ManiaBeatmap : Beatmap - { - /// - /// The definitions for each stage in a . - /// - public List Stages = new List(); - - /// - /// Total number of columns represented by all stages in this . - /// - public int TotalColumns => Stages.Sum(g => g.Columns); - - /// - /// Creates a new . - /// - /// The initial stages. - public ManiaBeatmap(StageDefinition defaultStage) - { - Stages.Add(defaultStage); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Beatmaps +{ + public class ManiaBeatmap : Beatmap + { + /// + /// The definitions for each stage in a . + /// + public List Stages = new List(); + + /// + /// Total number of columns represented by all stages in this . + /// + public int TotalColumns => Stages.Sum(g => g.Columns); + + /// + /// Creates a new . + /// + /// The initial stages. + public ManiaBeatmap(StageDefinition defaultStage) + { + Stages.Add(defaultStage); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 4734e40803..60b92cb7b3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -1,225 +1,225 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mania.Objects; -using System; -using System.Linq; -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Mania.Beatmaps.Patterns; -using osu.Game.Rulesets.Mania.MathUtils; -using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; -using OpenTK; -using osu.Game.Audio; - -namespace osu.Game.Rulesets.Mania.Beatmaps -{ - public class ManiaBeatmapConverter : BeatmapConverter - { - /// - /// Maximum number of previous notes to consider for density calculation. - /// - private const int max_notes_for_density = 7; - - protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; - - public int TargetColumns; - public readonly bool IsForCurrentRuleset; - - private Pattern lastPattern = new Pattern(); - private FastRandom random; - - private ManiaBeatmap beatmap; - - public ManiaBeatmapConverter(bool isForCurrentRuleset, Beatmap original) - { - IsForCurrentRuleset = isForCurrentRuleset; - - var roundedCircleSize = Math.Round(original.BeatmapInfo.BaseDifficulty.CircleSize); - var roundedOverallDifficulty = Math.Round(original.BeatmapInfo.BaseDifficulty.OverallDifficulty); - - if (isForCurrentRuleset) - TargetColumns = (int)Math.Max(1, roundedCircleSize); - else - { - float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count; - if (percentSliderOrSpinner < 0.2) - TargetColumns = 7; - else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) - TargetColumns = roundedOverallDifficulty > 5 ? 7 : 6; - else if (percentSliderOrSpinner > 0.6) - TargetColumns = roundedOverallDifficulty > 4 ? 5 : 4; - else - TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); - } - } - - protected override Beatmap ConvertBeatmap(Beatmap original) - { - BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; - - int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); - random = new FastRandom(seed); - - return base.ConvertBeatmap(original); - } - - protected override Beatmap CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); - - protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) - { - var maniaOriginal = original as ManiaHitObject; - if (maniaOriginal != null) - { - yield return maniaOriginal; - yield break; - } - - var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap); - - if (objects == null) - yield break; - - foreach (ManiaHitObject obj in objects) - yield return obj; - } - - private readonly List prevNoteTimes = new List(max_notes_for_density); - private double density = int.MaxValue; - private void computeDensity(double newNoteTime) - { - if (prevNoteTimes.Count == max_notes_for_density) - prevNoteTimes.RemoveAt(0); - prevNoteTimes.Add(newNoteTime); - - density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count; - } - - private double lastTime; - private Vector2 lastPosition; - private PatternType lastStair; - private void recordNote(double time, Vector2 position) - { - lastTime = time; - lastPosition = position; - } - - /// - /// Method that generates hit objects for osu!mania specific beatmaps. - /// - /// The original hit object. - /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. - /// The hit objects generated. - private IEnumerable generateSpecific(HitObject original, Beatmap originalBeatmap) - { - var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); - - Pattern newPattern = generator.Generate(); - lastPattern = newPattern; - - return newPattern.HitObjects; - } - - /// - /// Method that generates hit objects for non-osu!mania beatmaps. - /// - /// The original hit object. - /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. - /// The hit objects generated. - private IEnumerable generateConverted(HitObject original, Beatmap originalBeatmap) - { - var endTimeData = original as IHasEndTime; - var distanceData = original as IHasDistance; - var positionData = original as IHasPosition; - - Patterns.PatternGenerator conversion = null; - - if (distanceData != null) - conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); - else if (endTimeData != null) - conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap); - else if (positionData != null) - { - computeDensity(original.StartTime); - - conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); - - recordNote(original.StartTime, positionData.Position); - } - - if (conversion == null) - return null; - - Pattern newPattern = conversion.Generate(); - - lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; - lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; - - return newPattern.HitObjects; - } - - /// - /// A pattern generator for osu!mania-specific beatmaps. - /// - private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator - { - public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) - : base(random, hitObject, beatmap, previousPattern, originalBeatmap) - { - } - - public override Pattern Generate() - { - var endTimeData = HitObject as IHasEndTime; - var positionData = HitObject as IHasXPosition; - - int column = GetColumn(positionData?.X ?? 0); - - var pattern = new Pattern(); - - if (endTimeData != null) - { - pattern.Add(new HoldNote - { - StartTime = HitObject.StartTime, - Duration = endTimeData.Duration, - Column = column, - Head = { Samples = sampleInfoListAt(HitObject.StartTime) }, - Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) }, - }); - } - else if (positionData != null) - { - pattern.Add(new Note - { - StartTime = HitObject.StartTime, - Samples = HitObject.Samples, - Column = column - }); - } - - return pattern; - } - - /// - /// Retrieves the sample info list at a point in time. - /// - /// The time to retrieve the sample info list from. - /// - private List sampleInfoListAt(double time) - { - var curveData = HitObject as IHasCurve; - - if (curveData == null) - return HitObject.Samples; - - double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); - - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.RepeatSamples[index]; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mania.Objects; +using System; +using System.Linq; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Mania.Beatmaps.Patterns; +using osu.Game.Rulesets.Mania.MathUtils; +using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; +using OpenTK; +using osu.Game.Audio; + +namespace osu.Game.Rulesets.Mania.Beatmaps +{ + public class ManiaBeatmapConverter : BeatmapConverter + { + /// + /// Maximum number of previous notes to consider for density calculation. + /// + private const int max_notes_for_density = 7; + + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; + + public int TargetColumns; + public readonly bool IsForCurrentRuleset; + + private Pattern lastPattern = new Pattern(); + private FastRandom random; + + private ManiaBeatmap beatmap; + + public ManiaBeatmapConverter(bool isForCurrentRuleset, Beatmap original) + { + IsForCurrentRuleset = isForCurrentRuleset; + + var roundedCircleSize = Math.Round(original.BeatmapInfo.BaseDifficulty.CircleSize); + var roundedOverallDifficulty = Math.Round(original.BeatmapInfo.BaseDifficulty.OverallDifficulty); + + if (isForCurrentRuleset) + TargetColumns = (int)Math.Max(1, roundedCircleSize); + else + { + float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count; + if (percentSliderOrSpinner < 0.2) + TargetColumns = 7; + else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) + TargetColumns = roundedOverallDifficulty > 5 ? 7 : 6; + else if (percentSliderOrSpinner > 0.6) + TargetColumns = roundedOverallDifficulty > 4 ? 5 : 4; + else + TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); + } + } + + protected override Beatmap ConvertBeatmap(Beatmap original) + { + BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; + + int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); + random = new FastRandom(seed); + + return base.ConvertBeatmap(original); + } + + protected override Beatmap CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); + + protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) + { + var maniaOriginal = original as ManiaHitObject; + if (maniaOriginal != null) + { + yield return maniaOriginal; + yield break; + } + + var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap); + + if (objects == null) + yield break; + + foreach (ManiaHitObject obj in objects) + yield return obj; + } + + private readonly List prevNoteTimes = new List(max_notes_for_density); + private double density = int.MaxValue; + private void computeDensity(double newNoteTime) + { + if (prevNoteTimes.Count == max_notes_for_density) + prevNoteTimes.RemoveAt(0); + prevNoteTimes.Add(newNoteTime); + + density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count; + } + + private double lastTime; + private Vector2 lastPosition; + private PatternType lastStair; + private void recordNote(double time, Vector2 position) + { + lastTime = time; + lastPosition = position; + } + + /// + /// Method that generates hit objects for osu!mania specific beatmaps. + /// + /// The original hit object. + /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. + /// The hit objects generated. + private IEnumerable generateSpecific(HitObject original, Beatmap originalBeatmap) + { + var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); + + Pattern newPattern = generator.Generate(); + lastPattern = newPattern; + + return newPattern.HitObjects; + } + + /// + /// Method that generates hit objects for non-osu!mania beatmaps. + /// + /// The original hit object. + /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. + /// The hit objects generated. + private IEnumerable generateConverted(HitObject original, Beatmap originalBeatmap) + { + var endTimeData = original as IHasEndTime; + var distanceData = original as IHasDistance; + var positionData = original as IHasPosition; + + Patterns.PatternGenerator conversion = null; + + if (distanceData != null) + conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); + else if (endTimeData != null) + conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap); + else if (positionData != null) + { + computeDensity(original.StartTime); + + conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); + + recordNote(original.StartTime, positionData.Position); + } + + if (conversion == null) + return null; + + Pattern newPattern = conversion.Generate(); + + lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; + lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; + + return newPattern.HitObjects; + } + + /// + /// A pattern generator for osu!mania-specific beatmaps. + /// + private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator + { + public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) + { + } + + public override Pattern Generate() + { + var endTimeData = HitObject as IHasEndTime; + var positionData = HitObject as IHasXPosition; + + int column = GetColumn(positionData?.X ?? 0); + + var pattern = new Pattern(); + + if (endTimeData != null) + { + pattern.Add(new HoldNote + { + StartTime = HitObject.StartTime, + Duration = endTimeData.Duration, + Column = column, + Head = { Samples = sampleInfoListAt(HitObject.StartTime) }, + Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) }, + }); + } + else if (positionData != null) + { + pattern.Add(new Note + { + StartTime = HitObject.StartTime, + Samples = HitObject.Samples, + Column = column + }); + } + + return pattern; + } + + /// + /// Retrieves the sample info list at a point in time. + /// + /// The time to retrieve the sample info list from. + /// + private List sampleInfoListAt(double time) + { + var curveData = HitObject as IHasCurve; + + if (curveData == null) + return HitObject.Samples; + + double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); + + int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + return curveData.RepeatSamples[index]; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 28cf119833..3b5c028bfd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -1,491 +1,491 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.MathUtils; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy -{ - /// - /// A pattern generator for IHasDistance hit objects. - /// - internal class DistanceObjectPatternGenerator : PatternGenerator - { - /// - /// Base osu! slider scoring distance. - /// - private const float osu_base_scoring_distance = 100; - - private readonly double endTime; - private readonly double segmentDuration; - private readonly int spanCount; - - private PatternType convertType; - - public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) - : base(random, hitObject, beatmap, previousPattern, originalBeatmap) - { - convertType = PatternType.None; - if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) - convertType = PatternType.LowProbability; - - var distanceData = hitObject as IHasDistance; - var repeatsData = hitObject as IHasRepeats; - - spanCount = repeatsData?.SpanCount() ?? 1; - - TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); - - // The true distance, accounting for any repeats - double distance = (distanceData?.Distance ?? 0) * spanCount; - // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; - // The duration of the osu! hit object - double osuDuration = distance / osuVelocity; - - endTime = hitObject.StartTime + osuDuration; - segmentDuration = (endTime - HitObject.StartTime) / spanCount; - } - - public override Pattern Generate() - { - if (spanCount > 1) - { - if (segmentDuration <= 90) - return generateRandomHoldNotes(HitObject.StartTime, 1); - - if (segmentDuration <= 120) - { - convertType |= PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, spanCount + 1); - } - - if (segmentDuration <= 160) - return generateStair(HitObject.StartTime); - - if (segmentDuration <= 200 && ConversionDifficulty > 3) - return generateRandomMultipleNotes(HitObject.StartTime); - - double duration = endTime - HitObject.StartTime; - if (duration >= 4000) - return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); - - if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart) - return generateTiledHoldNotes(HitObject.StartTime); - - return generateHoldAndNormalNotes(HitObject.StartTime); - } - - if (segmentDuration <= 110) - { - if (PreviousPattern.ColumnWithObjects < TotalColumns) - convertType |= PatternType.ForceNotStack; - else - convertType &= ~PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2); - } - - if (ConversionDifficulty > 6.5) - { - if ((convertType & PatternType.LowProbability) > 0) - return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); - return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); - } - - if (ConversionDifficulty > 4) - { - if ((convertType & PatternType.LowProbability) > 0) - return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); - return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); - } - - if (ConversionDifficulty > 2.5) - { - if ((convertType & PatternType.LowProbability) > 0) - return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); - return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); - } - - if ((convertType & PatternType.LowProbability) > 0) - return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); - return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); - } - - /// - /// Generates random hold notes that start at an span the same amount of rows. - /// - /// Start time of each hold note. - /// Number of hold notes. - /// The containing the hit objects. - private Pattern generateRandomHoldNotes(double startTime, int noteCount) - { - // - - - - - // ■ - ■ ■ - // □ - □ □ - // ■ - ■ ■ - - var pattern = new Pattern(); - - int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; - int nextColumn = Random.Next(RandomStart, TotalColumns); - for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) - { - while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column - nextColumn = Random.Next(RandomStart, TotalColumns); - addToPattern(pattern, nextColumn, startTime, endTime); - } - - // This is can't be combined with the above loop due to RNG - for (int i = 0; i < noteCount - usableColumns; i++) - { - while (pattern.ColumnHasObject(nextColumn)) - nextColumn = Random.Next(RandomStart, TotalColumns); - addToPattern(pattern, nextColumn, startTime, endTime); - } - - return pattern; - } - - /// - /// Generates random notes, with one note per row and no stacking. - /// - /// The start time. - /// The number of notes. - /// The containing the hit objects. - private Pattern generateRandomNotes(double startTime, int noteCount) - { - // - - - - - // x - - - - // - - x - - // - - - x - // x - - - - - var pattern = new Pattern(); - - int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) - { - while (PreviousPattern.ColumnHasObject(nextColumn)) - nextColumn = Random.Next(RandomStart, TotalColumns); - } - - int lastColumn = nextColumn; - for (int i = 0; i < noteCount; i++) - { - addToPattern(pattern, nextColumn, startTime, startTime); - while (nextColumn == lastColumn) - nextColumn = Random.Next(RandomStart, TotalColumns); - - lastColumn = nextColumn; - startTime += segmentDuration; - } - - return pattern; - } - - /// - /// Generates a stair of notes, with one note per row. - /// - /// The start time. - /// The containing the hit objects. - private Pattern generateStair(double startTime) - { - // - - - - - // x - - - - // - x - - - // - - x - - // - - - x - // - - x - - // - x - - - // x - - - - - var pattern = new Pattern(); - - int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - bool increasing = Random.NextDouble() > 0.5; - - for (int i = 0; i <= spanCount; i++) - { - addToPattern(pattern, column, startTime, startTime); - startTime += segmentDuration; - - // Check if we're at the borders of the stage, and invert the pattern if so - if (increasing) - { - if (column >= TotalColumns - 1) - { - increasing = false; - column--; - } - else - column++; - } - else - { - if (column <= RandomStart) - { - increasing = true; - column++; - } - else - column--; - } - } - - return pattern; - } - - /// - /// Generates random notes with 1-2 notes per row and no stacking. - /// - /// The start time. - /// The containing the hit objects. - private Pattern generateRandomMultipleNotes(double startTime) - { - // - - - - - // x - - - - // - x x - - // - - - x - // x - x - - - var pattern = new Pattern(); - - bool legacy = TotalColumns >= 4 && TotalColumns <= 8; - int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); - - int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - for (int i = 0; i <= spanCount; i++) - { - addToPattern(pattern, nextColumn, startTime, startTime); - - nextColumn += interval; - if (nextColumn >= TotalColumns - RandomStart) - nextColumn = nextColumn - TotalColumns - RandomStart + (legacy ? 1 : 0); - nextColumn += RandomStart; - - // If we're in 2K, let's not add many consecutive doubles - if (TotalColumns > 2) - addToPattern(pattern, nextColumn, startTime, startTime); - - nextColumn = Random.Next(RandomStart, TotalColumns); - startTime += segmentDuration; - } - - return pattern; - } - - /// - /// Generates random hold notes. The amount of hold notes generated is determined by probabilities. - /// - /// The hold note start time. - /// The probability required for 2 hold notes to be generated. - /// The probability required for 3 hold notes to be generated. - /// The probability required for 4 hold notes to be generated. - /// The containing the hit objects. - private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4) - { - // - - - - - // ■ - ■ ■ - // □ - □ □ - // ■ - ■ ■ - - switch (TotalColumns) - { - case 2: - p2 = 0; - p3 = 0; - p4 = 0; - break; - case 3: - p2 = Math.Min(p2, 0.1); - p3 = 0; - p4 = 0; - break; - case 4: - p2 = Math.Min(p2, 0.3); - p3 = Math.Min(p3, 0.04); - p4 = 0; - break; - case 5: - p2 = Math.Min(p2, 0.34); - p3 = Math.Min(p3, 0.1); - p4 = Math.Min(p4, 0.03); - break; - } - - bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH; - - bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0; - canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); - - if (canGenerateTwoNotes) - p2 = 1; - - return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4)); - } - - /// - /// Generates tiled hold notes. You can think of this as a stair of hold notes. - /// - /// The first hold note start time. - /// The containing the hit objects. - private Pattern generateTiledHoldNotes(double startTime) - { - // - - - - - // ■ ■ ■ ■ - // □ □ □ □ - // □ □ □ □ - // □ □ □ ■ - // □ □ ■ - - // □ ■ - - - // ■ - - - - - var pattern = new Pattern(); - - int columnRepeat = Math.Min(spanCount, TotalColumns); - - int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) - { - while (PreviousPattern.ColumnHasObject(nextColumn)) - nextColumn = Random.Next(RandomStart, TotalColumns); - } - - for (int i = 0; i < columnRepeat; i++) - { - while (pattern.ColumnHasObject(nextColumn)) - nextColumn = Random.Next(RandomStart, TotalColumns); - - addToPattern(pattern, nextColumn, startTime, endTime); - startTime += segmentDuration; - } - - return pattern; - } - - /// - /// Generates a hold note alongside normal notes. - /// - /// The start time of notes. - /// The containing the hit objects. - private Pattern generateHoldAndNormalNotes(double startTime) - { - // - - - - - // ■ x x - - // ■ - x x - // ■ x - x - // ■ - x x - - var pattern = new Pattern(); - - int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) - { - while (PreviousPattern.ColumnHasObject(holdColumn)) - holdColumn = Random.Next(RandomStart, TotalColumns); - } - - // Create the hold note - addToPattern(pattern, holdColumn, startTime, endTime); - - int nextColumn = Random.Next(RandomStart, TotalColumns); - int noteCount; - if (ConversionDifficulty > 6.5) - noteCount = GetRandomNoteCount(0.63, 0); - else if (ConversionDifficulty > 4) - noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0); - else if (ConversionDifficulty > 2.5) - noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0); - else - noteCount = 0; - noteCount = Math.Min(TotalColumns - 1, noteCount); - - bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); - - var rowPattern = new Pattern(); - for (int i = 0; i <= spanCount; i++) - { - if (!(ignoreHead && startTime == HitObject.StartTime)) - { - for (int j = 0; j < noteCount; j++) - { - while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn) - nextColumn = Random.Next(RandomStart, TotalColumns); - addToPattern(rowPattern, nextColumn, startTime, startTime); - } - } - - pattern.Add(rowPattern); - rowPattern.Clear(); - - startTime += segmentDuration; - } - - return pattern; - } - - /// - /// Retrieves the sample info list at a point in time. - /// - /// The time to retrieve the sample info list from. - /// - private List sampleInfoListAt(double time) - { - var curveData = HitObject as IHasCurve; - - if (curveData == null) - return HitObject.Samples; - - double segmentTime = (endTime - HitObject.StartTime) / spanCount; - - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.RepeatSamples[index]; - } - - /// - /// Constructs and adds a note to a pattern. - /// - /// The pattern to add to. - /// The column to add the note to. - /// The start time of the note. - /// The end time of the note (set to for a non-hold note). - private void addToPattern(Pattern pattern, int column, double startTime, double endTime) - { - ManiaHitObject newObject; - - if (startTime == endTime) - { - newObject = new Note - { - StartTime = startTime, - Samples = sampleInfoListAt(startTime), - Column = column - }; - } - else - { - var holdNote = new HoldNote - { - StartTime = startTime, - Column = column, - Duration = endTime - startTime, - Head = { Samples = sampleInfoListAt(startTime) }, - Tail = { Samples = sampleInfoListAt(endTime) } - }; - - newObject = holdNote; - } - - pattern.Add(newObject); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.MathUtils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + /// + /// A pattern generator for IHasDistance hit objects. + /// + internal class DistanceObjectPatternGenerator : PatternGenerator + { + /// + /// Base osu! slider scoring distance. + /// + private const float osu_base_scoring_distance = 100; + + private readonly double endTime; + private readonly double segmentDuration; + private readonly int spanCount; + + private PatternType convertType; + + public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) + { + convertType = PatternType.None; + if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) + convertType = PatternType.LowProbability; + + var distanceData = hitObject as IHasDistance; + var repeatsData = hitObject as IHasRepeats; + + spanCount = repeatsData?.SpanCount() ?? 1; + + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); + + // The true distance, accounting for any repeats + double distance = (distanceData?.Distance ?? 0) * spanCount; + // The velocity of the osu! hit object - calculated as the velocity of a slider + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; + // The duration of the osu! hit object + double osuDuration = distance / osuVelocity; + + endTime = hitObject.StartTime + osuDuration; + segmentDuration = (endTime - HitObject.StartTime) / spanCount; + } + + public override Pattern Generate() + { + if (spanCount > 1) + { + if (segmentDuration <= 90) + return generateRandomHoldNotes(HitObject.StartTime, 1); + + if (segmentDuration <= 120) + { + convertType |= PatternType.ForceNotStack; + return generateRandomNotes(HitObject.StartTime, spanCount + 1); + } + + if (segmentDuration <= 160) + return generateStair(HitObject.StartTime); + + if (segmentDuration <= 200 && ConversionDifficulty > 3) + return generateRandomMultipleNotes(HitObject.StartTime); + + double duration = endTime - HitObject.StartTime; + if (duration >= 4000) + return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); + + if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart) + return generateTiledHoldNotes(HitObject.StartTime); + + return generateHoldAndNormalNotes(HitObject.StartTime); + } + + if (segmentDuration <= 110) + { + if (PreviousPattern.ColumnWithObjects < TotalColumns) + convertType |= PatternType.ForceNotStack; + else + convertType &= ~PatternType.ForceNotStack; + return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2); + } + + if (ConversionDifficulty > 6.5) + { + if ((convertType & PatternType.LowProbability) > 0) + return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); + return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); + } + + if (ConversionDifficulty > 4) + { + if ((convertType & PatternType.LowProbability) > 0) + return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); + return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); + } + + if (ConversionDifficulty > 2.5) + { + if ((convertType & PatternType.LowProbability) > 0) + return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); + return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); + } + + if ((convertType & PatternType.LowProbability) > 0) + return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); + return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); + } + + /// + /// Generates random hold notes that start at an span the same amount of rows. + /// + /// Start time of each hold note. + /// Number of hold notes. + /// The containing the hit objects. + private Pattern generateRandomHoldNotes(double startTime, int noteCount) + { + // - - - - + // ■ - ■ ■ + // □ - □ □ + // ■ - ■ ■ + + var pattern = new Pattern(); + + int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; + int nextColumn = Random.Next(RandomStart, TotalColumns); + for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) + { + while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column + nextColumn = Random.Next(RandomStart, TotalColumns); + addToPattern(pattern, nextColumn, startTime, endTime); + } + + // This is can't be combined with the above loop due to RNG + for (int i = 0; i < noteCount - usableColumns; i++) + { + while (pattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, TotalColumns); + addToPattern(pattern, nextColumn, startTime, endTime); + } + + return pattern; + } + + /// + /// Generates random notes, with one note per row and no stacking. + /// + /// The start time. + /// The number of notes. + /// The containing the hit objects. + private Pattern generateRandomNotes(double startTime, int noteCount) + { + // - - - - + // x - - - + // - - x - + // - - - x + // x - - - + + var pattern = new Pattern(); + + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) + { + while (PreviousPattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, TotalColumns); + } + + int lastColumn = nextColumn; + for (int i = 0; i < noteCount; i++) + { + addToPattern(pattern, nextColumn, startTime, startTime); + while (nextColumn == lastColumn) + nextColumn = Random.Next(RandomStart, TotalColumns); + + lastColumn = nextColumn; + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Generates a stair of notes, with one note per row. + /// + /// The start time. + /// The containing the hit objects. + private Pattern generateStair(double startTime) + { + // - - - - + // x - - - + // - x - - + // - - x - + // - - - x + // - - x - + // - x - - + // x - - - + + var pattern = new Pattern(); + + int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + bool increasing = Random.NextDouble() > 0.5; + + for (int i = 0; i <= spanCount; i++) + { + addToPattern(pattern, column, startTime, startTime); + startTime += segmentDuration; + + // Check if we're at the borders of the stage, and invert the pattern if so + if (increasing) + { + if (column >= TotalColumns - 1) + { + increasing = false; + column--; + } + else + column++; + } + else + { + if (column <= RandomStart) + { + increasing = true; + column++; + } + else + column--; + } + } + + return pattern; + } + + /// + /// Generates random notes with 1-2 notes per row and no stacking. + /// + /// The start time. + /// The containing the hit objects. + private Pattern generateRandomMultipleNotes(double startTime) + { + // - - - - + // x - - - + // - x x - + // - - - x + // x - x - + + var pattern = new Pattern(); + + bool legacy = TotalColumns >= 4 && TotalColumns <= 8; + int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); + + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i <= spanCount; i++) + { + addToPattern(pattern, nextColumn, startTime, startTime); + + nextColumn += interval; + if (nextColumn >= TotalColumns - RandomStart) + nextColumn = nextColumn - TotalColumns - RandomStart + (legacy ? 1 : 0); + nextColumn += RandomStart; + + // If we're in 2K, let's not add many consecutive doubles + if (TotalColumns > 2) + addToPattern(pattern, nextColumn, startTime, startTime); + + nextColumn = Random.Next(RandomStart, TotalColumns); + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Generates random hold notes. The amount of hold notes generated is determined by probabilities. + /// + /// The hold note start time. + /// The probability required for 2 hold notes to be generated. + /// The probability required for 3 hold notes to be generated. + /// The probability required for 4 hold notes to be generated. + /// The containing the hit objects. + private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4) + { + // - - - - + // ■ - ■ ■ + // □ - □ □ + // ■ - ■ ■ + + switch (TotalColumns) + { + case 2: + p2 = 0; + p3 = 0; + p4 = 0; + break; + case 3: + p2 = Math.Min(p2, 0.1); + p3 = 0; + p4 = 0; + break; + case 4: + p2 = Math.Min(p2, 0.3); + p3 = Math.Min(p3, 0.04); + p4 = 0; + break; + case 5: + p2 = Math.Min(p2, 0.34); + p3 = Math.Min(p3, 0.1); + p4 = Math.Min(p4, 0.03); + break; + } + + bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH; + + bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0; + canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); + + if (canGenerateTwoNotes) + p2 = 1; + + return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4)); + } + + /// + /// Generates tiled hold notes. You can think of this as a stair of hold notes. + /// + /// The first hold note start time. + /// The containing the hit objects. + private Pattern generateTiledHoldNotes(double startTime) + { + // - - - - + // ■ ■ ■ ■ + // □ □ □ □ + // □ □ □ □ + // □ □ □ ■ + // □ □ ■ - + // □ ■ - - + // ■ - - - + + var pattern = new Pattern(); + + int columnRepeat = Math.Min(spanCount, TotalColumns); + + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) + { + while (PreviousPattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, TotalColumns); + } + + for (int i = 0; i < columnRepeat; i++) + { + while (pattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, TotalColumns); + + addToPattern(pattern, nextColumn, startTime, endTime); + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Generates a hold note alongside normal notes. + /// + /// The start time of notes. + /// The containing the hit objects. + private Pattern generateHoldAndNormalNotes(double startTime) + { + // - - - - + // ■ x x - + // ■ - x x + // ■ x - x + // ■ - x x + + var pattern = new Pattern(); + + int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) + { + while (PreviousPattern.ColumnHasObject(holdColumn)) + holdColumn = Random.Next(RandomStart, TotalColumns); + } + + // Create the hold note + addToPattern(pattern, holdColumn, startTime, endTime); + + int nextColumn = Random.Next(RandomStart, TotalColumns); + int noteCount; + if (ConversionDifficulty > 6.5) + noteCount = GetRandomNoteCount(0.63, 0); + else if (ConversionDifficulty > 4) + noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0); + else if (ConversionDifficulty > 2.5) + noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0); + else + noteCount = 0; + noteCount = Math.Min(TotalColumns - 1, noteCount); + + bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); + + var rowPattern = new Pattern(); + for (int i = 0; i <= spanCount; i++) + { + if (!(ignoreHead && startTime == HitObject.StartTime)) + { + for (int j = 0; j < noteCount; j++) + { + while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn) + nextColumn = Random.Next(RandomStart, TotalColumns); + addToPattern(rowPattern, nextColumn, startTime, startTime); + } + } + + pattern.Add(rowPattern); + rowPattern.Clear(); + + startTime += segmentDuration; + } + + return pattern; + } + + /// + /// Retrieves the sample info list at a point in time. + /// + /// The time to retrieve the sample info list from. + /// + private List sampleInfoListAt(double time) + { + var curveData = HitObject as IHasCurve; + + if (curveData == null) + return HitObject.Samples; + + double segmentTime = (endTime - HitObject.StartTime) / spanCount; + + int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + return curveData.RepeatSamples[index]; + } + + /// + /// Constructs and adds a note to a pattern. + /// + /// The pattern to add to. + /// The column to add the note to. + /// The start time of the note. + /// The end time of the note (set to for a non-hold note). + private void addToPattern(Pattern pattern, int column, double startTime, double endTime) + { + ManiaHitObject newObject; + + if (startTime == endTime) + { + newObject = new Note + { + StartTime = startTime, + Samples = sampleInfoListAt(startTime), + Column = column + }; + } + else + { + var holdNote = new HoldNote + { + StartTime = startTime, + Column = column, + Duration = endTime - startTime, + Head = { Samples = sampleInfoListAt(startTime) }, + Tail = { Samples = sampleInfoListAt(endTime) } + }; + + newObject = holdNote; + } + + pattern.Add(newObject); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index ffbabba75a..743e230cb2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -1,102 +1,102 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Rulesets.Mania.MathUtils; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using System.Linq; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; - -namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy -{ - internal class EndTimeObjectPatternGenerator : PatternGenerator - { - private readonly double endTime; - - public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap) - : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) - { - var endtimeData = HitObject as IHasEndTime; - - endTime = endtimeData?.EndTime ?? 0; - } - - public override Pattern Generate() - { - var pattern = new Pattern(); - - bool generateHold = endTime - HitObject.StartTime >= 100; - - if (TotalColumns == 8) - { - if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000) - addToPattern(pattern, 0, generateHold); - else - addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold); - } - else if (TotalColumns > 0) - addToPattern(pattern, getNextRandomColumn(0), generateHold); - - return pattern; - } - - /// - /// Picks a random column after a column. - /// - /// The starting column. - /// A random column after . - private int getNextRandomColumn(int start) - { - int nextColumn = Random.Next(start, TotalColumns); - - while (PreviousPattern.ColumnHasObject(nextColumn)) - nextColumn = Random.Next(start, TotalColumns); - - return nextColumn; - } - - /// - /// Constructs and adds a note to a pattern. - /// - /// The pattern to add to. - /// The column to add the note to. - /// Whether to add a hold note. - private void addToPattern(Pattern pattern, int column, bool holdNote) - { - ManiaHitObject newObject; - - if (holdNote) - { - var hold = new HoldNote - { - StartTime = HitObject.StartTime, - Column = column, - Duration = endTime - HitObject.StartTime - }; - - if (hold.Head.Samples == null) - hold.Head.Samples = new List(); - - hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); - - hold.Tail.Samples = HitObject.Samples; - - newObject = hold; - } - else - { - newObject = new Note - { - StartTime = HitObject.StartTime, - Samples = HitObject.Samples, - Column = column - }; - } - - pattern.Add(newObject); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.MathUtils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + internal class EndTimeObjectPatternGenerator : PatternGenerator + { + private readonly double endTime; + + public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) + { + var endtimeData = HitObject as IHasEndTime; + + endTime = endtimeData?.EndTime ?? 0; + } + + public override Pattern Generate() + { + var pattern = new Pattern(); + + bool generateHold = endTime - HitObject.StartTime >= 100; + + if (TotalColumns == 8) + { + if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000) + addToPattern(pattern, 0, generateHold); + else + addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold); + } + else if (TotalColumns > 0) + addToPattern(pattern, getNextRandomColumn(0), generateHold); + + return pattern; + } + + /// + /// Picks a random column after a column. + /// + /// The starting column. + /// A random column after . + private int getNextRandomColumn(int start) + { + int nextColumn = Random.Next(start, TotalColumns); + + while (PreviousPattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(start, TotalColumns); + + return nextColumn; + } + + /// + /// Constructs and adds a note to a pattern. + /// + /// The pattern to add to. + /// The column to add the note to. + /// Whether to add a hold note. + private void addToPattern(Pattern pattern, int column, bool holdNote) + { + ManiaHitObject newObject; + + if (holdNote) + { + var hold = new HoldNote + { + StartTime = HitObject.StartTime, + Column = column, + Duration = endTime - HitObject.StartTime + }; + + if (hold.Head.Samples == null) + hold.Head.Samples = new List(); + + hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); + + hold.Tail.Samples = HitObject.Samples; + + newObject = hold; + } + else + { + newObject = new Note + { + StartTime = HitObject.StartTime, + Samples = HitObject.Samples, + Column = column + }; + } + + pattern.Add(newObject); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index e126534c54..652c92dd78 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -1,401 +1,401 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using OpenTK; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.MathUtils; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy -{ - internal class HitObjectPatternGenerator : PatternGenerator - { - public PatternType StairType { get; private set; } - - private readonly PatternType convertType; - - public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap) - : base(random, hitObject, beatmap, previousPattern, originalBeatmap) - { - if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime)); - if (density < 0) throw new ArgumentOutOfRangeException(nameof(density)); - - StairType = lastStair; - - TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime); - - var positionData = hitObject as IHasPosition; - - float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length; - double timeSeparation = hitObject.StartTime - previousTime; - - if (timeSeparation <= 80) - { - // More than 187 BPM - convertType |= PatternType.ForceNotStack | PatternType.KeepSingle; - } - else if (timeSeparation <= 95) - { - // More than 157 BPM - convertType |= PatternType.ForceNotStack | PatternType.KeepSingle | lastStair; - } - else if (timeSeparation <= 105) - { - // More than 140 BPM - convertType |= PatternType.ForceNotStack | PatternType.LowProbability; - } - else if (timeSeparation <= 125) - { - // More than 120 BPM - convertType |= PatternType.ForceNotStack; - } - else if (timeSeparation <= 135 && positionSeparation < 20) - { - // More than 111 BPM stream - convertType |= PatternType.Cycle | PatternType.KeepSingle; - } - else if (timeSeparation <= 150 && positionSeparation < 20) - { - // More than 100 BPM stream - convertType |= PatternType.ForceStack | PatternType.LowProbability; - } - else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5) - { - // Low density stream - convertType |= PatternType.Reverse | PatternType.LowProbability; - } - else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode) - { - // High density - } - else - convertType |= PatternType.LowProbability; - } - - public override Pattern Generate() - { - int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; - - if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any()) - { - // Generate a new pattern by copying the last hit objects in reverse-column order - var pattern = new Pattern(); - - for (int i = RandomStart; i < TotalColumns; i++) - if (PreviousPattern.ColumnHasObject(i)) - addToPattern(pattern, RandomStart + TotalColumns - i - 1); - - return pattern; - } - - if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1 - // If we convert to 7K + 1, let's not overload the special key - && (TotalColumns != 8 || lastColumn != 0) - // Make sure the last column was not the centre column - && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) - { - // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object) - var pattern = new Pattern(); - - int column = RandomStart + TotalColumns - lastColumn - 1; - addToPattern(pattern, column); - - return pattern; - } - - if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any()) - { - // Generate a new pattern by placing on the already filled columns - var pattern = new Pattern(); - - for (int i = RandomStart; i < TotalColumns; i++) - if (PreviousPattern.ColumnHasObject(i)) - addToPattern(pattern, i); - - return pattern; - } - - if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1) - { - // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" - var pattern = new Pattern(); - - int targetColumn = lastColumn + 1; - if (targetColumn == TotalColumns) - { - targetColumn = RandomStart; - StairType = PatternType.ReverseStair; - } - - addToPattern(pattern, targetColumn); - return pattern; - } - - if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1) - { - // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" - var pattern = new Pattern(); - - int targetColumn = lastColumn - 1; - if (targetColumn == RandomStart - 1) - { - targetColumn = TotalColumns - 1; - StairType = PatternType.Stair; - } - - addToPattern(pattern, targetColumn); - return pattern; - } - - if ((convertType & PatternType.KeepSingle) > 0) - return generateRandomNotes(1); - - if ((convertType & PatternType.Mirror) > 0) - { - if (ConversionDifficulty > 6.5) - return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); - if (ConversionDifficulty > 4) - return generateRandomPatternWithMirrored(0.12, 0.17, 0); - return generateRandomPatternWithMirrored(0.12, 0, 0); - } - - if (ConversionDifficulty > 6.5) - { - if ((convertType & PatternType.LowProbability) > 0) - return generateRandomPattern(0.78, 0.42, 0, 0); - return generateRandomPattern(1, 0.62, 0, 0); - } - - if (ConversionDifficulty > 4) - { - if ((convertType & PatternType.LowProbability) > 0) - return generateRandomPattern(0.35, 0.08, 0, 0); - return generateRandomPattern(0.52, 0.15, 0, 0); - } - - if (ConversionDifficulty > 2) - { - if ((convertType & PatternType.LowProbability) > 0) - return generateRandomPattern(0.18, 0, 0, 0); - return generateRandomPattern(0.45, 0, 0, 0); - } - - return generateRandomPattern(0, 0, 0, 0); - } - - /// - /// Generates random notes. - /// - /// This will generate as many as it can up to , accounting for - /// any stacks if is forcing no stacks. - /// - /// - /// The amount of notes to generate. - /// The containing the hit objects. - private Pattern generateRandomNotes(int noteCount) - { - var pattern = new Pattern(); - - bool allowStacking = (convertType & PatternType.ForceNotStack) == 0; - - if (!allowStacking) - noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); - - int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - for (int i = 0; i < noteCount; i++) - { - while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking) - { - if ((convertType & PatternType.Gathered) > 0) - { - nextColumn++; - if (nextColumn == TotalColumns) - nextColumn = RandomStart; - } - else - nextColumn = Random.Next(RandomStart, TotalColumns); - } - - addToPattern(pattern, nextColumn); - } - - return pattern; - } - - /// - /// Whether this hit object can generate a note in the special column. - /// - private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH); - - /// - /// Generates a random pattern. - /// - /// Probability for 2 notes to be generated. - /// Probability for 3 notes to be generated. - /// Probability for 4 notes to be generated. - /// Probability for 5 notes to be generated. - /// The containing the hit objects. - private Pattern generateRandomPattern(double p2, double p3, double p4, double p5) - { - var pattern = new Pattern(); - - pattern.Add(generateRandomNotes(getRandomNoteCount(p2, p3, p4, p5))); - - if (RandomStart > 0 && hasSpecialColumn) - addToPattern(pattern, 0); - - return pattern; - } - - /// - /// Generates a random pattern which has both normal and mirrored notes. - /// - /// The probability for a note to be added to the centre column. - /// Probability for 2 notes to be generated. - /// Probability for 3 notes to be generated. - /// The containing the hit objects. - private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) - { - var pattern = new Pattern(); - - bool addToCentre; - int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre); - - int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; - int nextColumn = Random.Next(RandomStart, columnLimit); - for (int i = 0; i < noteCount; i++) - { - while (pattern.ColumnHasObject(nextColumn)) - nextColumn = Random.Next(RandomStart, columnLimit); - - // Add normal note - addToPattern(pattern, nextColumn); - // Add mirrored note - addToPattern(pattern, RandomStart + TotalColumns - nextColumn - 1); - } - - if (addToCentre) - addToPattern(pattern, TotalColumns / 2); - - if (RandomStart > 0 && hasSpecialColumn) - addToPattern(pattern, 0); - - return pattern; - } - - /// - /// Generates a count of notes to be generated from a list of probabilities. - /// - /// Probability for 2 notes to be generated. - /// Probability for 3 notes to be generated. - /// Probability for 4 notes to be generated. - /// Probability for 5 notes to be generated. - /// The amount of notes to be generated. - private int getRandomNoteCount(double p2, double p3, double p4, double p5) - { - switch (TotalColumns) - { - case 2: - p2 = 0; - p3 = 0; - p4 = 0; - p5 = 0; - break; - case 3: - p2 = Math.Min(p2, 0.1); - p3 = 0; - p4 = 0; - p5 = 0; - break; - case 4: - p2 = Math.Min(p2, 0.23); - p3 = Math.Min(p3, 0.04); - p4 = 0; - p5 = 0; - break; - case 5: - p3 = Math.Min(p3, 0.15); - p4 = Math.Min(p4, 0.03); - p5 = 0; - break; - } - - if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)) - p2 = 1; - - return GetRandomNoteCount(p2, p3, p4, p5); - } - - /// - /// Generates a count of notes to be generated from a list of probabilities. - /// - /// The probability for a note to be added to the centre column. - /// Probability for 2 notes to be generated. - /// Probability for 3 notes to be generated. - /// Whether to add a note to the centre column. - /// The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count. - private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre) - { - addToCentre = false; - - if ((convertType & PatternType.ForceNotStack) > 0) - return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3); - - switch (TotalColumns) - { - case 2: - centreProbability = 0; - p2 = 0; - p3 = 0; - break; - case 3: - centreProbability = Math.Min(centreProbability, 0.03); - p2 = 0; - p3 = 0; - break; - case 4: - centreProbability = 0; - p2 = Math.Min(p2 * 2, 0.2); - p3 = 0; - break; - case 5: - centreProbability = Math.Min(centreProbability, 0.03); - p3 = 0; - break; - case 6: - centreProbability = 0; - p2 = Math.Min(p2 * 2, 0.5); - p3 = Math.Min(p3 * 2, 0.15); - break; - } - - double centreVal = Random.NextDouble(); - int noteCount = GetRandomNoteCount(p2, p3); - - addToCentre = TotalColumns % 2 != 0 && noteCount != 3 && centreVal > 1 - centreProbability; - return noteCount; - } - - /// - /// Constructs and adds a note to a pattern. - /// - /// The pattern to add to. - /// The column to add the note to. - private void addToPattern(Pattern pattern, int column) - { - pattern.Add(new Note - { - StartTime = HitObject.StartTime, - Samples = HitObject.Samples, - Column = column - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.MathUtils; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + internal class HitObjectPatternGenerator : PatternGenerator + { + public PatternType StairType { get; private set; } + + private readonly PatternType convertType; + + public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap) + : base(random, hitObject, beatmap, previousPattern, originalBeatmap) + { + if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime)); + if (density < 0) throw new ArgumentOutOfRangeException(nameof(density)); + + StairType = lastStair; + + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); + EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime); + + var positionData = hitObject as IHasPosition; + + float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length; + double timeSeparation = hitObject.StartTime - previousTime; + + if (timeSeparation <= 80) + { + // More than 187 BPM + convertType |= PatternType.ForceNotStack | PatternType.KeepSingle; + } + else if (timeSeparation <= 95) + { + // More than 157 BPM + convertType |= PatternType.ForceNotStack | PatternType.KeepSingle | lastStair; + } + else if (timeSeparation <= 105) + { + // More than 140 BPM + convertType |= PatternType.ForceNotStack | PatternType.LowProbability; + } + else if (timeSeparation <= 125) + { + // More than 120 BPM + convertType |= PatternType.ForceNotStack; + } + else if (timeSeparation <= 135 && positionSeparation < 20) + { + // More than 111 BPM stream + convertType |= PatternType.Cycle | PatternType.KeepSingle; + } + else if (timeSeparation <= 150 && positionSeparation < 20) + { + // More than 100 BPM stream + convertType |= PatternType.ForceStack | PatternType.LowProbability; + } + else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5) + { + // Low density stream + convertType |= PatternType.Reverse | PatternType.LowProbability; + } + else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode) + { + // High density + } + else + convertType |= PatternType.LowProbability; + } + + public override Pattern Generate() + { + int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; + + if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any()) + { + // Generate a new pattern by copying the last hit objects in reverse-column order + var pattern = new Pattern(); + + for (int i = RandomStart; i < TotalColumns; i++) + if (PreviousPattern.ColumnHasObject(i)) + addToPattern(pattern, RandomStart + TotalColumns - i - 1); + + return pattern; + } + + if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1 + // If we convert to 7K + 1, let's not overload the special key + && (TotalColumns != 8 || lastColumn != 0) + // Make sure the last column was not the centre column + && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) + { + // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object) + var pattern = new Pattern(); + + int column = RandomStart + TotalColumns - lastColumn - 1; + addToPattern(pattern, column); + + return pattern; + } + + if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any()) + { + // Generate a new pattern by placing on the already filled columns + var pattern = new Pattern(); + + for (int i = RandomStart; i < TotalColumns; i++) + if (PreviousPattern.ColumnHasObject(i)) + addToPattern(pattern, i); + + return pattern; + } + + if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1) + { + // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" + var pattern = new Pattern(); + + int targetColumn = lastColumn + 1; + if (targetColumn == TotalColumns) + { + targetColumn = RandomStart; + StairType = PatternType.ReverseStair; + } + + addToPattern(pattern, targetColumn); + return pattern; + } + + if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1) + { + // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" + var pattern = new Pattern(); + + int targetColumn = lastColumn - 1; + if (targetColumn == RandomStart - 1) + { + targetColumn = TotalColumns - 1; + StairType = PatternType.Stair; + } + + addToPattern(pattern, targetColumn); + return pattern; + } + + if ((convertType & PatternType.KeepSingle) > 0) + return generateRandomNotes(1); + + if ((convertType & PatternType.Mirror) > 0) + { + if (ConversionDifficulty > 6.5) + return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); + if (ConversionDifficulty > 4) + return generateRandomPatternWithMirrored(0.12, 0.17, 0); + return generateRandomPatternWithMirrored(0.12, 0, 0); + } + + if (ConversionDifficulty > 6.5) + { + if ((convertType & PatternType.LowProbability) > 0) + return generateRandomPattern(0.78, 0.42, 0, 0); + return generateRandomPattern(1, 0.62, 0, 0); + } + + if (ConversionDifficulty > 4) + { + if ((convertType & PatternType.LowProbability) > 0) + return generateRandomPattern(0.35, 0.08, 0, 0); + return generateRandomPattern(0.52, 0.15, 0, 0); + } + + if (ConversionDifficulty > 2) + { + if ((convertType & PatternType.LowProbability) > 0) + return generateRandomPattern(0.18, 0, 0, 0); + return generateRandomPattern(0.45, 0, 0, 0); + } + + return generateRandomPattern(0, 0, 0, 0); + } + + /// + /// Generates random notes. + /// + /// This will generate as many as it can up to , accounting for + /// any stacks if is forcing no stacks. + /// + /// + /// The amount of notes to generate. + /// The containing the hit objects. + private Pattern generateRandomNotes(int noteCount) + { + var pattern = new Pattern(); + + bool allowStacking = (convertType & PatternType.ForceNotStack) == 0; + + if (!allowStacking) + noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); + + int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i < noteCount; i++) + { + while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking) + { + if ((convertType & PatternType.Gathered) > 0) + { + nextColumn++; + if (nextColumn == TotalColumns) + nextColumn = RandomStart; + } + else + nextColumn = Random.Next(RandomStart, TotalColumns); + } + + addToPattern(pattern, nextColumn); + } + + return pattern; + } + + /// + /// Whether this hit object can generate a note in the special column. + /// + private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH); + + /// + /// Generates a random pattern. + /// + /// Probability for 2 notes to be generated. + /// Probability for 3 notes to be generated. + /// Probability for 4 notes to be generated. + /// Probability for 5 notes to be generated. + /// The containing the hit objects. + private Pattern generateRandomPattern(double p2, double p3, double p4, double p5) + { + var pattern = new Pattern(); + + pattern.Add(generateRandomNotes(getRandomNoteCount(p2, p3, p4, p5))); + + if (RandomStart > 0 && hasSpecialColumn) + addToPattern(pattern, 0); + + return pattern; + } + + /// + /// Generates a random pattern which has both normal and mirrored notes. + /// + /// The probability for a note to be added to the centre column. + /// Probability for 2 notes to be generated. + /// Probability for 3 notes to be generated. + /// The containing the hit objects. + private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) + { + var pattern = new Pattern(); + + bool addToCentre; + int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre); + + int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; + int nextColumn = Random.Next(RandomStart, columnLimit); + for (int i = 0; i < noteCount; i++) + { + while (pattern.ColumnHasObject(nextColumn)) + nextColumn = Random.Next(RandomStart, columnLimit); + + // Add normal note + addToPattern(pattern, nextColumn); + // Add mirrored note + addToPattern(pattern, RandomStart + TotalColumns - nextColumn - 1); + } + + if (addToCentre) + addToPattern(pattern, TotalColumns / 2); + + if (RandomStart > 0 && hasSpecialColumn) + addToPattern(pattern, 0); + + return pattern; + } + + /// + /// Generates a count of notes to be generated from a list of probabilities. + /// + /// Probability for 2 notes to be generated. + /// Probability for 3 notes to be generated. + /// Probability for 4 notes to be generated. + /// Probability for 5 notes to be generated. + /// The amount of notes to be generated. + private int getRandomNoteCount(double p2, double p3, double p4, double p5) + { + switch (TotalColumns) + { + case 2: + p2 = 0; + p3 = 0; + p4 = 0; + p5 = 0; + break; + case 3: + p2 = Math.Min(p2, 0.1); + p3 = 0; + p4 = 0; + p5 = 0; + break; + case 4: + p2 = Math.Min(p2, 0.23); + p3 = Math.Min(p3, 0.04); + p4 = 0; + p5 = 0; + break; + case 5: + p3 = Math.Min(p3, 0.15); + p4 = Math.Min(p4, 0.03); + p5 = 0; + break; + } + + if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)) + p2 = 1; + + return GetRandomNoteCount(p2, p3, p4, p5); + } + + /// + /// Generates a count of notes to be generated from a list of probabilities. + /// + /// The probability for a note to be added to the centre column. + /// Probability for 2 notes to be generated. + /// Probability for 3 notes to be generated. + /// Whether to add a note to the centre column. + /// The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count. + private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre) + { + addToCentre = false; + + if ((convertType & PatternType.ForceNotStack) > 0) + return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3); + + switch (TotalColumns) + { + case 2: + centreProbability = 0; + p2 = 0; + p3 = 0; + break; + case 3: + centreProbability = Math.Min(centreProbability, 0.03); + p2 = 0; + p3 = 0; + break; + case 4: + centreProbability = 0; + p2 = Math.Min(p2 * 2, 0.2); + p3 = 0; + break; + case 5: + centreProbability = Math.Min(centreProbability, 0.03); + p3 = 0; + break; + case 6: + centreProbability = 0; + p2 = Math.Min(p2 * 2, 0.5); + p3 = Math.Min(p3 * 2, 0.15); + break; + } + + double centreVal = Random.NextDouble(); + int noteCount = GetRandomNoteCount(p2, p3); + + addToCentre = TotalColumns % 2 != 0 && noteCount != 3 && centreVal > 1 - centreProbability; + return noteCount; + } + + /// + /// Constructs and adds a note to a pattern. + /// + /// The pattern to add to. + /// The column to add the note to. + private void addToPattern(Pattern pattern, int column) + { + pattern.Add(new Note + { + StartTime = HitObject.StartTime, + Samples = HitObject.Samples, + Column = column + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 501950cdcd..02306846a3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -1,123 +1,123 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.MathUtils; -using osu.Game.Rulesets.Objects; -using OpenTK; - -namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy -{ - /// - /// A pattern generator for legacy hit objects. - /// - internal abstract class PatternGenerator : Patterns.PatternGenerator - { - /// - /// The column index at which to start generating random notes. - /// - protected readonly int RandomStart; - - /// - /// The random number generator to use. - /// - protected readonly FastRandom Random; - - /// - /// The beatmap which is being converted from. - /// - protected readonly Beatmap OriginalBeatmap; - - protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) - : base(hitObject, beatmap, previousPattern) - { - if (random == null) throw new ArgumentNullException(nameof(random)); - if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap)); - - Random = random; - OriginalBeatmap = originalBeatmap; - - RandomStart = TotalColumns == 8 ? 1 : 0; - } - - /// - /// Converts an x-position into a column. - /// - /// The x-position. - /// Whether to treat as 7K + 1. - /// The column. - protected int GetColumn(float position, bool allowSpecial = false) - { - if (allowSpecial && TotalColumns == 8) - { - const float local_x_divisor = 512f / 7; - return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; - } - - float localXDivisor = 512f / TotalColumns; - return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); - } - - /// - /// Generates a count of notes to be generated from probabilities. - /// - /// Probability for 2 notes to be generated. - /// Probability for 3 notes to be generated. - /// Probability for 4 notes to be generated. - /// Probability for 5 notes to be generated. - /// Probability for 6 notes to be generated. - /// The amount of notes to be generated. - protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0) - { - if (p2 < 0 || p2 > 1) throw new ArgumentOutOfRangeException(nameof(p2)); - if (p3 < 0 || p3 > 1) throw new ArgumentOutOfRangeException(nameof(p3)); - if (p4 < 0 || p4 > 1) throw new ArgumentOutOfRangeException(nameof(p4)); - if (p5 < 0 || p5 > 1) throw new ArgumentOutOfRangeException(nameof(p5)); - if (p6 < 0 || p6 > 1) throw new ArgumentOutOfRangeException(nameof(p6)); - - double val = Random.NextDouble(); - if (val >= 1 - p6) - return 6; - if (val >= 1 - p5) - return 5; - if (val >= 1 - p4) - return 4; - if (val >= 1 - p3) - return 3; - return val >= 1 - p2 ? 2 : 1; - } - - private double? conversionDifficulty; - /// - /// A difficulty factor used for various conversion methods from osu!stable. - /// - protected double ConversionDifficulty - { - get - { - if (conversionDifficulty != null) - return conversionDifficulty.Value; - - HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault(); - HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault(); - - double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); - drainTime -= OriginalBeatmap.TotalBreakTime; - - if (drainTime == 0) - drainTime = 10000000; - - // We need this in seconds - drainTime /= 1000; - - BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; - conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; - conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); - - return conversionDifficulty.Value; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.MathUtils; +using osu.Game.Rulesets.Objects; +using OpenTK; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + /// + /// A pattern generator for legacy hit objects. + /// + internal abstract class PatternGenerator : Patterns.PatternGenerator + { + /// + /// The column index at which to start generating random notes. + /// + protected readonly int RandomStart; + + /// + /// The random number generator to use. + /// + protected readonly FastRandom Random; + + /// + /// The beatmap which is being converted from. + /// + protected readonly Beatmap OriginalBeatmap; + + protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) + : base(hitObject, beatmap, previousPattern) + { + if (random == null) throw new ArgumentNullException(nameof(random)); + if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap)); + + Random = random; + OriginalBeatmap = originalBeatmap; + + RandomStart = TotalColumns == 8 ? 1 : 0; + } + + /// + /// Converts an x-position into a column. + /// + /// The x-position. + /// Whether to treat as 7K + 1. + /// The column. + protected int GetColumn(float position, bool allowSpecial = false) + { + if (allowSpecial && TotalColumns == 8) + { + const float local_x_divisor = 512f / 7; + return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; + } + + float localXDivisor = 512f / TotalColumns; + return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); + } + + /// + /// Generates a count of notes to be generated from probabilities. + /// + /// Probability for 2 notes to be generated. + /// Probability for 3 notes to be generated. + /// Probability for 4 notes to be generated. + /// Probability for 5 notes to be generated. + /// Probability for 6 notes to be generated. + /// The amount of notes to be generated. + protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0) + { + if (p2 < 0 || p2 > 1) throw new ArgumentOutOfRangeException(nameof(p2)); + if (p3 < 0 || p3 > 1) throw new ArgumentOutOfRangeException(nameof(p3)); + if (p4 < 0 || p4 > 1) throw new ArgumentOutOfRangeException(nameof(p4)); + if (p5 < 0 || p5 > 1) throw new ArgumentOutOfRangeException(nameof(p5)); + if (p6 < 0 || p6 > 1) throw new ArgumentOutOfRangeException(nameof(p6)); + + double val = Random.NextDouble(); + if (val >= 1 - p6) + return 6; + if (val >= 1 - p5) + return 5; + if (val >= 1 - p4) + return 4; + if (val >= 1 - p3) + return 3; + return val >= 1 - p2 ? 2 : 1; + } + + private double? conversionDifficulty; + /// + /// A difficulty factor used for various conversion methods from osu!stable. + /// + protected double ConversionDifficulty + { + get + { + if (conversionDifficulty != null) + return conversionDifficulty.Value; + + HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault(); + HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault(); + + double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); + drainTime -= OriginalBeatmap.TotalBreakTime; + + if (drainTime == 0) + drainTime = 10000000; + + // We need this in seconds + drainTime /= 1000; + + BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; + conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; + conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); + + return conversionDifficulty.Value; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs index 2ead37e136..2eba2d5843 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs @@ -1,65 +1,65 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy -{ - /// - /// The type of pattern to generate. Used for legacy patterns. - /// - [Flags] - internal enum PatternType - { - None = 0, - /// - /// Keep the same as last row. - /// - ForceStack = 1 << 0, - /// - /// Keep different from last row. - /// - ForceNotStack = 1 << 1, - /// - /// Keep as single note at its original position. - /// - KeepSingle = 1 << 2, - /// - /// Use a lower random value. - /// - LowProbability = 1 << 3, - /// - /// Reserved. - /// - Alternate = 1 << 4, - /// - /// Ignore the repeat count. - /// - ForceSigSlider = 1 << 5, - /// - /// Convert slider to circle. - /// - ForceNotSlider = 1 << 6, - /// - /// Notes gathered together. - /// - Gathered = 1 << 7, - Mirror = 1 << 8, - /// - /// Change 0 -> 6. - /// - Reverse = 1 << 9, - /// - /// 1 -> 5 -> 1 -> 5 like reverse. - /// - Cycle = 1 << 10, - /// - /// Next note will be at column + 1. - /// - Stair = 1 << 11, - /// - /// Next note will be at column - 1. - /// - ReverseStair = 1 << 12 - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + /// + /// The type of pattern to generate. Used for legacy patterns. + /// + [Flags] + internal enum PatternType + { + None = 0, + /// + /// Keep the same as last row. + /// + ForceStack = 1 << 0, + /// + /// Keep different from last row. + /// + ForceNotStack = 1 << 1, + /// + /// Keep as single note at its original position. + /// + KeepSingle = 1 << 2, + /// + /// Use a lower random value. + /// + LowProbability = 1 << 3, + /// + /// Reserved. + /// + Alternate = 1 << 4, + /// + /// Ignore the repeat count. + /// + ForceSigSlider = 1 << 5, + /// + /// Convert slider to circle. + /// + ForceNotSlider = 1 << 6, + /// + /// Notes gathered together. + /// + Gathered = 1 << 7, + Mirror = 1 << 8, + /// + /// Change 0 -> 6. + /// + Reverse = 1 << 9, + /// + /// 1 -> 5 -> 1 -> 5 like reverse. + /// + Cycle = 1 << 10, + /// + /// Next note will be at column + 1. + /// + Stair = 1 << 11, + /// + /// Next note will be at column - 1. + /// + ReverseStair = 1 << 12 + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs index d4b0eafa64..32d2d096b2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs @@ -1,57 +1,57 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Game.Rulesets.Mania.Objects; - -namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns -{ - /// - /// Creates a pattern containing hit objects. - /// - internal class Pattern - { - private readonly List hitObjects = new List(); - - /// - /// All the hit objects contained in this pattern. - /// - public IEnumerable HitObjects => hitObjects; - - /// - /// Check whether a column of this patterns contains a hit object. - /// - /// The column index. - /// Whether the column with index contains a hit object. - public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column); - - /// - /// Amount of columns taken up by hit objects in this pattern. - /// - public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count(); - - /// - /// Adds a hit object to this pattern. - /// - /// The hit object to add. - public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject); - - /// - /// Copies hit object from another pattern to this one. - /// - /// The other pattern. - public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects); - - /// - /// Clears this pattern, removing all hit objects. - /// - public void Clear() => hitObjects.Clear(); - - /// - /// Removes a hit object from this pattern. - /// - /// The hit object to remove. - public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns +{ + /// + /// Creates a pattern containing hit objects. + /// + internal class Pattern + { + private readonly List hitObjects = new List(); + + /// + /// All the hit objects contained in this pattern. + /// + public IEnumerable HitObjects => hitObjects; + + /// + /// Check whether a column of this patterns contains a hit object. + /// + /// The column index. + /// Whether the column with index contains a hit object. + public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column); + + /// + /// Amount of columns taken up by hit objects in this pattern. + /// + public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count(); + + /// + /// Adds a hit object to this pattern. + /// + /// The hit object to add. + public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject); + + /// + /// Copies hit object from another pattern to this one. + /// + /// The other pattern. + public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects); + + /// + /// Clears this pattern, removing all hit objects. + /// + public void Clear() => hitObjects.Clear(); + + /// + /// Removes a hit object from this pattern. + /// + /// The hit object to remove. + public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject); + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index 6862fd57c1..2bfcd52b6a 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -1,50 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns -{ - /// - /// Generator to create a pattern from a hit object. - /// - internal abstract class PatternGenerator - { - /// - /// The last pattern. - /// - protected readonly Pattern PreviousPattern; - - /// - /// The hit object to create the pattern for. - /// - protected readonly HitObject HitObject; - - /// - /// The beatmap which is a part of. - /// - protected readonly ManiaBeatmap Beatmap; - - protected readonly int TotalColumns; - - protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) - { - if (hitObject == null) throw new ArgumentNullException(nameof(hitObject)); - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); - if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern)); - - HitObject = hitObject; - Beatmap = beatmap; - PreviousPattern = previousPattern; - - TotalColumns = Beatmap.TotalColumns; - } - - /// - /// Generates the pattern for , filled with hit objects. - /// - /// The containing the hit objects. - public abstract Pattern Generate(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns +{ + /// + /// Generator to create a pattern from a hit object. + /// + internal abstract class PatternGenerator + { + /// + /// The last pattern. + /// + protected readonly Pattern PreviousPattern; + + /// + /// The hit object to create the pattern for. + /// + protected readonly HitObject HitObject; + + /// + /// The beatmap which is a part of. + /// + protected readonly ManiaBeatmap Beatmap; + + protected readonly int TotalColumns; + + protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) + { + if (hitObject == null) throw new ArgumentNullException(nameof(hitObject)); + if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); + if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern)); + + HitObject = hitObject; + Beatmap = beatmap; + PreviousPattern = previousPattern; + + TotalColumns = Beatmap.TotalColumns; + } + + /// + /// Generates the pattern for , filled with hit objects. + /// + /// The containing the hit objects. + public abstract Pattern Generate(); + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index cb500735f7..afec607a7c 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mania.UI; - -namespace osu.Game.Rulesets.Mania.Beatmaps -{ - /// - /// Defines properties for each stage in a . - /// - public struct StageDefinition - { - /// - /// The number of s which this stage contains. - /// - public int Columns; - - /// - /// Whether the column index is a special column for this stage. - /// - /// The 0-based column index. - /// Whether the column is a special column. - public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Beatmaps +{ + /// + /// Defines properties for each stage in a . + /// + public struct StageDefinition + { + /// + /// The number of s which this stage contains. + /// + public int Columns; + + /// + /// Whether the column index is a special column for this stage. + /// + /// The 0-based column index. + /// Whether the column is a special column. + public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; + } +} diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs index a4de360870..ea5f590bd1 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration.Tracking; -using osu.Game.Configuration; -using osu.Game.Rulesets.Configuration; - -namespace osu.Game.Rulesets.Mania.Configuration -{ - public class ManiaConfigManager : RulesetConfigManager - { - public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) - : base(settings, ruleset, variant) - { - } - - protected override void InitialiseDefaults() - { - base.InitialiseDefaults(); - - Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0); - } - - public override TrackedSettings CreateTrackedSettings() => new TrackedSettings - { - new TrackedSetting(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) - }; - } - - public enum ManiaSetting - { - ScrollTime - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration.Tracking; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; + +namespace osu.Game.Rulesets.Mania.Configuration +{ + public class ManiaConfigManager : RulesetConfigManager + { + public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) + : base(settings, ruleset, variant) + { + } + + protected override void InitialiseDefaults() + { + base.InitialiseDefaults(); + + Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0); + } + + public override TrackedSettings CreateTrackedSettings() => new TrackedSettings + { + new TrackedSetting(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) + }; + } + + public enum ManiaSetting + { + ScrollTime + } +} diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs index 361923266c..3a4beda77d 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Judgements -{ - public class HoldNoteTailJudgement : ManiaJudgement - { - /// - /// Whether the hold note has been released too early and shouldn't give full score for the release. - /// - public bool HasBroken; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return base.NumericResultFor(result); - case HitResult.Great: - case HitResult.Perfect: - return base.NumericResultFor(HasBroken ? HitResult.Good : result); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Judgements +{ + public class HoldNoteTailJudgement : ManiaJudgement + { + /// + /// Whether the hold note has been released too early and shouldn't give full score for the release. + /// + public bool HasBroken; + + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return base.NumericResultFor(result); + case HitResult.Great: + case HitResult.Perfect: + return base.NumericResultFor(HasBroken ? HitResult.Good : result); + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 62df4a30cc..6eb5a79200 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Judgements -{ - public class HoldNoteTickJudgement : ManiaJudgement - { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 20; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Judgements +{ + public class HoldNoteTickJudgement : ManiaJudgement + { + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) => 20; + } +} diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index dae89bf88d..4e0649c708 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Judgements -{ - public class ManiaJudgement : Judgement - { - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - case HitResult.Meh: - return 50; - case HitResult.Ok: - return 100; - case HitResult.Good: - return 200; - case HitResult.Great: - case HitResult.Perfect: - return 300; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Judgements +{ + public class ManiaJudgement : Judgement + { + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Meh: + return 50; + case HitResult.Ok: + return 100; + case HitResult.Good: + return 200; + case HitResult.Great: + case HitResult.Perfect: + return 300; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs index f4e3d54a3d..5eea346836 100644 --- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs @@ -1,146 +1,146 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; -using System; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Mania -{ - internal class ManiaDifficultyCalculator : DifficultyCalculator - { - private const double star_scaling_factor = 0.018; - - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 400; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.9; - - /// - /// HitObjects are stored as a member variable. - /// - private readonly List difficultyHitObjects = new List(); - - public ManiaDifficultyCalculator(Beatmap beatmap) - : base(beatmap) - { - } - - public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods) - : base(beatmap, mods) - { - } - - public override double Calculate(Dictionary categoryDifficulty = null) - { - // Fill our custom DifficultyHitObject class, that carries additional information - difficultyHitObjects.Clear(); - - int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7; - - foreach (var hitObject in Beatmap.HitObjects) - difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount)); - - // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. - difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - - if (!calculateStrainValues()) - return 0; - - double starRating = calculateDifficulty() * star_scaling_factor; - - categoryDifficulty?.Add("Strain", starRating); - - return starRating; - } - - private bool calculateStrainValues() - { - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) - { - if (!hitObjectsEnumerator.MoveNext()) - return false; - - ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current; - - // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. - while (hitObjectsEnumerator.MoveNext()) - { - var next = hitObjectsEnumerator.Current; - next?.CalculateStrains(current, TimeRate); - current = next; - } - - return true; - } - } - - private double calculateDifficulty() - { - double actualStrainStep = strain_step * TimeRate; - - // Find the highest strain value within each strain step - List highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - ManiaHitObjectDifficulty previousHitObject = null; - foreach (var hitObject in difficultyHitObjects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - double strain = hitObject.IndividualStrain + hitObject.OverallStrain; - maximumStrain = Math.Max(strain, maximumStrain); - - previousHitObject = hitObject; - } - - // Build the weighted sum over the highest strains for each interval - double difficulty = 0; - double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - foreach (double strain in highestStrains) - { - difficulty += weight * strain; - weight *= decay_weight; - } - - return difficulty; - } - - protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; +using System; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Mania +{ + internal class ManiaDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.018; + + /// + /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step. + /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. + /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. + /// + private const double strain_step = 400; + + /// + /// The weighting of each strain value decays to this number * it's previous value + /// + private const double decay_weight = 0.9; + + /// + /// HitObjects are stored as a member variable. + /// + private readonly List difficultyHitObjects = new List(); + + public ManiaDifficultyCalculator(Beatmap beatmap) + : base(beatmap) + { + } + + public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods) + : base(beatmap, mods) + { + } + + public override double Calculate(Dictionary categoryDifficulty = null) + { + // Fill our custom DifficultyHitObject class, that carries additional information + difficultyHitObjects.Clear(); + + int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7; + + foreach (var hitObject in Beatmap.HitObjects) + difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount)); + + // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. + difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); + + if (!calculateStrainValues()) + return 0; + + double starRating = calculateDifficulty() * star_scaling_factor; + + categoryDifficulty?.Add("Strain", starRating); + + return starRating; + } + + private bool calculateStrainValues() + { + // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. + using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) + { + if (!hitObjectsEnumerator.MoveNext()) + return false; + + ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current; + + // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. + while (hitObjectsEnumerator.MoveNext()) + { + var next = hitObjectsEnumerator.Current; + next?.CalculateStrains(current, TimeRate); + current = next; + } + + return true; + } + } + + private double calculateDifficulty() + { + double actualStrainStep = strain_step * TimeRate; + + // Find the highest strain value within each strain step + List highestStrains = new List(); + double intervalEndTime = actualStrainStep; + double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval + + ManiaHitObjectDifficulty previousHitObject = null; + foreach (var hitObject in difficultyHitObjects) + { + // While we are beyond the current interval push the currently available maximum to our strain list + while (hitObject.BaseHitObject.StartTime > intervalEndTime) + { + highestStrains.Add(maximumStrain); + + // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay + // until the beginning of the next interval. + if (previousHitObject == null) + { + maximumStrain = 0; + } + else + { + double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay; + } + + // Go to the next time interval + intervalEndTime += actualStrainStep; + } + + // Obtain maximum strain + double strain = hitObject.IndividualStrain + hitObject.OverallStrain; + maximumStrain = Math.Max(strain, maximumStrain); + + previousHitObject = hitObject; + } + + // Build the weighted sum over the highest strains for each interval + double difficulty = 0; + double weight = 1; + highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + foreach (double strain in highestStrains) + { + difficulty += weight * strain; + weight *= decay_weight; + } + + return difficulty; + } + + protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap); + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 01e2821540..61356d96dc 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mania -{ - public class ManiaInputManager : RulesetInputManager - { - public ManiaInputManager(RulesetInfo ruleset, int variant) - : base(ruleset, variant, SimultaneousBindingMode.Unique) - { - } - } - - public enum ManiaAction - { - [Description("Special 1")] - Special1 = 1, - [Description("Special 2")] - Special2, - - // This offsets the start value of normal keys in-case we add more special keys - // above at a later time, without breaking replays/configs. - [Description("Key 1")] - Key1 = 10, - [Description("Key 2")] - Key2, - [Description("Key 3")] - Key3, - [Description("Key 4")] - Key4, - [Description("Key 5")] - Key5, - [Description("Key 6")] - Key6, - [Description("Key 7")] - Key7, - [Description("Key 8")] - Key8, - [Description("Key 9")] - Key9, - [Description("Key 10")] - Key10, - [Description("Key 11")] - Key11, - [Description("Key 12")] - Key12, - [Description("Key 13")] - Key13, - [Description("Key 14")] - Key14, - [Description("Key 15")] - Key15, - [Description("Key 16")] - Key16, - [Description("Key 17")] - Key17, - [Description("Key 18")] - Key18, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania +{ + public class ManiaInputManager : RulesetInputManager + { + public ManiaInputManager(RulesetInfo ruleset, int variant) + : base(ruleset, variant, SimultaneousBindingMode.Unique) + { + } + } + + public enum ManiaAction + { + [Description("Special 1")] + Special1 = 1, + [Description("Special 2")] + Special2, + + // This offsets the start value of normal keys in-case we add more special keys + // above at a later time, without breaking replays/configs. + [Description("Key 1")] + Key1 = 10, + [Description("Key 2")] + Key2, + [Description("Key 3")] + Key3, + [Description("Key 4")] + Key4, + [Description("Key 5")] + Key5, + [Description("Key 6")] + Key6, + [Description("Key 7")] + Key7, + [Description("Key 8")] + Key8, + [Description("Key 9")] + Key9, + [Description("Key 10")] + Key10, + [Description("Key 11")] + Key11, + [Description("Key 12")] + Key12, + [Description("Key 13")] + Key13, + [Description("Key 14")] + Key14, + [Description("Key 15")] + Key15, + [Description("Key 16")] + Key16, + [Description("Key 17")] + Key17, + [Description("Key 18")] + Key18, + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index e135e14001..7f37f55d14 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -1,312 +1,312 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Replays.Types; - -namespace osu.Game.Rulesets.Mania -{ - public class ManiaRuleset : Ruleset - { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset); - - public override IEnumerable GetModsFor(ModType type) - { - switch (type) - { - case ModType.DifficultyReduction: - return new Mod[] - { - new ManiaModEasy(), - new ManiaModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new ManiaModHalfTime(), - new ManiaModDaycore(), - }, - }, - }; - - case ModType.DifficultyIncrease: - return new Mod[] - { - new ManiaModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new ManiaModSuddenDeath(), - new ManiaModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new ManiaModDoubleTime(), - new ManiaModNightcore(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new ManiaModFadeIn(), - new ManiaModHidden(), - } - }, - new ManiaModFlashlight(), - }; - - case ModType.Special: - return new Mod[] - { - new MultiMod - { - Mods = new Mod[] - { - new ManiaModKey4(), - new ManiaModKey5(), - new ManiaModKey6(), - new ManiaModKey7(), - new ManiaModKey8(), - new ManiaModKey9(), - new ManiaModKey1(), - new ManiaModKey2(), - new ManiaModKey3(), - }, - }, - new ManiaModRandom(), - new ManiaModDualStages(), - new ManiaModMirror(), - new MultiMod - { - Mods = new Mod[] - { - new ManiaModAutoplay(), - new ModCinema(), - }, - }, - }; - - default: - return new Mod[] { }; - } - } - - public override string Description => "osu!mania"; - - public override string ShortName => "mania"; - - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; - - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods); - - public override int? LegacyID => 3; - - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); - - public ManiaRuleset(RulesetInfo rulesetInfo = null) - : base(rulesetInfo) - { - } - - public override IEnumerable AvailableVariants - { - get - { - for (int i = 1; i <= 9; i++) - yield return (int)PlayfieldType.Single + i; - for (int i = 2; i <= 18; i += 2) - yield return (int)PlayfieldType.Dual + i; - } - } - - public override IEnumerable GetDefaultKeyBindings(int variant = 0) - { - switch (getPlayfieldType(variant)) - { - case PlayfieldType.Single: - return new VariantMappingGenerator - { - LeftKeys = new[] - { - InputKey.A, - InputKey.S, - InputKey.D, - InputKey.F - }, - RightKeys = new[] - { - InputKey.J, - InputKey.K, - InputKey.L, - InputKey.Semicolon - }, - SpecialKey = InputKey.Space, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1, - }.GenerateKeyBindingsFor(variant, out _); - case PlayfieldType.Dual: - int keys = getDualStageKeyCount(variant); - - var stage1Bindings = new VariantMappingGenerator - { - LeftKeys = new[] - { - InputKey.Number1, - InputKey.Number2, - InputKey.Number3, - InputKey.Number4, - }, - RightKeys = new[] - { - InputKey.Z, - InputKey.X, - InputKey.C, - InputKey.V - }, - SpecialKey = InputKey.Tilde, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1 - }.GenerateKeyBindingsFor(keys, out var nextNormal); - - var stage2Bindings = new VariantMappingGenerator - { - LeftKeys = new[] - { - InputKey.Number7, - InputKey.Number8, - InputKey.Number9, - InputKey.Number0 - }, - RightKeys = new[] - { - InputKey.O, - InputKey.P, - InputKey.BracketLeft, - InputKey.BracketRight - }, - SpecialKey = InputKey.BackSlash, - SpecialAction = ManiaAction.Special2, - NormalActionStart = nextNormal - }.GenerateKeyBindingsFor(keys, out _); - - return stage1Bindings.Concat(stage2Bindings); - } - - return new KeyBinding[0]; - } - - public override string GetVariantName(int variant) - { - switch (getPlayfieldType(variant)) - { - default: - return $"{variant}K"; - case PlayfieldType.Dual: - { - var keys = getDualStageKeyCount(variant); - return $"{keys}K + {keys}K"; - } - } - } - - /// - /// Finds the number of keys for each stage in a variant. - /// - /// The variant. - private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2; - - /// - /// Finds the that corresponds to a variant value. - /// - /// The variant value. - /// The that corresponds to . - private PlayfieldType getPlayfieldType(int variant) - { - return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); - } - - private class VariantMappingGenerator - { - /// - /// All the s available to the left hand. - /// - public InputKey[] LeftKeys; - - /// - /// All the s available to the right hand. - /// - public InputKey[] RightKeys; - - /// - /// The for the special key. - /// - public InputKey SpecialKey; - - /// - /// The at which the normal columns should begin. - /// - public ManiaAction NormalActionStart; - - /// - /// The for the special column. - /// - public ManiaAction SpecialAction; - - /// - /// Generates a list of s for a specific number of columns. - /// - /// The number of columns that need to be bound. - /// The next to use for normal columns. - /// The keybindings. - public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) - { - ManiaAction currentNormalAction = NormalActionStart; - - var bindings = new List(); - - for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) - bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); - - for (int i = 0; i < columns / 2; i++) - bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); - - if (columns % 2 == 1) - bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); - - nextNormalAction = currentNormalAction; - return bindings; - } - } - } - - public enum PlayfieldType - { - /// - /// Columns are grouped into a single stage. - /// Number of columns in this stage lies at (item - Single). - /// - Single = 0, - /// - /// Columns are grouped into two stages. - /// Overall number of columns lies at (item - Dual), further computation is required for - /// number of columns in each individual stage. - /// - Dual = 1000, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Replays.Types; + +namespace osu.Game.Rulesets.Mania +{ + public class ManiaRuleset : Ruleset + { + public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset); + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.DifficultyReduction: + return new Mod[] + { + new ManiaModEasy(), + new ManiaModNoFail(), + new MultiMod + { + Mods = new Mod[] + { + new ManiaModHalfTime(), + new ManiaModDaycore(), + }, + }, + }; + + case ModType.DifficultyIncrease: + return new Mod[] + { + new ManiaModHardRock(), + new MultiMod + { + Mods = new Mod[] + { + new ManiaModSuddenDeath(), + new ManiaModPerfect(), + }, + }, + new MultiMod + { + Mods = new Mod[] + { + new ManiaModDoubleTime(), + new ManiaModNightcore(), + }, + }, + new MultiMod + { + Mods = new Mod[] + { + new ManiaModFadeIn(), + new ManiaModHidden(), + } + }, + new ManiaModFlashlight(), + }; + + case ModType.Special: + return new Mod[] + { + new MultiMod + { + Mods = new Mod[] + { + new ManiaModKey4(), + new ManiaModKey5(), + new ManiaModKey6(), + new ManiaModKey7(), + new ManiaModKey8(), + new ManiaModKey9(), + new ManiaModKey1(), + new ManiaModKey2(), + new ManiaModKey3(), + }, + }, + new ManiaModRandom(), + new ManiaModDualStages(), + new ManiaModMirror(), + new MultiMod + { + Mods = new Mod[] + { + new ManiaModAutoplay(), + new ModCinema(), + }, + }, + }; + + default: + return new Mod[] { }; + } + } + + public override string Description => "osu!mania"; + + public override string ShortName => "mania"; + + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods); + + public override int? LegacyID => 3; + + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); + + public ManiaRuleset(RulesetInfo rulesetInfo = null) + : base(rulesetInfo) + { + } + + public override IEnumerable AvailableVariants + { + get + { + for (int i = 1; i <= 9; i++) + yield return (int)PlayfieldType.Single + i; + for (int i = 2; i <= 18; i += 2) + yield return (int)PlayfieldType.Dual + i; + } + } + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) + { + switch (getPlayfieldType(variant)) + { + case PlayfieldType.Single: + return new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.A, + InputKey.S, + InputKey.D, + InputKey.F + }, + RightKeys = new[] + { + InputKey.J, + InputKey.K, + InputKey.L, + InputKey.Semicolon + }, + SpecialKey = InputKey.Space, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1, + }.GenerateKeyBindingsFor(variant, out _); + case PlayfieldType.Dual: + int keys = getDualStageKeyCount(variant); + + var stage1Bindings = new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.Number1, + InputKey.Number2, + InputKey.Number3, + InputKey.Number4, + }, + RightKeys = new[] + { + InputKey.Z, + InputKey.X, + InputKey.C, + InputKey.V + }, + SpecialKey = InputKey.Tilde, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1 + }.GenerateKeyBindingsFor(keys, out var nextNormal); + + var stage2Bindings = new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.Number7, + InputKey.Number8, + InputKey.Number9, + InputKey.Number0 + }, + RightKeys = new[] + { + InputKey.O, + InputKey.P, + InputKey.BracketLeft, + InputKey.BracketRight + }, + SpecialKey = InputKey.BackSlash, + SpecialAction = ManiaAction.Special2, + NormalActionStart = nextNormal + }.GenerateKeyBindingsFor(keys, out _); + + return stage1Bindings.Concat(stage2Bindings); + } + + return new KeyBinding[0]; + } + + public override string GetVariantName(int variant) + { + switch (getPlayfieldType(variant)) + { + default: + return $"{variant}K"; + case PlayfieldType.Dual: + { + var keys = getDualStageKeyCount(variant); + return $"{keys}K + {keys}K"; + } + } + } + + /// + /// Finds the number of keys for each stage in a variant. + /// + /// The variant. + private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2; + + /// + /// Finds the that corresponds to a variant value. + /// + /// The variant value. + /// The that corresponds to . + private PlayfieldType getPlayfieldType(int variant) + { + return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); + } + + private class VariantMappingGenerator + { + /// + /// All the s available to the left hand. + /// + public InputKey[] LeftKeys; + + /// + /// All the s available to the right hand. + /// + public InputKey[] RightKeys; + + /// + /// The for the special key. + /// + public InputKey SpecialKey; + + /// + /// The at which the normal columns should begin. + /// + public ManiaAction NormalActionStart; + + /// + /// The for the special column. + /// + public ManiaAction SpecialAction; + + /// + /// Generates a list of s for a specific number of columns. + /// + /// The number of columns that need to be bound. + /// The next to use for normal columns. + /// The keybindings. + public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) + { + ManiaAction currentNormalAction = NormalActionStart; + + var bindings = new List(); + + for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) + bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); + + for (int i = 0; i < columns / 2; i++) + bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); + + if (columns % 2 == 1) + bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); + + nextNormalAction = currentNormalAction; + return bindings; + } + } + } + + public enum PlayfieldType + { + /// + /// Columns are grouped into a single stage. + /// Number of columns in this stage lies at (item - Single). + /// + Single = 0, + /// + /// Columns are grouped into two stages. + /// Overall number of columns lies at (item - Dual), further computation is required for + /// number of columns in each individual stage. + /// + Dual = 1000, + } +} diff --git a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs index c8277af415..a3efd5c2bd 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs @@ -1,91 +1,91 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Rulesets.Mania.MathUtils -{ - /// - /// A PRNG specified in http://heliosphan.org/fastrandom.html. - /// - internal class FastRandom - { - private const double int_to_real = 1.0 / (int.MaxValue + 1.0); - private const uint int_mask = 0x7FFFFFFF; - private const uint y = 842502087; - private const uint z = 3579807591; - private const uint w = 273326509; - private uint _x, _y = y, _z = z, _w = w; - - public FastRandom(int seed) - { - _x = (uint)seed; - } - - public FastRandom() - : this(Environment.TickCount) - { - } - - /// - /// Generates a random unsigned integer within the range [, ). - /// - /// The random value. - public uint NextUInt() - { - uint t = _x ^ _x << 11; - _x = _y; - _y = _z; - _z = _w; - return _w = _w ^ _w >> 19 ^ t ^ t >> 8; - } - - /// - /// Generates a random integer value within the range [0, ). - /// - /// The random value. - public int Next() => (int)(int_mask & NextUInt()); - - /// - /// Generates a random integer value within the range [0, ). - /// - /// The upper bound. - /// The random value. - public int Next(int upperBound) => (int)(NextDouble() * upperBound); - - /// - /// Generates a random integer value within the range [, ). - /// - /// The lower bound of the range. - /// The upper bound of the range. - /// The random value. - public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound)); - - /// - /// Generates a random double value within the range [0, 1). - /// - /// The random value. - public double NextDouble() => int_to_real * Next(); - - private uint bitBuffer; - private int bitIndex = 32; - - /// - /// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls. - /// - /// The random value. - public bool NextBool() - { - if (bitIndex == 32) - { - bitBuffer = NextUInt(); - bitIndex = 1; - - return (bitBuffer & 1) == 1; - } - - bitIndex++; - return ((bitBuffer >>= 1) & 1) == 1; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Rulesets.Mania.MathUtils +{ + /// + /// A PRNG specified in http://heliosphan.org/fastrandom.html. + /// + internal class FastRandom + { + private const double int_to_real = 1.0 / (int.MaxValue + 1.0); + private const uint int_mask = 0x7FFFFFFF; + private const uint y = 842502087; + private const uint z = 3579807591; + private const uint w = 273326509; + private uint _x, _y = y, _z = z, _w = w; + + public FastRandom(int seed) + { + _x = (uint)seed; + } + + public FastRandom() + : this(Environment.TickCount) + { + } + + /// + /// Generates a random unsigned integer within the range [, ). + /// + /// The random value. + public uint NextUInt() + { + uint t = _x ^ _x << 11; + _x = _y; + _y = _z; + _z = _w; + return _w = _w ^ _w >> 19 ^ t ^ t >> 8; + } + + /// + /// Generates a random integer value within the range [0, ). + /// + /// The random value. + public int Next() => (int)(int_mask & NextUInt()); + + /// + /// Generates a random integer value within the range [0, ). + /// + /// The upper bound. + /// The random value. + public int Next(int upperBound) => (int)(NextDouble() * upperBound); + + /// + /// Generates a random integer value within the range [, ). + /// + /// The lower bound of the range. + /// The upper bound of the range. + /// The random value. + public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound)); + + /// + /// Generates a random double value within the range [0, 1). + /// + /// The random value. + public double NextDouble() => int_to_real * Next(); + + private uint bitBuffer; + private int bitIndex = 32; + + /// + /// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls. + /// + /// The random value. + public bool NextBool() + { + if (bitIndex == 32) + { + bitBuffer = NextUInt(); + bitIndex = 1; + + return (bitBuffer & 1) == 1; + } + + bitIndex++; + return ((bitBuffer >>= 1) & 1) == 1; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs index 93d98b5d83..e08c9aa2a8 100644 --- a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public interface IPlayfieldTypeMod : IApplicableMod - { - /// - /// The which this requires. - /// - PlayfieldType PlayfieldType { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public interface IPlayfieldTypeMod : IApplicableMod + { + /// + /// The which this requires. + /// + PlayfieldType PlayfieldType { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index aafebb61ec..dbd30121a8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter - { - public override string ShortenedName => Name; - public abstract int KeyCount { get; } - public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier - public override bool Ranked => true; - - public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter) - { - var mbc = (ManiaBeatmapConverter)beatmapConverter; - - // Although this can work, for now let's not allow keymods for mania-specific beatmaps - if (mbc.IsForCurrentRuleset) - return; - - mbc.TargetColumns = KeyCount; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter + { + public override string ShortenedName => Name; + public abstract int KeyCount { get; } + public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier + public override bool Ranked => true; + + public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter) + { + var mbc = (ManiaBeatmapConverter)beatmapConverter; + + // Although this can work, for now let's not allow keymods for mania-specific beatmaps + if (mbc.IsForCurrentRuleset) + return; + + mbc.TargetColumns = KeyCount; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 9ceb0ab7ea..89792956bb 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModAutoplay : ModAutoplay - { - protected override Score CreateReplayScore(Beatmap beatmap) - { - return new Score - { - User = new User { Username = "osu!topus!" }, - Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModAutoplay : ModAutoplay + { + protected override Score CreateReplayScore(Beatmap beatmap) + { + return new Score + { + User = new User { Username = "osu!topus!" }, + Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs index 99f49e6620..506879bb96 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModDaycore : ModDaycore - { - public override double ScoreMultiplier => 0.5; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModDaycore : ModDaycore + { + public override double ScoreMultiplier => 0.5; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs index a9d77988c8..bd93f0db38 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModDoubleTime : ModDoubleTime - { - public override double ScoreMultiplier => 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index a1f9e0290e..197b37b3f5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -1,52 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer - { - public override string Name => "Dual Stages"; - public override string ShortenedName => "DS"; - public override string Description => @"Double the stages, double the fun!"; - public override double ScoreMultiplier => 0; - - public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter) - { - var mbc = (ManiaBeatmapConverter)beatmapConverter; - - // Although this can work, for now let's not allow keymods for mania-specific beatmaps - if (mbc.IsForCurrentRuleset) - return; - - mbc.TargetColumns *= 2; - } - - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) - { - var mrc = (ManiaRulesetContainer)rulesetContainer; - - // Although this can work, for now let's not allow keymods for mania-specific beatmaps - if (mrc.IsForCurrentRuleset) - return; - - var newDefinitions = new List(); - foreach (var existing in mrc.Beatmap.Stages) - { - newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); - newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); - } - - mrc.Beatmap.Stages = newDefinitions; - } - - public PlayfieldType PlayfieldType => PlayfieldType.Dual; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer + { + public override string Name => "Dual Stages"; + public override string ShortenedName => "DS"; + public override string Description => @"Double the stages, double the fun!"; + public override double ScoreMultiplier => 0; + + public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter) + { + var mbc = (ManiaBeatmapConverter)beatmapConverter; + + // Although this can work, for now let's not allow keymods for mania-specific beatmaps + if (mbc.IsForCurrentRuleset) + return; + + mbc.TargetColumns *= 2; + } + + public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + var mrc = (ManiaRulesetContainer)rulesetContainer; + + // Although this can work, for now let's not allow keymods for mania-specific beatmaps + if (mrc.IsForCurrentRuleset) + return; + + var newDefinitions = new List(); + foreach (var existing in mrc.Beatmap.Stages) + { + newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); + newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); + } + + mrc.Beatmap.Stages = newDefinitions; + } + + public PlayfieldType PlayfieldType => PlayfieldType.Dual; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index 0b3e851c64..8c85846ed1 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModEasy : ModEasy - { - public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModEasy : ModEasy + { + public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!"; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index ca5667a400..08815ede09 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModFadeIn : Mod - { - public override string Name => "Fade In"; - public override string ShortenedName => "FI"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; - public override ModType Type => ModType.DifficultyIncrease; - public override string Description => @"Keys appear out of nowhere!"; - public override double ScoreMultiplier => 1; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModFadeIn : Mod + { + public override string Name => "Fade In"; + public override string ShortenedName => "FI"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => @"Keys appear out of nowhere!"; + public override double ScoreMultiplier => 1; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 8d8693d11f..d7a1bc4fbe 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModFlashlight : ModFlashlight - { - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModFlashlight : ModFlashlight + { + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs index c00bb4275a..978554362d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModHalfTime : ModHalfTime - { - public override double ScoreMultiplier => 0.5; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModHalfTime : ModHalfTime + { + public override double ScoreMultiplier => 0.5; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index 8b77ea4c25..7b766cab85 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModHardRock : ModHardRock - { - public override double ScoreMultiplier => 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 9317dba19f..2ef68a35fa 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModHidden : ModHidden - { - public override string Description => @"Keys fade out before you hit them!"; - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModHidden : ModHidden + { + public override string Description => @"Keys fade out before you hit them!"; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index c0107e3758..c2a4ed444f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey1 : ManiaKeyMod - { - public override int KeyCount => 1; - public override string Name => "One Key"; - public override string ShortenedName => "1K"; - public override string Description => @"Play with one key."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey1 : ManiaKeyMod + { + public override int KeyCount => 1; + public override string Name => "One Key"; + public override string ShortenedName => "1K"; + public override string Description => @"Play with one key."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 11dbe0ba76..3d78ad449b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey2 : ManiaKeyMod - { - public override int KeyCount => 2; - public override string Name => "Two Keys"; - public override string ShortenedName => "2K"; - public override string Description => @"Play with two keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey2 : ManiaKeyMod + { + public override int KeyCount => 2; + public override string Name => "Two Keys"; + public override string ShortenedName => "2K"; + public override string Description => @"Play with two keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index 94ad53d8ea..a96375a81d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey3 : ManiaKeyMod - { - public override int KeyCount => 3; - public override string Name => "Three Keys"; - public override string ShortenedName => "3K"; - public override string Description => @"Play with three keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey3 : ManiaKeyMod + { + public override int KeyCount => 3; + public override string Name => "Three Keys"; + public override string ShortenedName => "3K"; + public override string Description => @"Play with three keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs index d9c27c5ef1..2bd3d08648 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey4 : ManiaKeyMod - { - public override int KeyCount => 4; - public override string Name => "Four Keys"; - public override string ShortenedName => "4K"; - public override string Description => @"Play with four keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey4 : ManiaKeyMod + { + public override int KeyCount => 4; + public override string Name => "Four Keys"; + public override string ShortenedName => "4K"; + public override string Description => @"Play with four keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs index e54bae93a7..e59b2d6e59 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey5 : ManiaKeyMod - { - public override int KeyCount => 5; - public override string Name => "Five Keys"; - public override string ShortenedName => "5K"; - public override string Description => @"Play with five keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey5 : ManiaKeyMod + { + public override int KeyCount => 5; + public override string Name => "Five Keys"; + public override string ShortenedName => "5K"; + public override string Description => @"Play with five keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs index 9c3bdf46b9..6a05317f46 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey6 : ManiaKeyMod - { - public override int KeyCount => 6; - public override string Name => "Six Keys"; - public override string ShortenedName => "6K"; - public override string Description => @"Play with six keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey6 : ManiaKeyMod + { + public override int KeyCount => 6; + public override string Name => "Six Keys"; + public override string ShortenedName => "6K"; + public override string Description => @"Play with six keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs index f17ac80be5..7280c345b8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey7 : ManiaKeyMod - { - public override int KeyCount => 7; - public override string Name => "Seven Keys"; - public override string ShortenedName => "7K"; - public override string Description => @"Play with seven keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey7 : ManiaKeyMod + { + public override int KeyCount => 7; + public override string Name => "Seven Keys"; + public override string ShortenedName => "7K"; + public override string Description => @"Play with seven keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs index 36a6fc838f..dddef0fa9d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey8 : ManiaKeyMod - { - public override int KeyCount => 8; - public override string Name => "Eight Keys"; - public override string ShortenedName => "8K"; - public override string Description => @"Play with eight keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey8 : ManiaKeyMod + { + public override int KeyCount => 8; + public override string Name => "Eight Keys"; + public override string ShortenedName => "8K"; + public override string Description => @"Play with eight keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs index 10f03e2480..4ef38503a4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKey9 : ManiaKeyMod - { - public override int KeyCount => 9; - public override string Name => "Nine Keys"; - public override string ShortenedName => "9K"; - public override string Description => @"Play with nine keys."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModKey9 : ManiaKeyMod + { + public override int KeyCount => 9; + public override string Name => "Nine Keys"; + public override string ShortenedName => "9K"; + public override string Description => @"Play with nine keys."; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index cfa5ef88b8..4192ec78da 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; -using System.Linq; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModMirror : Mod, IApplicableToRulesetContainer - { - public override string Name => "Mirror"; - public override string ShortenedName => "MR"; - public override ModType Type => ModType.Special; - public override double ScoreMultiplier => 1; - public override bool Ranked => true; - - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) - { - var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; - - rulesetContainer.Objects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using System.Linq; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModMirror : Mod, IApplicableToRulesetContainer + { + public override string Name => "Mirror"; + public override string ShortenedName => "MR"; + public override ModType Type => ModType.Special; + public override double ScoreMultiplier => 1; + public override bool Ranked => true; + + public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; + + rulesetContainer.Objects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs index a007224b74..b3e73be4ee 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModNightcore : ModNightcore - { - public override double ScoreMultiplier => 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNightcore : ModNightcore + { + public override double ScoreMultiplier => 1; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs index c9c50f9919..c22549bfc7 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModNoFail : ModNoFail - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModNoFail : ModNoFail + { + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs index 2c0bd5f8c3..abada804fb 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModPerfect : ModPerfect - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModPerfect : ModPerfect + { + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index df0f9a5437..5af898287a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.MathUtils; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModRandom : Mod, IApplicableToRulesetContainer - { - public override string Name => "Random"; - public override string ShortenedName => "RD"; - public override FontAwesome Icon => FontAwesome.fa_osu_dice; - public override string Description => @"Shuffle around the keys!"; - public override double ScoreMultiplier => 0; - - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) - { - var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; - var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList(); - - rulesetContainer.Objects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModRandom : Mod, IApplicableToRulesetContainer + { + public override string Name => "Random"; + public override string ShortenedName => "RD"; + public override FontAwesome Icon => FontAwesome.fa_osu_dice; + public override string Description => @"Shuffle around the keys!"; + public override double ScoreMultiplier => 0; + + public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; + var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList(); + + rulesetContainer.Objects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs index 9edf131195..05843b3af8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModSuddenDeath : ModSuddenDeath - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModSuddenDeath : ModSuddenDeath + { + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs index e5cba161a2..0ceb10cc89 100644 --- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Mania.Objects -{ - public class BarLine : ManiaHitObject - { - /// - /// The control point which this bar line is part of. - /// - public TimingControlPoint ControlPoint; - - /// - /// The index of the beat which this bar line represents within the control point. - /// This is a "major" bar line if % == 0. - /// - public int BeatIndex; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Mania.Objects +{ + public class BarLine : ManiaHitObject + { + /// + /// The control point which this bar line is part of. + /// + public TimingControlPoint ControlPoint; + + /// + /// The index of the beat which this bar line represents within the control point. + /// This is a "major" bar line if % == 0. + /// + public int BeatIndex; + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 83d67c855e..2147c5a761 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -1,74 +1,74 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables -{ - /// - /// Visualises a . Although this derives DrawableManiaHitObject, - /// this does not handle input/sound like a normal hit object. - /// - public class DrawableBarLine : DrawableManiaHitObject - { - /// - /// Height of major bar line triangles. - /// - private const float triangle_height = 12; - - /// - /// Offset of the major bar line triangles from the sides of the bar line. - /// - private const float triangle_offset = 9; - - public DrawableBarLine(BarLine barLine) - : base(barLine) - { - RelativeSizeAxes = Axes.X; - Height = 1; - - AddInternal(new Box - { - Name = "Bar line", - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - }); - - bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0; - - if (isMajor) - { - AddInternal(new EquilateralTriangle - { - Name = "Left triangle", - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopCentre, - Size = new Vector2(triangle_height), - X = -triangle_offset, - Rotation = 90 - }); - - AddInternal(new EquilateralTriangle - { - Name = "Right triangle", - Anchor = Anchor.BottomRight, - Origin = Anchor.TopCentre, - Size = new Vector2(triangle_height), - X = triangle_offset, - Rotation = -90 - }); - } - - if (!isMajor && barLine.BeatIndex % 2 == 1) - Alpha = 0.2f; - } - - protected override void UpdateState(ArmedState state) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + /// + /// Visualises a . Although this derives DrawableManiaHitObject, + /// this does not handle input/sound like a normal hit object. + /// + public class DrawableBarLine : DrawableManiaHitObject + { + /// + /// Height of major bar line triangles. + /// + private const float triangle_height = 12; + + /// + /// Offset of the major bar line triangles from the sides of the bar line. + /// + private const float triangle_offset = 9; + + public DrawableBarLine(BarLine barLine) + : base(barLine) + { + RelativeSizeAxes = Axes.X; + Height = 1; + + AddInternal(new Box + { + Name = "Bar line", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + }); + + bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0; + + if (isMajor) + { + AddInternal(new EquilateralTriangle + { + Name = "Left triangle", + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopCentre, + Size = new Vector2(triangle_height), + X = -triangle_offset, + Rotation = 90 + }); + + AddInternal(new EquilateralTriangle + { + Name = "Right triangle", + Anchor = Anchor.BottomRight, + Origin = Anchor.TopCentre, + Size = new Vector2(triangle_height), + X = triangle_offset, + Rotation = -90 + }); + } + + if (!isMajor && barLine.BeatIndex % 2 == 1) + Alpha = 0.2f; + } + + protected override void UpdateState(ArmedState state) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index c3d6a69a72..f8b2311a13 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -1,254 +1,254 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using OpenTK.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Judgements; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables -{ - /// - /// Visualises a hit object. - /// - public class DrawableHoldNote : DrawableManiaHitObject, IKeyBindingHandler - { - private readonly DrawableNote head; - private readonly DrawableNote tail; - - private readonly GlowPiece glowPiece; - private readonly BodyPiece bodyPiece; - private readonly Container fullHeightContainer; - - /// - /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. - /// - private double? holdStartTime; - - /// - /// Whether the hold note has been released too early and shouldn't give full score for the release. - /// - private bool hasBroken; - - public DrawableHoldNote(HoldNote hitObject, ManiaAction action) - : base(hitObject, action) - { - Container tickContainer; - RelativeSizeAxes = Axes.X; - - InternalChildren = new Drawable[] - { - // The hit object itself cannot be used for various elements because the tail overshoots it - // So a specialized container that is updated to contain the tail height is used - fullHeightContainer = new Container - { - RelativeSizeAxes = Axes.X, - Child = glowPiece = new GlowPiece() - }, - bodyPiece = new BodyPiece - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - }, - tickContainer = new Container - { - RelativeSizeAxes = Axes.Both, - ChildrenEnumerable = HitObject.NestedHitObjects.OfType().Select(tick => new DrawableHoldNoteTick(tick) - { - HoldStartTime = () => holdStartTime - }) - }, - head = new DrawableHeadNote(this, action) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - tail = new DrawableTailNote(this, action) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - } - }; - - foreach (var tick in tickContainer) - AddNested(tick); - - AddNested(head); - AddNested(tail); - } - - public override Color4 AccentColour - { - get { return base.AccentColour; } - set - { - base.AccentColour = value; - - glowPiece.AccentColour = value; - bodyPiece.AccentColour = value; - head.AccentColour = value; - tail.AccentColour = value; - } - } - - protected override void UpdateState(ArmedState state) - { - } - - protected override void Update() - { - base.Update(); - - // Make the body piece not lie under the head note - bodyPiece.Y = head.Height; - bodyPiece.Height = DrawHeight - head.Height; - - // Make the fullHeightContainer "contain" the height of the tail note, keeping in mind - // that the tail note overshoots the height of this hit object - fullHeightContainer.Height = DrawHeight + tail.Height; - } - - public bool OnPressed(ManiaAction action) - { - // Make sure the action happened within the body of the hold note - if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime) - return false; - - if (action != Action) - return false; - - // The user has pressed during the body of the hold note, after the head note and its hit windows have passed - // and within the limited range of the above if-statement. This state will be managed by the head note if the - // user has pressed during the hit windows of the head note. - holdStartTime = Time.Current; - - return true; - } - - public bool OnReleased(ManiaAction action) - { - // Make sure that the user started holding the key during the hold note - if (!holdStartTime.HasValue) - return false; - - if (action != Action) - return false; - - holdStartTime = null; - - // If the key has been released too early, the user should not receive full score for the release - if (!tail.IsHit) - hasBroken = true; - - return true; - } - - /// - /// The head note of a hold. - /// - private class DrawableHeadNote : DrawableNote - { - private readonly DrawableHoldNote holdNote; - - public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action) - : base(holdNote.HitObject.Head, action) - { - this.holdNote = holdNote; - - GlowPiece.Alpha = 0; - } - - public override bool OnPressed(ManiaAction action) - { - if (!base.OnPressed(action)) - return false; - - // If the key has been released too early, the user should not receive full score for the release - if (Judgements.Any(j => j.Result == HitResult.Miss)) - holdNote.hasBroken = true; - - // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held - // The body doesn't handle these early early hits, so we have to explicitly set the holding state here - holdNote.holdStartTime = Time.Current; - - return true; - } - - protected override void UpdateState(ArmedState state) - { - // The holdnote keeps scrolling through for now, so having the head disappear looks weird - } - } - - /// - /// The tail note of a hold. - /// - private class DrawableTailNote : DrawableNote - { - private readonly DrawableHoldNote holdNote; - - public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action) - : base(holdNote.HitObject.Tail, action) - { - this.holdNote = holdNote; - - GlowPiece.Alpha = 0; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered) - { - if (!HitObject.HitWindows.CanBeHit(timeOffset)) - { - AddJudgement(new HoldNoteTailJudgement - { - Result = HitResult.Miss, - HasBroken = holdNote.hasBroken - }); - } - - return; - } - - var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) - return; - - AddJudgement(new HoldNoteTailJudgement - { - Result = result, - HasBroken = holdNote.hasBroken - }); - } - - protected override void UpdateState(ArmedState state) - { - // The holdnote keeps scrolling through, so having the tail disappear looks weird - } - - public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down - - public override bool OnReleased(ManiaAction action) - { - // Make sure that the user started holding the key during the hold note - if (!holdNote.holdStartTime.HasValue) - return false; - - if (action != Action) - return false; - - UpdateJudgement(true); - - // Handled by the hold note, which will set holding = false - return false; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using OpenTK.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + /// + /// Visualises a hit object. + /// + public class DrawableHoldNote : DrawableManiaHitObject, IKeyBindingHandler + { + private readonly DrawableNote head; + private readonly DrawableNote tail; + + private readonly GlowPiece glowPiece; + private readonly BodyPiece bodyPiece; + private readonly Container fullHeightContainer; + + /// + /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. + /// + private double? holdStartTime; + + /// + /// Whether the hold note has been released too early and shouldn't give full score for the release. + /// + private bool hasBroken; + + public DrawableHoldNote(HoldNote hitObject, ManiaAction action) + : base(hitObject, action) + { + Container tickContainer; + RelativeSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + // The hit object itself cannot be used for various elements because the tail overshoots it + // So a specialized container that is updated to contain the tail height is used + fullHeightContainer = new Container + { + RelativeSizeAxes = Axes.X, + Child = glowPiece = new GlowPiece() + }, + bodyPiece = new BodyPiece + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + }, + tickContainer = new Container + { + RelativeSizeAxes = Axes.Both, + ChildrenEnumerable = HitObject.NestedHitObjects.OfType().Select(tick => new DrawableHoldNoteTick(tick) + { + HoldStartTime = () => holdStartTime + }) + }, + head = new DrawableHeadNote(this, action) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + tail = new DrawableTailNote(this, action) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + }; + + foreach (var tick in tickContainer) + AddNested(tick); + + AddNested(head); + AddNested(tail); + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + + glowPiece.AccentColour = value; + bodyPiece.AccentColour = value; + head.AccentColour = value; + tail.AccentColour = value; + } + } + + protected override void UpdateState(ArmedState state) + { + } + + protected override void Update() + { + base.Update(); + + // Make the body piece not lie under the head note + bodyPiece.Y = head.Height; + bodyPiece.Height = DrawHeight - head.Height; + + // Make the fullHeightContainer "contain" the height of the tail note, keeping in mind + // that the tail note overshoots the height of this hit object + fullHeightContainer.Height = DrawHeight + tail.Height; + } + + public bool OnPressed(ManiaAction action) + { + // Make sure the action happened within the body of the hold note + if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime) + return false; + + if (action != Action) + return false; + + // The user has pressed during the body of the hold note, after the head note and its hit windows have passed + // and within the limited range of the above if-statement. This state will be managed by the head note if the + // user has pressed during the hit windows of the head note. + holdStartTime = Time.Current; + + return true; + } + + public bool OnReleased(ManiaAction action) + { + // Make sure that the user started holding the key during the hold note + if (!holdStartTime.HasValue) + return false; + + if (action != Action) + return false; + + holdStartTime = null; + + // If the key has been released too early, the user should not receive full score for the release + if (!tail.IsHit) + hasBroken = true; + + return true; + } + + /// + /// The head note of a hold. + /// + private class DrawableHeadNote : DrawableNote + { + private readonly DrawableHoldNote holdNote; + + public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action) + : base(holdNote.HitObject.Head, action) + { + this.holdNote = holdNote; + + GlowPiece.Alpha = 0; + } + + public override bool OnPressed(ManiaAction action) + { + if (!base.OnPressed(action)) + return false; + + // If the key has been released too early, the user should not receive full score for the release + if (Judgements.Any(j => j.Result == HitResult.Miss)) + holdNote.hasBroken = true; + + // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held + // The body doesn't handle these early early hits, so we have to explicitly set the holding state here + holdNote.holdStartTime = Time.Current; + + return true; + } + + protected override void UpdateState(ArmedState state) + { + // The holdnote keeps scrolling through for now, so having the head disappear looks weird + } + } + + /// + /// The tail note of a hold. + /// + private class DrawableTailNote : DrawableNote + { + private readonly DrawableHoldNote holdNote; + + public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action) + : base(holdNote.HitObject.Tail, action) + { + this.holdNote = holdNote; + + GlowPiece.Alpha = 0; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered) + { + if (!HitObject.HitWindows.CanBeHit(timeOffset)) + { + AddJudgement(new HoldNoteTailJudgement + { + Result = HitResult.Miss, + HasBroken = holdNote.hasBroken + }); + } + + return; + } + + var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) + return; + + AddJudgement(new HoldNoteTailJudgement + { + Result = result, + HasBroken = holdNote.hasBroken + }); + } + + protected override void UpdateState(ArmedState state) + { + // The holdnote keeps scrolling through, so having the tail disappear looks weird + } + + public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down + + public override bool OnReleased(ManiaAction action) + { + // Make sure that the user started holding the key during the hold note + if (!holdNote.holdStartTime.HasValue) + return false; + + if (action != Action) + return false; + + UpdateJudgement(true); + + // Handled by the hold note, which will set holding = false + return false; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index b50a5e897e..74c17ad0fb 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -1,111 +1,111 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables -{ - /// - /// Visualises a hit object. - /// - public class DrawableHoldNoteTick : DrawableManiaHitObject - { - /// - /// References the time at which the user started holding the hold note. - /// - public Func HoldStartTime; - - private readonly Container glowContainer; - - public DrawableHoldNoteTick(HoldNoteTick hitObject) - : base(hitObject) - { - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - RelativeSizeAxes = Axes.X; - Size = new Vector2(1); - - InternalChildren = new[] - { - glowContainer = new CircularContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - } - }; - } - - public override Color4 AccentColour - { - get { return base.AccentColour; } - set - { - base.AccentColour = value; - - glowContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 2f, - Roundness = 15f, - Colour = value.Opacity(0.3f) - }; - } - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered) - return; - - if (Time.Current < HitObject.StartTime) - return; - - if (HoldStartTime?.Invoke() > HitObject.StartTime) - return; - - AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); - } - - protected override void UpdateState(ArmedState state) - { - switch (State.Value) - { - case ArmedState.Hit: - AccentColour = Color4.Green; - break; - } - } - - protected override void Update() - { - if (AllJudged) - return; - - if (HoldStartTime?.Invoke() == null) - return; - - UpdateJudgement(true); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + /// + /// Visualises a hit object. + /// + public class DrawableHoldNoteTick : DrawableManiaHitObject + { + /// + /// References the time at which the user started holding the hold note. + /// + public Func HoldStartTime; + + private readonly Container glowContainer; + + public DrawableHoldNoteTick(HoldNoteTick hitObject) + : base(hitObject) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + RelativeSizeAxes = Axes.X; + Size = new Vector2(1); + + InternalChildren = new[] + { + glowContainer = new CircularContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + }; + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + + glowContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 2f, + Roundness = 15f, + Colour = value.Opacity(0.3f) + }; + } + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered) + return; + + if (Time.Current < HitObject.StartTime) + return; + + if (HoldStartTime?.Invoke() > HitObject.StartTime) + return; + + AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); + } + + protected override void UpdateState(ArmedState state) + { + switch (State.Value) + { + case ArmedState.Hit: + AccentColour = Color4.Green; + break; + } + } + + protected override void Update() + { + if (AllJudged) + return; + + if (HoldStartTime?.Invoke() == null) + return; + + UpdateJudgement(true); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 3aec8d25f9..db1fec40de 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables -{ - public abstract class DrawableManiaHitObject : DrawableHitObject - where TObject : ManiaHitObject - { - /// - /// The key that will trigger input for this hit object. - /// - protected ManiaAction Action { get; } - - public new TObject HitObject; - - protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null) - : base(hitObject) - { - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - HitObject = hitObject; - - if (action != null) - Action = action.Value; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + public abstract class DrawableManiaHitObject : DrawableHitObject + where TObject : ManiaHitObject + { + /// + /// The key that will trigger input for this hit object. + /// + protected ManiaAction Action { get; } + + public new TObject HitObject; + + protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null) + : base(hitObject) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + HitObject = hitObject; + + if (action != null) + Action = action.Value; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index c171325fb2..0340e6bba3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -1,95 +1,95 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables -{ - /// - /// Visualises a hit object. - /// - public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler - { - protected readonly GlowPiece GlowPiece; - - private readonly LaneGlowPiece laneGlowPiece; - private readonly NotePiece headPiece; - - public DrawableNote(Note hitObject, ManiaAction action) - : base(hitObject, action) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChildren = new Drawable[] - { - laneGlowPiece = new LaneGlowPiece - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - GlowPiece = new GlowPiece(), - headPiece = new NotePiece - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - } - }; - } - - public override Color4 AccentColour - { - get { return base.AccentColour; } - set - { - base.AccentColour = value; - laneGlowPiece.AccentColour = AccentColour; - GlowPiece.AccentColour = AccentColour; - headPiece.AccentColour = AccentColour; - } - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered) - { - if (!HitObject.HitWindows.CanBeHit(timeOffset)) - AddJudgement(new ManiaJudgement { Result = HitResult.Miss }); - return; - } - - var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) - return; - - AddJudgement(new ManiaJudgement { Result = result }); - } - - protected override void UpdateState(ArmedState state) - { - switch (state) - { - case ArmedState.Hit: - case ArmedState.Miss: - this.FadeOut(100).Expire(); - break; - } - } - - public virtual bool OnPressed(ManiaAction action) - { - if (action != Action) - return false; - - return UpdateJudgement(true); - } - - public virtual bool OnReleased(ManiaAction action) => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + /// + /// Visualises a hit object. + /// + public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler + { + protected readonly GlowPiece GlowPiece; + + private readonly LaneGlowPiece laneGlowPiece; + private readonly NotePiece headPiece; + + public DrawableNote(Note hitObject, ManiaAction action) + : base(hitObject, action) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + laneGlowPiece = new LaneGlowPiece + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + GlowPiece = new GlowPiece(), + headPiece = new NotePiece + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + }; + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + laneGlowPiece.AccentColour = AccentColour; + GlowPiece.AccentColour = AccentColour; + headPiece.AccentColour = AccentColour; + } + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered) + { + if (!HitObject.HitWindows.CanBeHit(timeOffset)) + AddJudgement(new ManiaJudgement { Result = HitResult.Miss }); + return; + } + + var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) + return; + + AddJudgement(new ManiaJudgement { Result = result }); + } + + protected override void UpdateState(ArmedState state) + { + switch (state) + { + case ArmedState.Hit: + case ArmedState.Miss: + this.FadeOut(100).Expire(); + break; + } + } + + public virtual bool OnPressed(ManiaAction action) + { + if (action != Action) + return false; + + return UpdateJudgement(true); + } + + public virtual bool OnReleased(ManiaAction action) => false; + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index d2e6547be4..17644a78a5 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -1,132 +1,132 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Caching; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces -{ - /// - /// Represents length-wise portion of a hold note. - /// - internal class BodyPiece : Container, IHasAccentColour - { - private readonly Container subtractionLayer; - - private readonly Drawable background; - private readonly BufferedContainer foreground; - private readonly BufferedContainer subtractionContainer; - - public BodyPiece() - { - Blending = BlendingMode.Additive; - - Children = new[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - foreground = new BufferedContainer - { - RelativeSizeAxes = Axes.Both, - CacheDrawnFrameBuffer = true, - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both }, - subtractionContainer = new BufferedContainer - { - RelativeSizeAxes = Axes.Both, - // This is needed because we're blending with another object - BackgroundColour = Color4.White.Opacity(0), - CacheDrawnFrameBuffer = true, - // The 'hole' is achieved by subtracting the result of this container with the parent - Blending = new BlendingParameters { AlphaEquation = BlendingEquation.ReverseSubtract }, - Child = subtractionLayer = new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - // Height computed in Update - Width = 1, - Masking = true, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - } - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateAccentColour(); - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - if (accentColour == value) - return; - accentColour = value; - - updateAccentColour(); - } - } - - private Cached subtractionCache = new Cached(); - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - subtractionCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - - protected override void Update() - { - base.Update(); - - if (!subtractionCache.IsValid) - { - subtractionLayer.Width = 5; - subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth); - subtractionLayer.EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.White, - Type = EdgeEffectType.Glow, - Radius = DrawWidth - }; - - foreground.ForceRedraw(); - subtractionContainer.ForceRedraw(); - - subtractionCache.Validate(); - } - } - - private void updateAccentColour() - { - if (!IsLoaded) - return; - - foreground.Colour = AccentColour.Opacity(0.4f); - background.Colour = AccentColour.Opacity(0.2f); - - subtractionCache.Invalidate(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Caching; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces +{ + /// + /// Represents length-wise portion of a hold note. + /// + internal class BodyPiece : Container, IHasAccentColour + { + private readonly Container subtractionLayer; + + private readonly Drawable background; + private readonly BufferedContainer foreground; + private readonly BufferedContainer subtractionContainer; + + public BodyPiece() + { + Blending = BlendingMode.Additive; + + Children = new[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + foreground = new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + CacheDrawnFrameBuffer = true, + Children = new Drawable[] + { + new Box { RelativeSizeAxes = Axes.Both }, + subtractionContainer = new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + // This is needed because we're blending with another object + BackgroundColour = Color4.White.Opacity(0), + CacheDrawnFrameBuffer = true, + // The 'hole' is achieved by subtracting the result of this container with the parent + Blending = new BlendingParameters { AlphaEquation = BlendingEquation.ReverseSubtract }, + Child = subtractionLayer = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + // Height computed in Update + Width = 1, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateAccentColour(); + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + if (accentColour == value) + return; + accentColour = value; + + updateAccentColour(); + } + } + + private Cached subtractionCache = new Cached(); + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.DrawSize) > 0) + subtractionCache.Invalidate(); + + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + base.Update(); + + if (!subtractionCache.IsValid) + { + subtractionLayer.Width = 5; + subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth); + subtractionLayer.EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.White, + Type = EdgeEffectType.Glow, + Radius = DrawWidth + }; + + foreground.ForceRedraw(); + subtractionContainer.ForceRedraw(); + + subtractionCache.Validate(); + } + } + + private void updateAccentColour() + { + if (!IsLoaded) + return; + + foreground.Colour = AccentColour.Opacity(0.4f); + background.Colour = AccentColour.Opacity(0.2f); + + subtractionCache.Invalidate(); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs index 5f4a4a6ace..68c9ce07d4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs @@ -1,65 +1,65 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces -{ - public class GlowPiece : CompositeDrawable, IHasAccentColour - { - private const float glow_alpha = 0.7f; - private const float glow_radius = 5; - - public GlowPiece() - { - RelativeSizeAxes = Axes.Both; - Masking = true; - - InternalChild = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateGlow(); - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - if (accentColour == value) - return; - accentColour = value; - - updateGlow(); - } - } - - private void updateGlow() - { - if (!IsLoaded) - return; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = AccentColour.Opacity(glow_alpha), - Radius = glow_radius, - Hollow = true - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces +{ + public class GlowPiece : CompositeDrawable, IHasAccentColour + { + private const float glow_alpha = 0.7f; + private const float glow_radius = 5; + + public GlowPiece() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateGlow(); + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + if (accentColour == value) + return; + accentColour = value; + + updateGlow(); + } + } + + private void updateGlow() + { + if (!IsLoaded) + return; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Opacity(glow_alpha), + Radius = glow_radius, + Hollow = true + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs index ee83bc93d0..445abc28e2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs @@ -1,85 +1,85 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces -{ - public class LaneGlowPiece : CompositeDrawable, IHasAccentColour - { - private const float total_height = 100; - private const float glow_height = 50; - private const float glow_alpha = 0.4f; - private const float edge_alpha = 0.3f; - - public LaneGlowPiece() - { - BypassAutoSizeAxes = Axes.Both; - RelativeSizeAxes = Axes.X; - Height = total_height; - - InternalChildren = new[] - { - new Container - { - Name = "Left edge", - RelativeSizeAxes = Axes.Y, - Width = 1, - Children = createGradient(edge_alpha) - }, - new Container - { - Name = "Right edge", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 1, - Children = createGradient(edge_alpha) - }, - new Container - { - Name = "Glow", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = glow_height, - Children = createGradient(glow_alpha) - } - }; - } - - private Drawable[] createGradient(float alpha) => new Drawable[] - { - new Box - { - Name = "Top", - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Blending = BlendingMode.Additive, - Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha)) - }, - new Box - { - Name = "Bottom", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Blending = BlendingMode.Additive, - Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent) - } - }; - - public Color4 AccentColour - { - get { return Colour; } - set { Colour = value; } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces +{ + public class LaneGlowPiece : CompositeDrawable, IHasAccentColour + { + private const float total_height = 100; + private const float glow_height = 50; + private const float glow_alpha = 0.4f; + private const float edge_alpha = 0.3f; + + public LaneGlowPiece() + { + BypassAutoSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + Height = total_height; + + InternalChildren = new[] + { + new Container + { + Name = "Left edge", + RelativeSizeAxes = Axes.Y, + Width = 1, + Children = createGradient(edge_alpha) + }, + new Container + { + Name = "Right edge", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 1, + Children = createGradient(edge_alpha) + }, + new Container + { + Name = "Glow", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = glow_height, + Children = createGradient(glow_alpha) + } + }; + } + + private Drawable[] createGradient(float alpha) => new Drawable[] + { + new Box + { + Name = "Top", + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Blending = BlendingMode.Additive, + Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha)) + }, + new Box + { + Name = "Bottom", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Blending = BlendingMode.Additive, + Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent) + } + }; + + public Color4 AccentColour + { + get { return Colour; } + set { Colour = value; } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs index e8a3377689..e23b24508b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs @@ -1,59 +1,59 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces -{ - /// - /// Represents the static hit markers of notes. - /// - internal class NotePiece : Container, IHasAccentColour - { - private const float head_height = 10; - private const float head_colour_height = 6; - - private readonly Box colouredBox; - - public NotePiece() - { - RelativeSizeAxes = Axes.X; - Height = head_height; - - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both - }, - colouredBox = new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = head_colour_height, - Alpha = 0.2f - } - }; - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - if (accentColour == value) - return; - accentColour = value; - - colouredBox.Colour = AccentColour.Lighten(0.9f); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces +{ + /// + /// Represents the static hit markers of notes. + /// + internal class NotePiece : Container, IHasAccentColour + { + private const float head_height = 10; + private const float head_colour_height = 6; + + private readonly Box colouredBox; + + public NotePiece() + { + RelativeSizeAxes = Axes.X; + Height = head_height; + + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both + }, + colouredBox = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = head_colour_height, + Alpha = 0.2f + } + }; + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + if (accentColour == value) + return; + accentColour = value; + + colouredBox.Colour = AccentColour.Lighten(0.9f); + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 728c79b9cf..4cf22ccd39 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -1,117 +1,117 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Mania.Objects -{ - /// - /// Represents a hit object which requires pressing, holding, and releasing a key. - /// - public class HoldNote : ManiaHitObject, IHasEndTime - { - public double EndTime => StartTime + Duration; - - private double duration; - public double Duration - { - get { return duration; } - set - { - duration = value; - Tail.StartTime = EndTime; - } - } - - public override double StartTime - { - get { return base.StartTime; } - set - { - base.StartTime = value; - Head.StartTime = value; - Tail.StartTime = EndTime; - } - } - - public override int Column - { - get { return base.Column; } - set - { - base.Column = value; - Head.Column = value; - Tail.Column = value; - } - } - - /// - /// The head note of the hold. - /// - public readonly Note Head = new Note(); - - /// - /// The tail note of the hold. - /// - public readonly Note Tail = new TailNote(); - - /// - /// The time between ticks of this hold. - /// - private double tickSpacing = 50; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; - - Head.ApplyDefaults(controlPointInfo, difficulty); - Tail.ApplyDefaults(controlPointInfo, difficulty); - } - - protected override void CreateNestedHitObjects() - { - base.CreateNestedHitObjects(); - - createTicks(); - } - - private void createTicks() - { - if (tickSpacing == 0) - return; - - for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) - { - AddNested(new HoldNoteTick - { - StartTime = t, - Column = Column - }); - } - } - - /// - /// The tail of the hold note. - /// - private class TailNote : Note - { - /// - /// Lenience of release hit windows. This is to make cases where the hold note release - /// is timed alongside presses of other hit objects less awkward. - /// - private const double release_window_lenience = 1.5; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - HitWindows *= release_window_lenience; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Mania.Objects +{ + /// + /// Represents a hit object which requires pressing, holding, and releasing a key. + /// + public class HoldNote : ManiaHitObject, IHasEndTime + { + public double EndTime => StartTime + Duration; + + private double duration; + public double Duration + { + get { return duration; } + set + { + duration = value; + Tail.StartTime = EndTime; + } + } + + public override double StartTime + { + get { return base.StartTime; } + set + { + base.StartTime = value; + Head.StartTime = value; + Tail.StartTime = EndTime; + } + } + + public override int Column + { + get { return base.Column; } + set + { + base.Column = value; + Head.Column = value; + Tail.Column = value; + } + } + + /// + /// The head note of the hold. + /// + public readonly Note Head = new Note(); + + /// + /// The tail note of the hold. + /// + public readonly Note Tail = new TailNote(); + + /// + /// The time between ticks of this hold. + /// + private double tickSpacing = 50; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; + + Head.ApplyDefaults(controlPointInfo, difficulty); + Tail.ApplyDefaults(controlPointInfo, difficulty); + } + + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + + createTicks(); + } + + private void createTicks() + { + if (tickSpacing == 0) + return; + + for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) + { + AddNested(new HoldNoteTick + { + StartTime = t, + Column = Column + }); + } + } + + /// + /// The tail of the hold note. + /// + private class TailNote : Note + { + /// + /// Lenience of release hit windows. This is to make cases where the hold note release + /// is timed alongside presses of other hit objects less awkward. + /// + private const double release_window_lenience = 1.5; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + HitWindows *= release_window_lenience; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs index ec502a803d..d078c15c92 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Objects -{ - /// - /// A scoring tick of a hold note. - /// - public class HoldNoteTick : ManiaHitObject - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Objects +{ + /// + /// A scoring tick of a hold note. + /// + public class HoldNoteTick : ManiaHitObject + { + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index be93471bcd..4f0e02ff0d 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Objects.Types; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mania.Objects -{ - public abstract class ManiaHitObject : HitObject, IHasColumn - { - public virtual int Column { get; set; } - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - HitWindows.AllowsPerfect = true; - HitWindows.AllowsOk = true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Objects.Types; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Objects +{ + public abstract class ManiaHitObject : HitObject, IHasColumn + { + public virtual int Column { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + HitWindows.AllowsPerfect = true; + HitWindows.AllowsOk = true; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs index 2b59279972..390d2b0218 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs @@ -1,113 +1,113 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using System; - -namespace osu.Game.Rulesets.Mania.Objects -{ - internal class ManiaHitObjectDifficulty - { - /// - /// Factor by how much individual / overall strain decays per second. - /// - /// - /// These values are results of tweaking a lot and taking into account general feedback. - /// - internal const double INDIVIDUAL_DECAY_BASE = 0.125; - internal const double OVERALL_DECAY_BASE = 0.30; - - internal ManiaHitObject BaseHitObject; - - private readonly int beatmapColumnCount; - - - private readonly double endTime; - private readonly double[] heldUntil; - - /// - /// Measures jacks or more generally: repeated presses of the same button - /// - private readonly double[] individualStrains; - - internal double IndividualStrain - { - get - { - return individualStrains[BaseHitObject.Column]; - } - - set - { - individualStrains[BaseHitObject.Column] = value; - } - } - - /// - /// Measures note density in a way - /// - internal double OverallStrain = 1; - - public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount) - { - BaseHitObject = baseHitObject; - - endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime; - - beatmapColumnCount = columnCount; - heldUntil = new double[beatmapColumnCount]; - individualStrains = new double[beatmapColumnCount]; - - for (int i = 0; i < beatmapColumnCount; ++i) - { - individualStrains[i] = 0; - heldUntil[i] = 0; - } - } - - internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate) - { - // TODO: Factor in holds - double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000); - double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000); - - double holdFactor = 1.0; // Factor to all additional strains in case something else is held - double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly - - // Fill up the heldUntil array - for (int i = 0; i < beatmapColumnCount; ++i) - { - heldUntil[i] = previousHitObject.heldUntil[i]; - - // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... - if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i]) - { - holdAddition = 1.0; - } - - // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 - if (endTime == heldUntil[i]) - { - holdAddition = 0; - } - - // We give a slight bonus to everything if something is held meanwhile - if (heldUntil[i] > endTime) - { - holdFactor = 1.25; - } - - // Decay individual strains - individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay; - } - - heldUntil[BaseHitObject.Column] = endTime; - - // Increase individual strain in own column - IndividualStrain += 2.0 * holdFactor; - - OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using System; + +namespace osu.Game.Rulesets.Mania.Objects +{ + internal class ManiaHitObjectDifficulty + { + /// + /// Factor by how much individual / overall strain decays per second. + /// + /// + /// These values are results of tweaking a lot and taking into account general feedback. + /// + internal const double INDIVIDUAL_DECAY_BASE = 0.125; + internal const double OVERALL_DECAY_BASE = 0.30; + + internal ManiaHitObject BaseHitObject; + + private readonly int beatmapColumnCount; + + + private readonly double endTime; + private readonly double[] heldUntil; + + /// + /// Measures jacks or more generally: repeated presses of the same button + /// + private readonly double[] individualStrains; + + internal double IndividualStrain + { + get + { + return individualStrains[BaseHitObject.Column]; + } + + set + { + individualStrains[BaseHitObject.Column] = value; + } + } + + /// + /// Measures note density in a way + /// + internal double OverallStrain = 1; + + public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount) + { + BaseHitObject = baseHitObject; + + endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime; + + beatmapColumnCount = columnCount; + heldUntil = new double[beatmapColumnCount]; + individualStrains = new double[beatmapColumnCount]; + + for (int i = 0; i < beatmapColumnCount; ++i) + { + individualStrains[i] = 0; + heldUntil[i] = 0; + } + } + + internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate) + { + // TODO: Factor in holds + double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; + double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000); + double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000); + + double holdFactor = 1.0; // Factor to all additional strains in case something else is held + double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly + + // Fill up the heldUntil array + for (int i = 0; i < beatmapColumnCount; ++i) + { + heldUntil[i] = previousHitObject.heldUntil[i]; + + // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... + if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i]) + { + holdAddition = 1.0; + } + + // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 + if (endTime == heldUntil[i]) + { + holdAddition = 0; + } + + // We give a slight bonus to everything if something is held meanwhile + if (heldUntil[i] > endTime) + { + holdFactor = 1.25; + } + + // Decay individual strains + individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay; + } + + heldUntil[BaseHitObject.Column] = endTime; + + // Increase individual strain in own column + IndividualStrain += 2.0 * holdFactor; + + OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 438116b363..975188e550 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Objects -{ - /// - /// Represents a hit object which has a single hit press. - /// - public class Note : ManiaHitObject - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Objects +{ + /// + /// Represents a hit object which has a single hit press. + /// + public class Note : ManiaHitObject + { + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs b/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs index cdff2b0827..44a79de7db 100644 --- a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs +++ b/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Objects.Types -{ - /// - /// A type of hit object which lies in one of a number of predetermined columns. - /// - public interface IHasColumn - { - /// - /// The column which the hit object lies in. - /// - int Column { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Objects.Types +{ + /// + /// A type of hit object which lies in one of a number of predetermined columns. + /// + public interface IHasColumn + { + /// + /// The column which the hit object lies in. + /// + int Column { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs index 515aeab9df..76ccfe324b 100644 --- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Runtime.CompilerServices; - -// We publish our internal attributes to other sub-projects of the framework. -// Note, that we omit visual tests as they are meant to test the framework -// behavior "in the wild". - -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")] -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")] +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Runtime.CompilerServices; + +// We publish our internal attributes to other sub-projects of the framework. +// Note, that we omit visual tests as they are meant to test the framework +// behavior "in the wild". + +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")] diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 5a992bb970..d006a3e1c7 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -1,98 +1,98 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Mania.Replays -{ - internal class ManiaAutoGenerator : AutoGenerator - { - public const double RELEASE_DELAY = 20; - - public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - - private readonly ManiaAction[] columnActions; - - public ManiaAutoGenerator(ManiaBeatmap beatmap) - : base(beatmap) - { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; - - columnActions = new ManiaAction[Beatmap.TotalColumns]; - - var normalAction = ManiaAction.Key1; - var specialAction = ManiaAction.Special1; - int totalCounter = 0; - foreach (var stage in Beatmap.Stages) - { - for (int i = 0; i < stage.Columns; i++) - { - if (stage.IsSpecialColumn(i)) - columnActions[totalCounter] = specialAction++; - else - columnActions[totalCounter] = normalAction++; - totalCounter++; - } - } - } - - protected Replay Replay; - - public override Replay Generate() - { - // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled - Replay.Frames.Add(new ManiaReplayFrame(-100000, 0)); - - var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); - - var actions = new List(); - foreach (var group in pointGroups) - { - foreach (var point in group) - { - if (point is HitPoint) - actions.Add(columnActions[point.Column]); - if (point is ReleasePoint) - actions.Remove(columnActions[point.Column]); - } - - Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); - } - - return Replay; - } - - private IEnumerable generateActionPoints() - { - foreach (var obj in Beatmap.HitObjects) - { - yield return new HitPoint { Time = obj.StartTime, Column = obj.Column }; - yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column }; - } - } - - private interface IActionPoint - { - double Time { get; set; } - int Column { get; set; } - } - - private struct HitPoint : IActionPoint - { - public double Time { get; set; } - public int Column { get; set; } - } - - private struct ReleasePoint : IActionPoint - { - public double Time { get; set; } - public int Column { get; set; } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Replays; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Mania.Replays +{ + internal class ManiaAutoGenerator : AutoGenerator + { + public const double RELEASE_DELAY = 20; + + public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; + + private readonly ManiaAction[] columnActions; + + public ManiaAutoGenerator(ManiaBeatmap beatmap) + : base(beatmap) + { + Replay = new Replay { User = new User { Username = @"Autoplay" } }; + + columnActions = new ManiaAction[Beatmap.TotalColumns]; + + var normalAction = ManiaAction.Key1; + var specialAction = ManiaAction.Special1; + int totalCounter = 0; + foreach (var stage in Beatmap.Stages) + { + for (int i = 0; i < stage.Columns; i++) + { + if (stage.IsSpecialColumn(i)) + columnActions[totalCounter] = specialAction++; + else + columnActions[totalCounter] = normalAction++; + totalCounter++; + } + } + } + + protected Replay Replay; + + public override Replay Generate() + { + // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled + Replay.Frames.Add(new ManiaReplayFrame(-100000, 0)); + + var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); + + var actions = new List(); + foreach (var group in pointGroups) + { + foreach (var point in group) + { + if (point is HitPoint) + actions.Add(columnActions[point.Column]); + if (point is ReleasePoint) + actions.Remove(columnActions[point.Column]); + } + + Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); + } + + return Replay; + } + + private IEnumerable generateActionPoints() + { + foreach (var obj in Beatmap.HitObjects) + { + yield return new HitPoint { Time = obj.StartTime, Column = obj.Column }; + yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column }; + } + } + + private interface IActionPoint + { + double Time { get; set; } + int Column { get; set; } + } + + private struct HitPoint : IActionPoint + { + public double Time { get; set; } + public int Column { get; set; } + } + + private struct ReleasePoint : IActionPoint + { + public double Time { get; set; } + public int Column { get; set; } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index 3541561418..c71db745e0 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Input; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Mania.Replays -{ - internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler - { - public ManiaFramedReplayInputHandler(Replay replay) - : base(replay) - { - } - - protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - - public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Mania.Replays +{ + internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler + { + public ManiaFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); + + public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + } +} diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 9990f89b99..8d86325dd9 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -1,59 +1,59 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Rulesets.Replays.Types; - -namespace osu.Game.Rulesets.Mania.Replays -{ - public class ManiaReplayFrame : ReplayFrame, IConvertibleReplayFrame - { - public List Actions = new List(); - - public ManiaReplayFrame() - { - } - - public ManiaReplayFrame(double time, params ManiaAction[] actions) - : base(time) - { - Actions.AddRange(actions); - } - - public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) - { - // We don't need to fully convert, just create the converter - var converter = new ManiaBeatmapConverter(beatmap.BeatmapInfo.RulesetID == 3, beatmap); - - // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling - // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage. - - var stage = new StageDefinition { Columns = converter.TargetColumns }; - - var normalAction = ManiaAction.Key1; - var specialAction = ManiaAction.Special1; - - int activeColumns = (int)(legacyFrame.MouseX ?? 0); - int counter = 0; - while (activeColumns > 0) - { - var isSpecial = stage.IsSpecialColumn(counter); - - if ((activeColumns & 1) > 0) - Actions.Add(isSpecial ? specialAction : normalAction); - - if (isSpecial) - specialAction++; - else - normalAction++; - - counter++; - activeColumns >>= 1; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Rulesets.Replays.Types; + +namespace osu.Game.Rulesets.Mania.Replays +{ + public class ManiaReplayFrame : ReplayFrame, IConvertibleReplayFrame + { + public List Actions = new List(); + + public ManiaReplayFrame() + { + } + + public ManiaReplayFrame(double time, params ManiaAction[] actions) + : base(time) + { + Actions.AddRange(actions); + } + + public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) + { + // We don't need to fully convert, just create the converter + var converter = new ManiaBeatmapConverter(beatmap.BeatmapInfo.RulesetID == 3, beatmap); + + // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling + // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage. + + var stage = new StageDefinition { Columns = converter.TargetColumns }; + + var normalAction = ManiaAction.Key1; + var specialAction = ManiaAction.Special1; + + int activeColumns = (int)(legacyFrame.MouseX ?? 0); + int counter = 0; + while (activeColumns > 0) + { + var isSpecial = stage.IsSpecialColumn(counter); + + if ((activeColumns & 1) > 0) + Actions.Add(isSpecial ? specialAction : normalAction); + + if (isSpecial) + specialAction++; + else + normalAction++; + + counter++; + activeColumns >>= 1; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index c3b840b9f0..4c955a680e 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -1,173 +1,173 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mania.Scoring -{ - internal class ManiaScoreProcessor : ScoreProcessor - { - /// - /// The hit HP multiplier at OD = 0. - /// - private const double hp_multiplier_min = 0.75; - - /// - /// The hit HP multiplier at OD = 0. - /// - private const double hp_multiplier_mid = 0.85; - - /// - /// The hit HP multiplier at OD = 0. - /// - private const double hp_multiplier_max = 1; - - /// - /// The default BAD hit HP increase. - /// - private const double hp_increase_bad = 0.005; - - /// - /// The default OK hit HP increase. - /// - private const double hp_increase_ok = 0.010; - - /// - /// The default GOOD hit HP increase. - /// - private const double hp_increase_good = 0.035; - - /// - /// The default tick hit HP increase. - /// - private const double hp_increase_tick = 0.040; - - /// - /// The default GREAT hit HP increase. - /// - private const double hp_increase_great = 0.055; - - /// - /// The default PERFECT hit HP increase. - /// - private const double hp_increase_perfect = 0.065; - - /// - /// The MISS HP multiplier at OD = 0. - /// - private const double hp_multiplier_miss_min = 0.5; - - /// - /// The MISS HP multiplier at OD = 5. - /// - private const double hp_multiplier_miss_mid = 0.75; - - /// - /// The MISS HP multiplier at OD = 10. - /// - private const double hp_multiplier_miss_max = 1; - - /// - /// The default MISS HP increase. - /// - private const double hp_increase_miss = -0.125; - - /// - /// The MISS HP multiplier. This is multiplied to the miss hp increase. - /// - private double hpMissMultiplier = 1; - - /// - /// The HIT HP multiplier. This is multiplied to hit hp increases. - /// - private double hpMultiplier = 1; - - public ManiaScoreProcessor() - { - } - - public ManiaScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) - { - } - - protected override void SimulateAutoplay(Beatmap beatmap) - { - BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty; - hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max); - hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max); - - while (true) - { - foreach (var obj in beatmap.HitObjects) - { - var holdNote = obj as HoldNote; - - if (holdNote != null) - { - // Head - AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); - - // Ticks - int tickCount = holdNote.NestedHitObjects.OfType().Count(); - for (int i = 0; i < tickCount; i++) - AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); - } - - AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); - } - - if (!HasFailed) - break; - - hpMultiplier *= 1.01; - hpMissMultiplier *= 0.98; - - Reset(false); - } - } - - protected override void OnNewJudgement(Judgement judgement) - { - base.OnNewJudgement(judgement); - - bool isTick = judgement is HoldNoteTickJudgement; - - if (isTick) - { - if (judgement.IsHit) - Health.Value += hpMultiplier * hp_increase_tick; - } - else - { - switch (judgement.Result) - { - case HitResult.Miss: - Health.Value += hpMissMultiplier * hp_increase_miss; - break; - case HitResult.Meh: - Health.Value += hpMultiplier * hp_increase_bad; - break; - case HitResult.Ok: - Health.Value += hpMultiplier * hp_increase_ok; - break; - case HitResult.Good: - Health.Value += hpMultiplier * hp_increase_good; - break; - case HitResult.Great: - Health.Value += hpMultiplier * hp_increase_great; - break; - case HitResult.Perfect: - Health.Value += hpMultiplier * hp_increase_perfect; - break; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.Scoring +{ + internal class ManiaScoreProcessor : ScoreProcessor + { + /// + /// The hit HP multiplier at OD = 0. + /// + private const double hp_multiplier_min = 0.75; + + /// + /// The hit HP multiplier at OD = 0. + /// + private const double hp_multiplier_mid = 0.85; + + /// + /// The hit HP multiplier at OD = 0. + /// + private const double hp_multiplier_max = 1; + + /// + /// The default BAD hit HP increase. + /// + private const double hp_increase_bad = 0.005; + + /// + /// The default OK hit HP increase. + /// + private const double hp_increase_ok = 0.010; + + /// + /// The default GOOD hit HP increase. + /// + private const double hp_increase_good = 0.035; + + /// + /// The default tick hit HP increase. + /// + private const double hp_increase_tick = 0.040; + + /// + /// The default GREAT hit HP increase. + /// + private const double hp_increase_great = 0.055; + + /// + /// The default PERFECT hit HP increase. + /// + private const double hp_increase_perfect = 0.065; + + /// + /// The MISS HP multiplier at OD = 0. + /// + private const double hp_multiplier_miss_min = 0.5; + + /// + /// The MISS HP multiplier at OD = 5. + /// + private const double hp_multiplier_miss_mid = 0.75; + + /// + /// The MISS HP multiplier at OD = 10. + /// + private const double hp_multiplier_miss_max = 1; + + /// + /// The default MISS HP increase. + /// + private const double hp_increase_miss = -0.125; + + /// + /// The MISS HP multiplier. This is multiplied to the miss hp increase. + /// + private double hpMissMultiplier = 1; + + /// + /// The HIT HP multiplier. This is multiplied to hit hp increases. + /// + private double hpMultiplier = 1; + + public ManiaScoreProcessor() + { + } + + public ManiaScoreProcessor(RulesetContainer rulesetContainer) + : base(rulesetContainer) + { + } + + protected override void SimulateAutoplay(Beatmap beatmap) + { + BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty; + hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max); + hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max); + + while (true) + { + foreach (var obj in beatmap.HitObjects) + { + var holdNote = obj as HoldNote; + + if (holdNote != null) + { + // Head + AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); + + // Ticks + int tickCount = holdNote.NestedHitObjects.OfType().Count(); + for (int i = 0; i < tickCount; i++) + AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); + } + + AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); + } + + if (!HasFailed) + break; + + hpMultiplier *= 1.01; + hpMissMultiplier *= 0.98; + + Reset(false); + } + } + + protected override void OnNewJudgement(Judgement judgement) + { + base.OnNewJudgement(judgement); + + bool isTick = judgement is HoldNoteTickJudgement; + + if (isTick) + { + if (judgement.IsHit) + Health.Value += hpMultiplier * hp_increase_tick; + } + else + { + switch (judgement.Result) + { + case HitResult.Miss: + Health.Value += hpMissMultiplier * hp_increase_miss; + break; + case HitResult.Meh: + Health.Value += hpMultiplier * hp_increase_bad; + break; + case HitResult.Ok: + Health.Value += hpMultiplier * hp_increase_ok; + break; + case HitResult.Good: + Health.Value += hpMultiplier * hp_increase_good; + break; + case HitResult.Great: + Health.Value += hpMultiplier * hp_increase_great; + break; + case HitResult.Perfect: + Health.Value += hpMultiplier * hp_increase_perfect; + break; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 15c9a83b78..52d514221d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -1,276 +1,276 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Colour; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using System; -using System.Linq; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Mania.UI -{ - public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour - { - private const float key_icon_size = 10; - private const float key_icon_corner_radius = 3; - private const float key_icon_border_radius = 2; - - private const float hit_target_height = 10; - private const float hit_target_bar_height = 2; - - private const float column_width = 45; - private const float special_column_width = 70; - - public ManiaAction Action; - - private readonly Box background; - private readonly Container hitTargetBar; - private readonly Container keyIcon; - - internal readonly Container TopLevelContainer; - private readonly Container explosionContainer; - - protected override Container Content => content; - private readonly Container content; - - private const float opacity_released = 0.1f; - private const float opacity_pressed = 0.25f; - - public Column() - : base(ScrollingDirection.Up) - { - RelativeSizeAxes = Axes.Y; - Width = column_width; - - InternalChildren = new Drawable[] - { - background = new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Alpha = opacity_released - }, - new Container - { - Name = "Hit target + hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION }, - Children = new Drawable[] - { - new Container - { - Name = "Hit target", - RelativeSizeAxes = Axes.X, - Height = hit_target_height, - Children = new Drawable[] - { - new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - hitTargetBar = new Container - { - Name = "Bar", - RelativeSizeAxes = Axes.X, - Height = hit_target_bar_height, - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both - } - } - } - } - }, - content = new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - }, - // For column lighting, we need to capture input events before the notes - new InputTarget - { - Pressed = onPressed, - Released = onReleased - }, - explosionContainer = new Container - { - Name = "Hit explosions", - RelativeSizeAxes = Axes.Both - } - } - }, - new Container - { - Name = "Key", - RelativeSizeAxes = Axes.X, - Height = ManiaStage.HIT_TARGET_POSITION, - Children = new Drawable[] - { - new Box - { - Name = "Key gradient", - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)), - Alpha = 0.5f - }, - keyIcon = new Container - { - Name = "Key icon", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(key_icon_size), - Masking = true, - CornerRadius = key_icon_corner_radius, - BorderThickness = 2, - BorderColour = Color4.White, // Not true - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - } - } - }, - TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } - }; - - TopLevelContainer.Add(explosionContainer.CreateProxy()); - } - - public override Axes RelativeSizeAxes => Axes.Y; - - private bool isSpecial; - public bool IsSpecial - { - get { return isSpecial; } - set - { - if (isSpecial == value) - return; - isSpecial = value; - - Width = isSpecial ? special_column_width : column_width; - } - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - if (accentColour == value) - return; - accentColour = value; - - background.Colour = accentColour; - - hitTargetBar.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = accentColour.Opacity(0.5f), - }; - - keyIcon.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = accentColour.Opacity(0.5f), - }; - } - } - - /// - /// Adds a DrawableHitObject to this Playfield. - /// - /// The DrawableHitObject to add. - public override void Add(DrawableHitObject hitObject) - { - hitObject.AccentColour = AccentColour; - hitObject.OnJudgement += OnJudgement; - - HitObjects.Add(hitObject); - } - - internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) - { - if (!judgement.IsHit) - return; - - explosionContainer.Add(new HitExplosion(judgedObject)); - } - - private bool onPressed(ManiaAction action) - { - if (action == Action) - { - background.FadeTo(opacity_pressed, 50, Easing.OutQuint); - keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint); - } - - return false; - } - - private bool onReleased(ManiaAction action) - { - if (action == Action) - { - background.FadeTo(opacity_released, 800, Easing.OutQuart); - keyIcon.ScaleTo(1f, 400, Easing.OutQuart); - } - - return false; - } - - /// - /// This is a simple container which delegates various input events that have to be captured before the notes. - /// - private class InputTarget : Container, IKeyBindingHandler - { - public Func Pressed; - public Func Released; - - public InputTarget() - { - RelativeSizeAxes = Axes.Both; - AlwaysPresent = true; - Alpha = 0; - } - - public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false; - public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false; - } - - public bool OnPressed(ManiaAction action) - { - if (action != Action) - return false; - - var hitObject = HitObjects.Objects.LastOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.FirstOrDefault(); - hitObject?.PlaySamples(); - - return true; - } - - public bool OnReleased(ManiaAction action) => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using System; +using System.Linq; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour + { + private const float key_icon_size = 10; + private const float key_icon_corner_radius = 3; + private const float key_icon_border_radius = 2; + + private const float hit_target_height = 10; + private const float hit_target_bar_height = 2; + + private const float column_width = 45; + private const float special_column_width = 70; + + public ManiaAction Action; + + private readonly Box background; + private readonly Container hitTargetBar; + private readonly Container keyIcon; + + internal readonly Container TopLevelContainer; + private readonly Container explosionContainer; + + protected override Container Content => content; + private readonly Container content; + + private const float opacity_released = 0.1f; + private const float opacity_pressed = 0.25f; + + public Column() + : base(ScrollingDirection.Up) + { + RelativeSizeAxes = Axes.Y; + Width = column_width; + + InternalChildren = new Drawable[] + { + background = new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Alpha = opacity_released + }, + new Container + { + Name = "Hit target + hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION }, + Children = new Drawable[] + { + new Container + { + Name = "Hit target", + RelativeSizeAxes = Axes.X, + Height = hit_target_height, + Children = new Drawable[] + { + new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + hitTargetBar = new Container + { + Name = "Bar", + RelativeSizeAxes = Axes.X, + Height = hit_target_bar_height, + Masking = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both + } + } + } + } + }, + content = new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + }, + // For column lighting, we need to capture input events before the notes + new InputTarget + { + Pressed = onPressed, + Released = onReleased + }, + explosionContainer = new Container + { + Name = "Hit explosions", + RelativeSizeAxes = Axes.Both + } + } + }, + new Container + { + Name = "Key", + RelativeSizeAxes = Axes.X, + Height = ManiaStage.HIT_TARGET_POSITION, + Children = new Drawable[] + { + new Box + { + Name = "Key gradient", + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)), + Alpha = 0.5f + }, + keyIcon = new Container + { + Name = "Key icon", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(key_icon_size), + Masking = true, + CornerRadius = key_icon_corner_radius, + BorderThickness = 2, + BorderColour = Color4.White, // Not true + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + } + }, + TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } + }; + + TopLevelContainer.Add(explosionContainer.CreateProxy()); + } + + public override Axes RelativeSizeAxes => Axes.Y; + + private bool isSpecial; + public bool IsSpecial + { + get { return isSpecial; } + set + { + if (isSpecial == value) + return; + isSpecial = value; + + Width = isSpecial ? special_column_width : column_width; + } + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + if (accentColour == value) + return; + accentColour = value; + + background.Colour = accentColour; + + hitTargetBar.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = accentColour.Opacity(0.5f), + }; + + keyIcon.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = accentColour.Opacity(0.5f), + }; + } + } + + /// + /// Adds a DrawableHitObject to this Playfield. + /// + /// The DrawableHitObject to add. + public override void Add(DrawableHitObject hitObject) + { + hitObject.AccentColour = AccentColour; + hitObject.OnJudgement += OnJudgement; + + HitObjects.Add(hitObject); + } + + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + if (!judgement.IsHit) + return; + + explosionContainer.Add(new HitExplosion(judgedObject)); + } + + private bool onPressed(ManiaAction action) + { + if (action == Action) + { + background.FadeTo(opacity_pressed, 50, Easing.OutQuint); + keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint); + } + + return false; + } + + private bool onReleased(ManiaAction action) + { + if (action == Action) + { + background.FadeTo(opacity_released, 800, Easing.OutQuart); + keyIcon.ScaleTo(1f, 400, Easing.OutQuart); + } + + return false; + } + + /// + /// This is a simple container which delegates various input events that have to be captured before the notes. + /// + private class InputTarget : Container, IKeyBindingHandler + { + public Func Pressed; + public Func Released; + + public InputTarget() + { + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; + } + + public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false; + public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false; + } + + public bool OnPressed(ManiaAction action) + { + if (action != Action) + return false; + + var hitObject = HitObjects.Objects.LastOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.FirstOrDefault(); + hitObject?.PlaySamples(); + + return true; + } + + public bool OnReleased(ManiaAction action) => false; + } +} diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index f50f077c76..6566d44ef5 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -1,42 +1,42 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Mania.UI -{ - internal class DrawableManiaJudgement : DrawableJudgement - { - public DrawableManiaJudgement(Judgement judgement, DrawableHitObject judgedObject) - : base(judgement, judgedObject) - { - } - - [BackgroundDependencyLoader] - private void load() - { - if (JudgementText != null) - JudgementText.TextSize = 25; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - this.FadeInFromZero(50, Easing.OutQuint); - - if (Judgement.IsHit) - { - this.ScaleTo(0.8f); - this.ScaleTo(1, 250, Easing.OutElastic); - - this.Delay(50).FadeOut(200).ScaleTo(0.75f, 250); - } - - Expire(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.UI +{ + internal class DrawableManiaJudgement : DrawableJudgement + { + public DrawableManiaJudgement(Judgement judgement, DrawableHitObject judgedObject) + : base(judgement, judgedObject) + { + } + + [BackgroundDependencyLoader] + private void load() + { + if (JudgementText != null) + JudgementText.TextSize = 25; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeInFromZero(50, Easing.OutQuint); + + if (Judgement.IsHit) + { + this.ScaleTo(0.8f); + this.ScaleTo(1, 250, Easing.OutElastic); + + this.Delay(50).FadeOut(200).ScaleTo(0.75f, 250); + } + + Expire(); + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index dcf54f0819..f01dfc0db1 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -1,66 +1,66 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Mania.UI -{ - internal class HitExplosion : CompositeDrawable - { - private readonly Box inner; - - public HitExplosion(DrawableHitObject judgedObject) - { - bool isTick = judgedObject is DrawableHoldNoteTick; - - Anchor = Anchor.TopCentre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - Size = new Vector2(isTick ? 0.5f : 1); - FillMode = FillMode.Fit; - - Blending = BlendingMode.Additive; - - Color4 accent = isTick ? Color4.White : judgedObject.AccentColour; - - InternalChild = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = 1, - BorderColour = accent, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = accent, - Radius = 10, - Hollow = true - }, - Child = inner = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = accent, - Alpha = 1, - AlwaysPresent = true, - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500); - inner.FadeOut(250); - - Expire(true); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.UI +{ + internal class HitExplosion : CompositeDrawable + { + private readonly Box inner; + + public HitExplosion(DrawableHitObject judgedObject) + { + bool isTick = judgedObject is DrawableHoldNoteTick; + + Anchor = Anchor.TopCentre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(isTick ? 0.5f : 1); + FillMode = FillMode.Fit; + + Blending = BlendingMode.Additive; + + Color4 accent = isTick ? Color4.White : judgedObject.AccentColour; + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 1, + BorderColour = accent, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = accent, + Radius = 10, + Hollow = true + }, + Child = inner = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = accent, + Alpha = 1, + AlwaysPresent = true, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500); + inner.FadeOut(250); + + Expire(true); + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index c3cbf81af1..4b936fc7f9 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -1,94 +1,94 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.Objects; -using osu.Framework.Graphics.Containers; -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Mania.UI -{ - public class ManiaPlayfield : ScrollingPlayfield - { - /// - /// Whether this playfield should be inverted. This flips everything inside the playfield. - /// - public readonly Bindable Inverted = new Bindable(true); - - public List Columns => stages.SelectMany(x => x.Columns).ToList(); - private readonly List stages = new List(); - - public ManiaPlayfield(List stageDefinitions) - : base(ScrollingDirection.Up) - { - if (stageDefinitions == null) - throw new ArgumentNullException(nameof(stageDefinitions)); - - if (stageDefinitions.Count <= 0) - throw new ArgumentException("Can't have zero or fewer stages."); - - Inverted.Value = true; - - GridContainer playfieldGrid; - InternalChild = playfieldGrid = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] { new Drawable[stageDefinitions.Count] } - }; - - var normalColumnAction = ManiaAction.Key1; - var specialColumnAction = ManiaAction.Special1; - int firstColumnIndex = 0; - for (int i = 0; i < stageDefinitions.Count; i++) - { - var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); - newStage.VisibleTimeRange.BindTo(VisibleTimeRange); - newStage.Inverted.BindTo(Inverted); - - playfieldGrid.Content[0][i] = newStage; - - stages.Add(newStage); - AddNested(newStage); - - firstColumnIndex += newStage.Columns.Count; - } - } - - public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h); - - public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); - - private ManiaStage getStageByColumn(int column) - { - int sum = 0; - foreach (var stage in stages) - { - sum = sum + stage.Columns.Count; - if (sum > column) - return stage; - } - - return null; - } - - [BackgroundDependencyLoader] - private void load(ManiaConfigManager maniaConfig) - { - maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange); - } - - internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) - { - getStageByColumn(((ManiaHitObject)judgedObject.HitObject).Column).OnJudgement(judgedObject, judgement); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Objects; +using osu.Framework.Graphics.Containers; +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaPlayfield : ScrollingPlayfield + { + /// + /// Whether this playfield should be inverted. This flips everything inside the playfield. + /// + public readonly Bindable Inverted = new Bindable(true); + + public List Columns => stages.SelectMany(x => x.Columns).ToList(); + private readonly List stages = new List(); + + public ManiaPlayfield(List stageDefinitions) + : base(ScrollingDirection.Up) + { + if (stageDefinitions == null) + throw new ArgumentNullException(nameof(stageDefinitions)); + + if (stageDefinitions.Count <= 0) + throw new ArgumentException("Can't have zero or fewer stages."); + + Inverted.Value = true; + + GridContainer playfieldGrid; + InternalChild = playfieldGrid = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { new Drawable[stageDefinitions.Count] } + }; + + var normalColumnAction = ManiaAction.Key1; + var specialColumnAction = ManiaAction.Special1; + int firstColumnIndex = 0; + for (int i = 0; i < stageDefinitions.Count; i++) + { + var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + newStage.VisibleTimeRange.BindTo(VisibleTimeRange); + newStage.Inverted.BindTo(Inverted); + + playfieldGrid.Content[0][i] = newStage; + + stages.Add(newStage); + AddNested(newStage); + + firstColumnIndex += newStage.Columns.Count; + } + } + + public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h); + + public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); + + private ManiaStage getStageByColumn(int column) + { + int sum = 0; + foreach (var stage in stages) + { + sum = sum + stage.Columns.Count; + if (sum > column) + return stage; + } + + return null; + } + + [BackgroundDependencyLoader] + private void load(ManiaConfigManager maniaConfig) + { + maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange); + } + + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + getStageByColumn(((ManiaHitObject)judgedObject.HitObject).Column).OnJudgement(judgedObject, judgement); + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 3ecfee1e8c..76afaf270f 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -1,111 +1,111 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Configuration; -using osu.Game.Input.Handlers; -using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mania.Scoring; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; - -namespace osu.Game.Rulesets.Mania.UI -{ - public class ManiaRulesetContainer : ScrollingRulesetContainer - { - public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - - public IEnumerable BarLines; - - public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(ruleset, beatmap, isForCurrentRuleset) - { - // Generate the bar lines - double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - - var timingPoints = Beatmap.ControlPointInfo.TimingPoints; - var barLines = new List(); - - for (int i = 0; i < timingPoints.Count; i++) - { - TimingControlPoint point = timingPoints[i]; - - // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object - double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature; - - int index = 0; - for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) - { - barLines.Add(new BarLine - { - StartTime = t, - ControlPoint = point, - BeatIndex = index - }); - } - } - - BarLines = barLines; - } - - [BackgroundDependencyLoader] - private void load() - { - BarLines.ForEach(Playfield.Add); - } - - protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - - public override int Variant => (int)(Mods.OfType().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single) + Beatmap.TotalColumns; - - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - - protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap); - - protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) - { - ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action; - - var holdNote = h as HoldNote; - if (holdNote != null) - return new DrawableHoldNote(holdNote, action); - - var note = h as Note; - if (note != null) - return new DrawableNote(note, action); - - return null; - } - - protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); - - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); - - protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Input.Handlers; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaRulesetContainer : ScrollingRulesetContainer + { + public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; + + public IEnumerable BarLines; + + public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + // Generate the bar lines + double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; + + var timingPoints = Beatmap.ControlPointInfo.TimingPoints; + var barLines = new List(); + + for (int i = 0; i < timingPoints.Count; i++) + { + TimingControlPoint point = timingPoints[i]; + + // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature; + + int index = 0; + for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) + { + barLines.Add(new BarLine + { + StartTime = t, + ControlPoint = point, + BeatIndex = index + }); + } + } + + BarLines = barLines; + } + + [BackgroundDependencyLoader] + private void load() + { + BarLines.ForEach(Playfield.Add); + } + + protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); + + public override int Variant => (int)(Mods.OfType().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single) + Beatmap.TotalColumns; + + public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); + + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap); + + protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) + { + ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action; + + var holdNote = h as HoldNote; + if (holdNote != null) + return new DrawableHoldNote(holdNote, action); + + var note = h as Note; + if (note != null) + return new DrawableNote(note, action); + + return null; + } + + protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); + + protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant); + } +} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index d4ca704829..904be3a9e3 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -1,221 +1,221 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Mania.UI -{ - /// - /// A collection of s. - /// - internal class ManiaStage : ScrollingPlayfield - { - public const float HIT_TARGET_POSITION = 50; - - /// - /// Whether this playfield should be inverted. This flips everything inside the playfield. - /// - public readonly Bindable Inverted = new Bindable(true); - - public IReadOnlyList Columns => columnFlow.Children; - private readonly FillFlowContainer columnFlow; - - protected override Container Content => content; - private readonly Container content; - - public Container Judgements => judgements; - private readonly JudgementContainer judgements; - - private readonly Container topLevelContainer; - - private List normalColumnColours = new List(); - private Color4 specialColumnColour; - - private readonly int firstColumnIndex; - - public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) - : base(ScrollingDirection.Up) - { - this.firstColumnIndex = firstColumnIndex; - - Name = "Stage"; - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - InternalChildren = new Drawable[] - { - new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - new Container - { - Name = "Columns mask", - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new Drawable[] - { - new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - columnFlow = new FillFlowContainer - { - Name = "Columns", - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = 1, Right = 1 }, - Spacing = new Vector2(1, 0) - }, - } - }, - new Container - { - Name = "Barlines mask", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = 1366, // Bar lines should only be masked on the vertical axis - BypassAutoSizeAxes = Axes.Both, - Masking = true, - Child = content = new Container - { - Name = "Bar lines", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Top = HIT_TARGET_POSITION } - } - }, - judgements = new JudgementContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Y = HIT_TARGET_POSITION + 150, - BypassAutoSizeAxes = Axes.Both - }, - topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } - } - } - }; - - for (int i = 0; i < definition.Columns; i++) - { - var isSpecial = definition.IsSpecialColumn(i); - var column = new Column - { - IsSpecial = isSpecial, - Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ - }; - - AddColumn(column); - } - - Inverted.ValueChanged += invertedChanged; - Inverted.TriggerChange(); - } - - private void invertedChanged(bool newValue) - { - Scale = new Vector2(1, newValue ? -1 : 1); - Judgements.Scale = Scale; - } - - public void AddColumn(Column c) - { - c.VisibleTimeRange.BindTo(VisibleTimeRange); - - topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); - columnFlow.Add(c); - AddNested(c); - } - - public override void Add(DrawableHitObject h) - { - var maniaObject = (ManiaHitObject)h.HitObject; - int columnIndex = maniaObject.Column - firstColumnIndex; - Columns.ElementAt(columnIndex).Add(h); - h.OnJudgement += OnJudgement; - } - - public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); - - internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) - { - judgements.Clear(); - judgements.Add(new DrawableManiaJudgement(judgement, judgedObject) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - normalColumnColours = new List - { - colours.RedDark, - colours.GreenDark - }; - - specialColumnColour = colours.BlueDark; - - // Set the special column + colour + key - foreach (var column in Columns) - { - if (!column.IsSpecial) - continue; - - column.AccentColour = specialColumnColour; - } - - var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList(); - - // We'll set the colours of the non-special columns in a separate loop, because the non-special - // column colours are mirrored across their centre and special styles mess with this - for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++) - { - Color4 colour = normalColumnColours[i % normalColumnColours.Count]; - nonSpecialColumns[i].AccentColour = colour; - nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; - } - } - - protected override void Update() - { - // Due to masking differences, it is not possible to get the width of the columns container automatically - // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually - content.Width = columnFlow.Width; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Mania.UI +{ + /// + /// A collection of s. + /// + internal class ManiaStage : ScrollingPlayfield + { + public const float HIT_TARGET_POSITION = 50; + + /// + /// Whether this playfield should be inverted. This flips everything inside the playfield. + /// + public readonly Bindable Inverted = new Bindable(true); + + public IReadOnlyList Columns => columnFlow.Children; + private readonly FillFlowContainer columnFlow; + + protected override Container Content => content; + private readonly Container content; + + public Container Judgements => judgements; + private readonly JudgementContainer judgements; + + private readonly Container topLevelContainer; + + private List normalColumnColours = new List(); + private Color4 specialColumnColour; + + private readonly int firstColumnIndex; + + public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + : base(ScrollingDirection.Up) + { + this.firstColumnIndex = firstColumnIndex; + + Name = "Stage"; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new Container + { + Name = "Columns mask", + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + columnFlow = new FillFlowContainer + { + Name = "Columns", + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Left = 1, Right = 1 }, + Spacing = new Vector2(1, 0) + }, + } + }, + new Container + { + Name = "Barlines mask", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = 1366, // Bar lines should only be masked on the vertical axis + BypassAutoSizeAxes = Axes.Both, + Masking = true, + Child = content = new Container + { + Name = "Bar lines", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = HIT_TARGET_POSITION } + } + }, + judgements = new JudgementContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Y = HIT_TARGET_POSITION + 150, + BypassAutoSizeAxes = Axes.Both + }, + topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } + } + } + }; + + for (int i = 0; i < definition.Columns; i++) + { + var isSpecial = definition.IsSpecialColumn(i); + var column = new Column + { + IsSpecial = isSpecial, + Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ + }; + + AddColumn(column); + } + + Inverted.ValueChanged += invertedChanged; + Inverted.TriggerChange(); + } + + private void invertedChanged(bool newValue) + { + Scale = new Vector2(1, newValue ? -1 : 1); + Judgements.Scale = Scale; + } + + public void AddColumn(Column c) + { + c.VisibleTimeRange.BindTo(VisibleTimeRange); + + topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); + columnFlow.Add(c); + AddNested(c); + } + + public override void Add(DrawableHitObject h) + { + var maniaObject = (ManiaHitObject)h.HitObject; + int columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Add(h); + h.OnJudgement += OnJudgement; + } + + public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); + + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + judgements.Clear(); + judgements.Add(new DrawableManiaJudgement(judgement, judgedObject) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + normalColumnColours = new List + { + colours.RedDark, + colours.GreenDark + }; + + specialColumnColour = colours.BlueDark; + + // Set the special column + colour + key + foreach (var column in Columns) + { + if (!column.IsSpecial) + continue; + + column.AccentColour = specialColumnColour; + } + + var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList(); + + // We'll set the colours of the non-special columns in a separate loop, because the non-special + // column colours are mirrored across their centre and special styles mess with this + for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++) + { + Color4 colour = normalColumnColours[i % normalColumnColours.Count]; + nonSpecialColumns[i].AccentColour = colour; + nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; + } + } + + protected override void Update() + { + // Due to masking differences, it is not possible to get the width of the columns container automatically + // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually + content.Width = columnFlow.Width; + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 59c59dc0e3..6ac3c016a0 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -1,70 +1,70 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Tests.Beatmaps; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - public class OsuBeatmapConversionTest : BeatmapConversionTest - { - protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - - [TestCase("basic")] - [TestCase("colinear-perfect-curve")] - public new void Test(string name) - { - base.Test(name); - } - - protected override IEnumerable CreateConvertValue(HitObject hitObject) - { - var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192); - var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition; - - yield return new ConvertValue - { - StartTime = hitObject.StartTime, - EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, - StartX = startPosition.X, - StartY = startPosition.Y, - EndX = endPosition.X, - EndY = endPosition.Y - }; - } - - protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new OsuBeatmapConverter(); - } - - public struct ConvertValue : IEquatable - { - /// - /// A sane value to account for osu!stable using ints everwhere. - /// - private const double conversion_lenience = 2; - - public double StartTime; - public double EndTime; - public float StartX; - public float StartY; - public float EndX; - public float EndY; - - public bool Equals(ConvertValue other) - => Precision.AlmostEquals(StartTime, other.StartTime) - && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) - && Precision.AlmostEquals(StartX, other.StartX) - && Precision.AlmostEquals(StartY, other.StartY, conversion_lenience) - && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience) - && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class OsuBeatmapConversionTest : BeatmapConversionTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; + + [TestCase("basic")] + [TestCase("colinear-perfect-curve")] + public new void Test(string name) + { + base.Test(name); + } + + protected override IEnumerable CreateConvertValue(HitObject hitObject) + { + var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192); + var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition; + + yield return new ConvertValue + { + StartTime = hitObject.StartTime, + EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, + StartX = startPosition.X, + StartY = startPosition.Y, + EndX = endPosition.X, + EndY = endPosition.Y + }; + } + + protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new OsuBeatmapConverter(); + } + + public struct ConvertValue : IEquatable + { + /// + /// A sane value to account for osu!stable using ints everwhere. + /// + private const double conversion_lenience = 2; + + public double StartTime; + public double EndTime; + public float StartX; + public float StartY; + public float EndX; + public float EndY; + + public bool Equals(ConvertValue other) + => Precision.AlmostEquals(StartTime, other.StartTime) + && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) + && Precision.AlmostEquals(StartX, other.StartX) + && Precision.AlmostEquals(StartY, other.StartY, conversion_lenience) + && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience) + && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs index a11f32935e..f2525ad555 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseEditor : EditorTestCase - { - public TestCaseEditor() - : base(new OsuRuleset()) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseEditor : EditorTestCase + { + public TestCaseEditor() + : base(new OsuRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs index 273422f2e9..472da88e1f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics.Cursor; -using osu.Game.Rulesets.Osu.UI.Cursor; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor - { - private GameplayCursor cursor; - - public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) }; - - public CursorContainer Cursor => cursor; - - public bool ProvidingUserCursor => true; - - [BackgroundDependencyLoader] - private void load() - { - Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor + { + private GameplayCursor cursor; + + public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) }; + + public CursorContainer Cursor => cursor; + + public bool ProvidingUserCursor => true; + + [BackgroundDependencyLoader] + private void load() + { + Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs index b0cfa43f15..7af7140fd8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs @@ -1,115 +1,115 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; -using OpenTK; -using osu.Game.Rulesets.Osu.Judgements; -using System.Collections.Generic; -using System; -using osu.Game.Rulesets.Mods; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseHitCircle : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(DrawableHitCircle) - }; - - private readonly Container content; - protected override Container Content => content; - - private int depthIndex; - protected readonly List Mods = new List(); - - public TestCaseHitCircle() - { - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - - AddStep("Miss Big Single", () => testSingle(2)); - AddStep("Miss Medium Single", () => testSingle(5)); - AddStep("Miss Small Single", () => testSingle(7)); - AddStep("Hit Big Single", () => testSingle(2, true)); - AddStep("Hit Medium Single", () => testSingle(5, true)); - AddStep("Hit Small Single", () => testSingle(7, true)); - AddStep("Miss Big Stream", () => testStream(2)); - AddStep("Miss Medium Stream", () => testStream(5)); - AddStep("Miss Small Stream", () => testStream(7)); - AddStep("Hit Big Stream", () => testStream(2, true)); - AddStep("Hit Medium Stream", () => testStream(5, true)); - AddStep("Hit Small Stream", () => testStream(7, true)); - } - - private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) - { - positionOffset = positionOffset ?? Vector2.Zero; - - var circle = new HitCircle - { - StartTime = Time.Current + 1000 + timeOffset, - Position = positionOffset.Value, - }; - - circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); - - var drawable = new TestDrawableHitCircle(circle, auto) - { - Anchor = Anchor.Centre, - Depth = depthIndex++ - }; - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawable }); - - Add(drawable); - } - - private void testStream(float circleSize, bool auto = false) - { - Vector2 pos = new Vector2(-250, 0); - - for (int i = 0; i <= 1000; i += 100) - { - testSingle(circleSize, auto, i, pos); - pos.X += 50; - } - } - - private class TestDrawableHitCircle : DrawableHitCircle - { - private readonly bool auto; - - public TestDrawableHitCircle(HitCircle h, bool auto) : base(h) - { - this.auto = auto; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (auto && !userTriggered && timeOffset > 0) - { - // force success - AddJudgement(new OsuJudgement - { - Result = HitResult.Great - }); - State.Value = ArmedState.Hit; - } - else - base.CheckForJudgements(userTriggered, timeOffset); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; +using osu.Game.Rulesets.Osu.Judgements; +using System.Collections.Generic; +using System; +using osu.Game.Rulesets.Mods; +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseHitCircle : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableHitCircle) + }; + + private readonly Container content; + protected override Container Content => content; + + private int depthIndex; + protected readonly List Mods = new List(); + + public TestCaseHitCircle() + { + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + AddStep("Miss Big Single", () => testSingle(2)); + AddStep("Miss Medium Single", () => testSingle(5)); + AddStep("Miss Small Single", () => testSingle(7)); + AddStep("Hit Big Single", () => testSingle(2, true)); + AddStep("Hit Medium Single", () => testSingle(5, true)); + AddStep("Hit Small Single", () => testSingle(7, true)); + AddStep("Miss Big Stream", () => testStream(2)); + AddStep("Miss Medium Stream", () => testStream(5)); + AddStep("Miss Small Stream", () => testStream(7)); + AddStep("Hit Big Stream", () => testStream(2, true)); + AddStep("Hit Medium Stream", () => testStream(5, true)); + AddStep("Hit Small Stream", () => testStream(7, true)); + } + + private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) + { + positionOffset = positionOffset ?? Vector2.Zero; + + var circle = new HitCircle + { + StartTime = Time.Current + 1000 + timeOffset, + Position = positionOffset.Value, + }; + + circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); + + var drawable = new TestDrawableHitCircle(circle, auto) + { + Anchor = Anchor.Centre, + Depth = depthIndex++ + }; + + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(new[] { drawable }); + + Add(drawable); + } + + private void testStream(float circleSize, bool auto = false) + { + Vector2 pos = new Vector2(-250, 0); + + for (int i = 0; i <= 1000; i += 100) + { + testSingle(circleSize, auto, i, pos); + pos.X += 50; + } + } + + private class TestDrawableHitCircle : DrawableHitCircle + { + private readonly bool auto; + + public TestDrawableHitCircle(HitCircle h, bool auto) : base(h) + { + this.auto = auto; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (auto && !userTriggered && timeOffset > 0) + { + // force success + AddJudgement(new OsuJudgement + { + Result = HitResult.Great + }); + State.Value = ArmedState.Hit; + } + else + base.CheckForJudgements(userTriggered, timeOffset); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs index f030c6db60..68c35faca4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Osu.Mods; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseHitCircleHidden : TestCaseHitCircle - { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - - public TestCaseHitCircleHidden() - { - Mods.Add(new OsuModHidden()); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseHitCircleHidden : TestCaseHitCircle + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); + + public TestCaseHitCircleHidden() + { + Mods.Add(new OsuModHidden()); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Osu.Tests/TestCasePerformancePoints.cs index b6dca3f1cb..63026fe316 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCasePerformancePoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCasePerformancePoints.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints - { - public TestCasePerformancePoints() - : base(new OsuRuleset()) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints + { + public TestCasePerformancePoints() + : base(new OsuRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index e819d8fff5..f7f73f74a5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -1,313 +1,313 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Rulesets.Mods; -using System.Linq; -using NUnit.Framework; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseSlider : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(SliderBall), - typeof(SliderBody), - typeof(SliderTick), - typeof(DrawableSlider), - typeof(DrawableSliderTick), - typeof(DrawableRepeatPoint), - typeof(DrawableOsuHitObject) - }; - - private readonly Container content; - protected override Container Content => content; - - private int depthIndex; - protected readonly List Mods = new List(); - - public TestCaseSlider() - { - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - - AddStep("Big Single", () => testSimpleBig()); - AddStep("Medium Single", () => testSimpleMedium()); - AddStep("Small Single", () => testSimpleSmall()); - AddStep("Big 1 Repeat", () => testSimpleBig(1)); - AddStep("Medium 1 Repeat", () => testSimpleMedium(1)); - AddStep("Small 1 Repeat", () => testSimpleSmall(1)); - AddStep("Big 2 Repeats", () => testSimpleBig(2)); - AddStep("Medium 2 Repeats", () => testSimpleMedium(2)); - AddStep("Small 2 Repeats", () => testSimpleSmall(2)); - - AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps - AddStep("Slow Short Slider", () => testShortSlowSpeed()); - AddStep("Slow Short Slider 1 Repeats", () => testShortSlowSpeed(1)); - AddStep("Slow Short Slider 2 Repeats", () => testShortSlowSpeed(2)); - - AddStep("Fast Slider", () => testHighSpeed()); - AddStep("Fast Slider 1 Repeat", () => testHighSpeed(1)); - AddStep("Fast Slider 2 Repeats", () => testHighSpeed(2)); - AddStep("Fast Short Slider", () => testShortHighSpeed()); - AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1)); - AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2)); - AddStep("Fast Short Slider 6 Repeats", () => testShortHighSpeed(6)); - - AddStep("Perfect Curve", () => testPerfect()); - AddStep("Perfect Curve 1 Repeat", () => testPerfect(1)); - AddStep("Perfect Curve 2 Repeats", () => testPerfect(2)); - - AddStep("Linear Slider", () => testLinear()); - AddStep("Linear Slider 1 Repeat", () => testLinear(1)); - AddStep("Linear Slider 2 Repeats", () => testLinear(2)); - - AddStep("Bezier Slider", () => testBezier()); - AddStep("Bezier Slider 1 Repeat", () => testBezier(1)); - AddStep("Bezier Slider 2 Repeats", () => testBezier(2)); - - AddStep("Linear Overlapping", () => testLinearOverlapping()); - AddStep("Linear Overlapping 1 Repeat", () => testLinearOverlapping(1)); - AddStep("Linear Overlapping 2 Repeats", () => testLinearOverlapping(2)); - - AddStep("Catmull Slider", () => testCatmull()); - AddStep("Catmull Slider 1 Repeat", () => testCatmull(1)); - AddStep("Catmull Slider 2 Repeats", () => testCatmull(2)); - - AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset()); - AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1)); - } - - private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); - - private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); - - private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); - - private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); - - private void testSlowSpeed() => createSlider(speedMultiplier: 0.5); - - private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); - - private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); - - private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); - - private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) - { - var slider = new Slider - { - StartTime = Time.Current + 1000, - Position = new Vector2(-(distance / 2), 0), - ControlPoints = new List - { - Vector2.Zero, - new Vector2(distance, 0), - }, - Distance = distance, - RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats), - StackHeight = stackHeight - }; - - addSlider(slider, circleSize, speedMultiplier); - } - - private void testPerfect(int repeats = 0) - { - var slider = new Slider - { - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), - ControlPoints = new List - { - Vector2.Zero, - new Vector2(200, 200), - new Vector2(400, 0) - }, - Distance = 600, - RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) - }; - - addSlider(slider, 2, 3); - } - - private void testLinear(int repeats = 0) => createLinear(repeats); - - private void createLinear(int repeats) - { - var slider = new Slider - { - CurveType = CurveType.Linear, - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), - ControlPoints = new List - { - Vector2.Zero, - new Vector2(150, 75), - new Vector2(200, 0), - new Vector2(300, -200), - new Vector2(400, 0), - new Vector2(430, 0) - }, - Distance = 793.4417, - RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) - }; - - addSlider(slider, 2, 3); - } - - private void testBezier(int repeats = 0) => createBezier(repeats); - - private void createBezier(int repeats) - { - var slider = new Slider - { - CurveType = CurveType.Bezier, - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), - ControlPoints = new List - { - Vector2.Zero, - new Vector2(150, 75), - new Vector2(200, 100), - new Vector2(300, -200), - new Vector2(430, 0) - }, - Distance = 480, - RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) - }; - - addSlider(slider, 2, 3); - } - - private void testLinearOverlapping(int repeats = 0) => createOverlapping(repeats); - - private void createOverlapping(int repeats) - { - var slider = new Slider - { - CurveType = CurveType.Linear, - StartTime = Time.Current + 1000, - Position = new Vector2(0, 0), - ControlPoints = new List - { - Vector2.Zero, - new Vector2(-200, 0), - new Vector2(0, 0), - new Vector2(0, -200), - new Vector2(-200, -200), - new Vector2(0, -200) - }, - Distance = 1000, - RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) - }; - - addSlider(slider, 2, 3); - } - - private void testCatmull(int repeats = 0) => createCatmull(repeats); - - private void createCatmull(int repeats = 0) - { - var repeatSamples = new List>(); - for (int i = 0; i < repeats; i++) - repeatSamples.Add(new List()); - - var slider = new Slider - { - StartTime = Time.Current + 1000, - Position = new Vector2(-100, 0), - CurveType = CurveType.Catmull, - ControlPoints = new List - { - Vector2.Zero, - new Vector2(50, -50), - new Vector2(150, 50), - new Vector2(200, 0) - }, - Distance = 300, - RepeatCount = repeats, - RepeatSamples = repeatSamples - }; - - addSlider(slider, 3, 1); - } - - private List> createEmptySamples(int repeats) - { - var repeatSamples = new List>(); - for (int i = 0; i < repeats; i++) - repeatSamples.Add(new List()); - return repeatSamples; - } - - private void addSlider(Slider slider, float circleSize, double speedMultiplier) - { - var cpi = new ControlPointInfo(); - cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); - - slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); - - var drawable = new DrawableSlider(slider) - { - Anchor = Anchor.Centre, - Depth = depthIndex++ - }; - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawable }); - - drawable.OnJudgement += onJudgement; - - Add(drawable); - } - - private float judgementOffsetDirection = 1; - private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) - { - var osuObject = judgedObject as DrawableOsuHitObject; - if (osuObject == null) - return; - - OsuSpriteText text; - Add(text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = judgement.IsHit ? "Hit!" : "Miss!", - Colour = judgement.IsHit ? Color4.Green : Color4.Red, - TextSize = 30, - Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) - }); - - text.Delay(150) - .Then().FadeOut(200) - .Then().Expire(); - - judgementOffsetDirection *= -1; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Rulesets.Mods; +using System.Linq; +using NUnit.Framework; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseSlider : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SliderBall), + typeof(SliderBody), + typeof(SliderTick), + typeof(DrawableSlider), + typeof(DrawableSliderTick), + typeof(DrawableRepeatPoint), + typeof(DrawableOsuHitObject) + }; + + private readonly Container content; + protected override Container Content => content; + + private int depthIndex; + protected readonly List Mods = new List(); + + public TestCaseSlider() + { + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + AddStep("Big Single", () => testSimpleBig()); + AddStep("Medium Single", () => testSimpleMedium()); + AddStep("Small Single", () => testSimpleSmall()); + AddStep("Big 1 Repeat", () => testSimpleBig(1)); + AddStep("Medium 1 Repeat", () => testSimpleMedium(1)); + AddStep("Small 1 Repeat", () => testSimpleSmall(1)); + AddStep("Big 2 Repeats", () => testSimpleBig(2)); + AddStep("Medium 2 Repeats", () => testSimpleMedium(2)); + AddStep("Small 2 Repeats", () => testSimpleSmall(2)); + + AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps + AddStep("Slow Short Slider", () => testShortSlowSpeed()); + AddStep("Slow Short Slider 1 Repeats", () => testShortSlowSpeed(1)); + AddStep("Slow Short Slider 2 Repeats", () => testShortSlowSpeed(2)); + + AddStep("Fast Slider", () => testHighSpeed()); + AddStep("Fast Slider 1 Repeat", () => testHighSpeed(1)); + AddStep("Fast Slider 2 Repeats", () => testHighSpeed(2)); + AddStep("Fast Short Slider", () => testShortHighSpeed()); + AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1)); + AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2)); + AddStep("Fast Short Slider 6 Repeats", () => testShortHighSpeed(6)); + + AddStep("Perfect Curve", () => testPerfect()); + AddStep("Perfect Curve 1 Repeat", () => testPerfect(1)); + AddStep("Perfect Curve 2 Repeats", () => testPerfect(2)); + + AddStep("Linear Slider", () => testLinear()); + AddStep("Linear Slider 1 Repeat", () => testLinear(1)); + AddStep("Linear Slider 2 Repeats", () => testLinear(2)); + + AddStep("Bezier Slider", () => testBezier()); + AddStep("Bezier Slider 1 Repeat", () => testBezier(1)); + AddStep("Bezier Slider 2 Repeats", () => testBezier(2)); + + AddStep("Linear Overlapping", () => testLinearOverlapping()); + AddStep("Linear Overlapping 1 Repeat", () => testLinearOverlapping(1)); + AddStep("Linear Overlapping 2 Repeats", () => testLinearOverlapping(2)); + + AddStep("Catmull Slider", () => testCatmull()); + AddStep("Catmull Slider 1 Repeat", () => testCatmull(1)); + AddStep("Catmull Slider 2 Repeats", () => testCatmull(2)); + + AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset()); + AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1)); + } + + private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); + + private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); + + private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); + + private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); + + private void testSlowSpeed() => createSlider(speedMultiplier: 0.5); + + private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); + + private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); + + private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); + + private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) + { + var slider = new Slider + { + StartTime = Time.Current + 1000, + Position = new Vector2(-(distance / 2), 0), + ControlPoints = new List + { + Vector2.Zero, + new Vector2(distance, 0), + }, + Distance = distance, + RepeatCount = repeats, + RepeatSamples = createEmptySamples(repeats), + StackHeight = stackHeight + }; + + addSlider(slider, circleSize, speedMultiplier); + } + + private void testPerfect(int repeats = 0) + { + var slider = new Slider + { + StartTime = Time.Current + 1000, + Position = new Vector2(-200, 0), + ControlPoints = new List + { + Vector2.Zero, + new Vector2(200, 200), + new Vector2(400, 0) + }, + Distance = 600, + RepeatCount = repeats, + RepeatSamples = createEmptySamples(repeats) + }; + + addSlider(slider, 2, 3); + } + + private void testLinear(int repeats = 0) => createLinear(repeats); + + private void createLinear(int repeats) + { + var slider = new Slider + { + CurveType = CurveType.Linear, + StartTime = Time.Current + 1000, + Position = new Vector2(-200, 0), + ControlPoints = new List + { + Vector2.Zero, + new Vector2(150, 75), + new Vector2(200, 0), + new Vector2(300, -200), + new Vector2(400, 0), + new Vector2(430, 0) + }, + Distance = 793.4417, + RepeatCount = repeats, + RepeatSamples = createEmptySamples(repeats) + }; + + addSlider(slider, 2, 3); + } + + private void testBezier(int repeats = 0) => createBezier(repeats); + + private void createBezier(int repeats) + { + var slider = new Slider + { + CurveType = CurveType.Bezier, + StartTime = Time.Current + 1000, + Position = new Vector2(-200, 0), + ControlPoints = new List + { + Vector2.Zero, + new Vector2(150, 75), + new Vector2(200, 100), + new Vector2(300, -200), + new Vector2(430, 0) + }, + Distance = 480, + RepeatCount = repeats, + RepeatSamples = createEmptySamples(repeats) + }; + + addSlider(slider, 2, 3); + } + + private void testLinearOverlapping(int repeats = 0) => createOverlapping(repeats); + + private void createOverlapping(int repeats) + { + var slider = new Slider + { + CurveType = CurveType.Linear, + StartTime = Time.Current + 1000, + Position = new Vector2(0, 0), + ControlPoints = new List + { + Vector2.Zero, + new Vector2(-200, 0), + new Vector2(0, 0), + new Vector2(0, -200), + new Vector2(-200, -200), + new Vector2(0, -200) + }, + Distance = 1000, + RepeatCount = repeats, + RepeatSamples = createEmptySamples(repeats) + }; + + addSlider(slider, 2, 3); + } + + private void testCatmull(int repeats = 0) => createCatmull(repeats); + + private void createCatmull(int repeats = 0) + { + var repeatSamples = new List>(); + for (int i = 0; i < repeats; i++) + repeatSamples.Add(new List()); + + var slider = new Slider + { + StartTime = Time.Current + 1000, + Position = new Vector2(-100, 0), + CurveType = CurveType.Catmull, + ControlPoints = new List + { + Vector2.Zero, + new Vector2(50, -50), + new Vector2(150, 50), + new Vector2(200, 0) + }, + Distance = 300, + RepeatCount = repeats, + RepeatSamples = repeatSamples + }; + + addSlider(slider, 3, 1); + } + + private List> createEmptySamples(int repeats) + { + var repeatSamples = new List>(); + for (int i = 0; i < repeats; i++) + repeatSamples.Add(new List()); + return repeatSamples; + } + + private void addSlider(Slider slider, float circleSize, double speedMultiplier) + { + var cpi = new ControlPointInfo(); + cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); + + slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); + + var drawable = new DrawableSlider(slider) + { + Anchor = Anchor.Centre, + Depth = depthIndex++ + }; + + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(new[] { drawable }); + + drawable.OnJudgement += onJudgement; + + Add(drawable); + } + + private float judgementOffsetDirection = 1; + private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + var osuObject = judgedObject as DrawableOsuHitObject; + if (osuObject == null) + return; + + OsuSpriteText text; + Add(text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = judgement.IsHit ? "Hit!" : "Miss!", + Colour = judgement.IsHit ? Color4.Green : Color4.Red, + TextSize = 30, + Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) + }); + + text.Delay(150) + .Then().FadeOut(200) + .Then().Expire(); + + judgementOffsetDirection *= -1; + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs index 57b719464f..1c207d1fca 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Osu.Mods; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseSliderHidden : TestCaseSlider - { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - - public TestCaseSliderHidden() - { - Mods.Add(new OsuModHidden()); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseSliderHidden : TestCaseSlider + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); + + public TestCaseSliderHidden() + { + Mods.Add(new OsuModHidden()); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs index d3620bcbda..b05a763e88 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs @@ -1,88 +1,88 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseSpinner : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] -{ - typeof(SpinnerDisc), - typeof(DrawableSpinner), - typeof(DrawableOsuHitObject) - }; - - private readonly Container content; - protected override Container Content => content; - - private int depthIndex; - protected readonly List Mods = new List(); - - public TestCaseSpinner() - { - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - - AddStep("Miss Big", () => testSingle(2)); - AddStep("Miss Medium", () => testSingle(5)); - AddStep("Miss Small", () => testSingle(7)); - AddStep("Hit Big", () => testSingle(2, true)); - AddStep("Hit Medium", () => testSingle(5, true)); - AddStep("Hit Small", () => testSingle(7, true)); - } - - private void testSingle(float circleSize, bool auto = false) - { - var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 }; - - spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); - - var drawable = new TestDrawableSpinner(spinner, auto) - { - Anchor = Anchor.Centre, - Depth = depthIndex++ - }; - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(new[] { drawable }); - - Add(drawable); - } - - private class TestDrawableSpinner : DrawableSpinner - { - private bool auto; - - public TestDrawableSpinner(Spinner s, bool auto) : base(s) - { - this.auto = auto; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) - { - // force completion only once to not break human interaction - Disc.RotationAbsolute = Spinner.SpinsRequired * 360; - auto = false; - } - - base.CheckForJudgements(userTriggered, timeOffset); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseSpinner : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] +{ + typeof(SpinnerDisc), + typeof(DrawableSpinner), + typeof(DrawableOsuHitObject) + }; + + private readonly Container content; + protected override Container Content => content; + + private int depthIndex; + protected readonly List Mods = new List(); + + public TestCaseSpinner() + { + base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + + AddStep("Miss Big", () => testSingle(2)); + AddStep("Miss Medium", () => testSingle(5)); + AddStep("Miss Small", () => testSingle(7)); + AddStep("Hit Big", () => testSingle(2, true)); + AddStep("Hit Medium", () => testSingle(5, true)); + AddStep("Hit Small", () => testSingle(7, true)); + } + + private void testSingle(float circleSize, bool auto = false) + { + var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 }; + + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); + + var drawable = new TestDrawableSpinner(spinner, auto) + { + Anchor = Anchor.Centre, + Depth = depthIndex++ + }; + + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(new[] { drawable }); + + Add(drawable); + } + + private class TestDrawableSpinner : DrawableSpinner + { + private bool auto; + + public TestDrawableSpinner(Spinner s, bool auto) : base(s) + { + this.auto = auto; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) + { + // force completion only once to not break human interaction + Disc.RotationAbsolute = Spinner.SpinsRequired * 360; + auto = false; + } + + base.CheckForJudgements(userTriggered, timeOffset); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs index 75b3b4c763..b93a6ed0f1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Rulesets.Osu.Mods; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseSpinnerHidden : TestCaseSpinner - { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - - public TestCaseSpinnerHidden() - { - Mods.Add(new OsuModHidden()); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseSpinnerHidden : TestCaseSpinner + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); + + public TestCaseSpinnerHidden() + { + Mods.Add(new OsuModHidden()); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index b2b524a71c..49c5f15713 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -1,10 +1,10 @@ - - - - WinExe - netcoreapp2.0;net461 - - - - + + + + WinExe + netcoreapp2.0;net461 + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 37cb47a83e..1236076f48 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Types; -using System; -using osu.Game.Rulesets.Osu.UI; - -namespace osu.Game.Rulesets.Osu.Beatmaps -{ - internal class OsuBeatmapConverter : BeatmapConverter - { - protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) }; - - protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) - { - var curveData = original as IHasCurve; - var endTimeData = original as IHasEndTime; - var positionData = original as IHasPosition; - var comboData = original as IHasCombo; - - if (curveData != null) - { - yield return new Slider - { - StartTime = original.StartTime, - Samples = original.Samples, - ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, - Distance = curveData.Distance, - RepeatSamples = curveData.RepeatSamples, - RepeatCount = curveData.RepeatCount, - Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false - }; - } - else if (endTimeData != null) - { - yield return new Spinner - { - StartTime = original.StartTime, - Samples = original.Samples, - EndTime = endTimeData.EndTime, - - Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2, - }; - } - else - { - yield return new HitCircle - { - StartTime = original.StartTime, - Samples = original.Samples, - Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Types; +using System; +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Rulesets.Osu.Beatmaps +{ + internal class OsuBeatmapConverter : BeatmapConverter + { + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) }; + + protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) + { + var curveData = original as IHasCurve; + var endTimeData = original as IHasEndTime; + var positionData = original as IHasPosition; + var comboData = original as IHasCombo; + + if (curveData != null) + { + yield return new Slider + { + StartTime = original.StartTime, + Samples = original.Samples, + ControlPoints = curveData.ControlPoints, + CurveType = curveData.CurveType, + Distance = curveData.Distance, + RepeatSamples = curveData.RepeatSamples, + RepeatCount = curveData.RepeatCount, + Position = positionData?.Position ?? Vector2.Zero, + NewCombo = comboData?.NewCombo ?? false + }; + } + else if (endTimeData != null) + { + yield return new Spinner + { + StartTime = original.StartTime, + Samples = original.Samples, + EndTime = endTimeData.EndTime, + + Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2, + }; + } + else + { + yield return new HitCircle + { + StartTime = original.StartTime, + Samples = original.Samples, + Position = positionData?.Position ?? Vector2.Zero, + NewCombo = comboData?.NewCombo ?? false + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 42b22a71ec..afa2437bf6 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -1,166 +1,166 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Beatmaps -{ - internal class OsuBeatmapProcessor : BeatmapProcessor - { - public override void PostProcess(Beatmap beatmap) - { - applyStacking(beatmap); - base.PostProcess(beatmap); - } - - private void applyStacking(Beatmap beatmap) - { - const int stack_distance = 3; - - // Reset stacking - for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++) - beatmap.HitObjects[i].StackHeight = 0; - - // Extend the end index to include objects they are stacked on - int extendedEndIndex = beatmap.HitObjects.Count - 1; - for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--) - { - int stackBaseIndex = i; - for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) - { - OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; - if (stackBaseObject is Spinner) break; - - OsuHitObject objectN = beatmap.HitObjects[n]; - if (objectN is Spinner) - continue; - - double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; - double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; - - if (objectN.StartTime - endTime > stackThreshold) - //We are no longer within stacking range of the next object. - break; - - if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance || - stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance) - { - stackBaseIndex = n; - - // HitObjects after the specified update range haven't been reset yet - objectN.StackHeight = 0; - } - } - - if (stackBaseIndex > extendedEndIndex) - { - extendedEndIndex = stackBaseIndex; - if (extendedEndIndex == beatmap.HitObjects.Count - 1) - break; - } - } - - //Reverse pass for stack calculation. - int extendedStartIndex = 0; - for (int i = extendedEndIndex; i > 0; i--) - { - int n = i; - /* We should check every note which has not yet got a stack. - * Consider the case we have two interwound stacks and this will make sense. - * - * o <-1 o <-2 - * o <-3 o <-4 - * - * We first process starting from 4 and handle 2, - * then we come backwards on the i loop iteration until we reach 3 and handle 1. - * 2 and 1 will be ignored in the i loop because they already have a stack value. - */ - - OsuHitObject objectI = beatmap.HitObjects[i]; - if (objectI.StackHeight != 0 || objectI is Spinner) continue; - - double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; - - /* If this object is a hitcircle, then we enter this "special" case. - * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. - * Any other case is handled by the "is Slider" code below this. - */ - if (objectI is HitCircle) - { - while (--n >= 0) - { - OsuHitObject objectN = beatmap.HitObjects[n]; - if (objectN is Spinner) continue; - - double endTime = (objectN as IHasEndTime)?.EndTime ?? objectN.StartTime; - - if (objectI.StartTime - endTime > stackThreshold) - //We are no longer within stacking range of the previous object. - break; - - // HitObjects before the specified update range haven't been reset yet - if (n < extendedStartIndex) - { - objectN.StackHeight = 0; - extendedStartIndex = n; - } - - /* This is a special case where hticircles are moved DOWN and RIGHT (negative stacking) if they are under the *last* slider in a stacked pattern. - * o==o <- slider is at original location - * o <- hitCircle has stack of -1 - * o <- hitCircle has stack of -2 - */ - if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) - { - int offset = objectI.StackHeight - objectN.StackHeight + 1; - for (int j = n + 1; j <= i; j++) - { - //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). - OsuHitObject objectJ = beatmap.HitObjects[j]; - if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance) - objectJ.StackHeight -= offset; - } - - //We have hit a slider. We should restart calculation using this as the new base. - //Breaking here will mean that the slider still has StackCount of 0, so will be handled in the i-outer-loop. - break; - } - - if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance) - { - //Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out. - //NOTE: Sliders with start positions stacking are a special case that is also handled here. - - objectN.StackHeight = objectI.StackHeight + 1; - objectI = objectN; - } - } - } - else if (objectI is Slider) - { - /* We have hit the first slider in a possible stack. - * From this point on, we ALWAYS stack positive regardless. - */ - while (--n >= 0) - { - OsuHitObject objectN = beatmap.HitObjects[n]; - if (objectN is Spinner) continue; - - if (objectI.StartTime - objectN.StartTime > stackThreshold) - //We are no longer within stacking range of the previous object. - break; - - if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) - { - objectN.StackHeight = objectI.StackHeight + 1; - objectI = objectN; - } - } - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Beatmaps +{ + internal class OsuBeatmapProcessor : BeatmapProcessor + { + public override void PostProcess(Beatmap beatmap) + { + applyStacking(beatmap); + base.PostProcess(beatmap); + } + + private void applyStacking(Beatmap beatmap) + { + const int stack_distance = 3; + + // Reset stacking + for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++) + beatmap.HitObjects[i].StackHeight = 0; + + // Extend the end index to include objects they are stacked on + int extendedEndIndex = beatmap.HitObjects.Count - 1; + for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--) + { + int stackBaseIndex = i; + for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) + { + OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; + if (stackBaseObject is Spinner) break; + + OsuHitObject objectN = beatmap.HitObjects[n]; + if (objectN is Spinner) + continue; + + double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; + double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; + + if (objectN.StartTime - endTime > stackThreshold) + //We are no longer within stacking range of the next object. + break; + + if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance || + stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance) + { + stackBaseIndex = n; + + // HitObjects after the specified update range haven't been reset yet + objectN.StackHeight = 0; + } + } + + if (stackBaseIndex > extendedEndIndex) + { + extendedEndIndex = stackBaseIndex; + if (extendedEndIndex == beatmap.HitObjects.Count - 1) + break; + } + } + + //Reverse pass for stack calculation. + int extendedStartIndex = 0; + for (int i = extendedEndIndex; i > 0; i--) + { + int n = i; + /* We should check every note which has not yet got a stack. + * Consider the case we have two interwound stacks and this will make sense. + * + * o <-1 o <-2 + * o <-3 o <-4 + * + * We first process starting from 4 and handle 2, + * then we come backwards on the i loop iteration until we reach 3 and handle 1. + * 2 and 1 will be ignored in the i loop because they already have a stack value. + */ + + OsuHitObject objectI = beatmap.HitObjects[i]; + if (objectI.StackHeight != 0 || objectI is Spinner) continue; + + double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; + + /* If this object is a hitcircle, then we enter this "special" case. + * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. + * Any other case is handled by the "is Slider" code below this. + */ + if (objectI is HitCircle) + { + while (--n >= 0) + { + OsuHitObject objectN = beatmap.HitObjects[n]; + if (objectN is Spinner) continue; + + double endTime = (objectN as IHasEndTime)?.EndTime ?? objectN.StartTime; + + if (objectI.StartTime - endTime > stackThreshold) + //We are no longer within stacking range of the previous object. + break; + + // HitObjects before the specified update range haven't been reset yet + if (n < extendedStartIndex) + { + objectN.StackHeight = 0; + extendedStartIndex = n; + } + + /* This is a special case where hticircles are moved DOWN and RIGHT (negative stacking) if they are under the *last* slider in a stacked pattern. + * o==o <- slider is at original location + * o <- hitCircle has stack of -1 + * o <- hitCircle has stack of -2 + */ + if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) + { + int offset = objectI.StackHeight - objectN.StackHeight + 1; + for (int j = n + 1; j <= i; j++) + { + //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). + OsuHitObject objectJ = beatmap.HitObjects[j]; + if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance) + objectJ.StackHeight -= offset; + } + + //We have hit a slider. We should restart calculation using this as the new base. + //Breaking here will mean that the slider still has StackCount of 0, so will be handled in the i-outer-loop. + break; + } + + if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance) + { + //Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out. + //NOTE: Sliders with start positions stacking are a special case that is also handled here. + + objectN.StackHeight = objectI.StackHeight + 1; + objectI = objectN; + } + } + } + else if (objectI is Slider) + { + /* We have hit the first slider in a possible stack. + * From this point on, we ALWAYS stack positive regardless. + */ + while (--n >= 0) + { + OsuHitObject objectN = beatmap.HitObjects[n]; + if (objectN is Spinner) continue; + + if (objectI.StartTime - objectN.StartTime > stackThreshold) + //We are no longer within stacking range of the previous object. + break; + + if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) + { + objectN.StackHeight = objectI.StackHeight + 1; + objectI = objectN; + } + } + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs index 89a7686581..a2aa639004 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs @@ -1,37 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays -{ - public class HitCircleMask : HitObjectMask - { - public HitCircleMask(DrawableHitCircle hitCircle) - : base(hitCircle) - { - Origin = Anchor.Centre; - - Position = hitCircle.Position; - Size = hitCircle.Size; - Scale = hitCircle.Scale; - - CornerRadius = Size.X / 2; - - AddInternal(new RingPiece()); - - hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.Yellow; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays +{ + public class HitCircleMask : HitObjectMask + { + public HitCircleMask(DrawableHitCircle hitCircle) + : base(hitCircle) + { + Origin = Anchor.Centre; + + Position = hitCircle.Position; + Size = hitCircle.Size; + Scale = hitCircle.Scale; + + CornerRadius = Size.X / 2; + + AddInternal(new RingPiece()); + + hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index 96ff14205e..adb28289cf 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -1,61 +1,61 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays -{ - public class SliderCircleMask : HitObjectMask - { - public SliderCircleMask(DrawableHitCircle sliderHead, DrawableSlider slider) - : this(sliderHead, Vector2.Zero, slider) - { - } - - public SliderCircleMask(DrawableSliderTail sliderTail, DrawableSlider slider) - : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider) - { - } - - private readonly DrawableOsuHitObject hitObject; - - private SliderCircleMask(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider) - : base(hitObject) - { - this.hitObject = hitObject; - - Origin = Anchor.Centre; - - Position = position; - Size = slider.HeadCircle.Size; - Scale = slider.HeadCircle.Scale; - - AddInternal(new RingPiece()); - - Select(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.Yellow; - } - - protected override void Update() - { - base.Update(); - - RelativeAnchorPosition = hitObject.RelativeAnchorPosition; - } - - // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. - public override bool HandleMouseInput => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays +{ + public class SliderCircleMask : HitObjectMask + { + public SliderCircleMask(DrawableHitCircle sliderHead, DrawableSlider slider) + : this(sliderHead, Vector2.Zero, slider) + { + } + + public SliderCircleMask(DrawableSliderTail sliderTail, DrawableSlider slider) + : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider) + { + } + + private readonly DrawableOsuHitObject hitObject; + + private SliderCircleMask(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider) + : base(hitObject) + { + this.hitObject = hitObject; + + Origin = Anchor.Centre; + + Position = position; + Size = slider.HeadCircle.Size; + Scale = slider.HeadCircle.Scale; + + AddInternal(new RingPiece()); + + Select(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + + protected override void Update() + { + base.Update(); + + RelativeAnchorPosition = hitObject.RelativeAnchorPosition; + } + + // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. + public override bool HandleMouseInput => false; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs index 629bce1847..0f6143a83d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs @@ -1,67 +1,67 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Primitives; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays -{ - public class SliderMask : HitObjectMask - { - private readonly SliderBody body; - private readonly DrawableSlider slider; - - public SliderMask(DrawableSlider slider) - : base(slider) - { - this.slider = slider; - - Position = slider.Position; - - var sliderObject = (Slider)slider.HitObject; - - InternalChildren = new Drawable[] - { - body = new SliderBody(sliderObject) - { - AccentColour = Color4.Transparent, - PathWidth = sliderObject.Scale * 64 - }, - new SliderCircleMask(slider.HeadCircle, slider), - new SliderCircleMask(slider.TailCircle, slider), - }; - - sliderObject.PositionChanged += _ => Position = slider.Position; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - body.BorderColour = colours.Yellow; - } - - protected override void Update() - { - base.Update(); - - Size = slider.Size; - OriginPosition = slider.OriginPosition; - - // Need to cause one update - body.UpdateProgress(0); - } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos); - - public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition); - public override Quad SelectionQuad => body.PathDrawQuad; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays +{ + public class SliderMask : HitObjectMask + { + private readonly SliderBody body; + private readonly DrawableSlider slider; + + public SliderMask(DrawableSlider slider) + : base(slider) + { + this.slider = slider; + + Position = slider.Position; + + var sliderObject = (Slider)slider.HitObject; + + InternalChildren = new Drawable[] + { + body = new SliderBody(sliderObject) + { + AccentColour = Color4.Transparent, + PathWidth = sliderObject.Scale * 64 + }, + new SliderCircleMask(slider.HeadCircle, slider), + new SliderCircleMask(slider.TailCircle, slider), + }; + + sliderObject.PositionChanged += _ => Position = slider.Position; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + body.BorderColour = colours.Yellow; + } + + protected override void Update() + { + base.Update(); + + Size = slider.Size; + OriginPosition = slider.OriginPosition; + + // Need to cause one update + body.UpdateProgress(0); + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos); + + public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition); + public override Quad SelectionQuad => body.PathDrawQuad; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs index 46a3d8575f..3518e030b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Osu.UI; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public class OsuEditPlayfield : OsuPlayfield - { - protected override bool ProxyApproachCircles => false; - protected override bool DisplayJudgements => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuEditPlayfield : OsuPlayfield + { + protected override bool ProxyApproachCircles => false; + protected override bool DisplayJudgements => false; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs index a8d895bc1d..8d4c342740 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Cursor; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public class OsuEditRulesetContainer : OsuRulesetContainer - { - public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(ruleset, beatmap, isForCurrentRuleset) - { - } - - protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); - - protected override Vector2 PlayfieldArea => Vector2.One; - - protected override CursorContainer CreateCursor() => null; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Cursor; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuEditRulesetContainer : OsuRulesetContainer + { + public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); + + protected override Vector2 PlayfieldArea => Vector2.One; + + protected override CursorContainer CreateCursor() => null; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 026c85d909..7bf0651443 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -1,49 +1,49 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public class OsuHitObjectComposer : HitObjectComposer - { - public OsuHitObjectComposer(Ruleset ruleset) - : base(ruleset) - { - } - - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap, true); - - protected override IReadOnlyList CompositionTools => new ICompositionTool[] - { - new HitObjectCompositionTool(), - new HitObjectCompositionTool(), - new HitObjectCompositionTool() - }; - - protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both }; - - public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) - { - switch (hitObject) - { - case DrawableHitCircle circle: - return new HitCircleMask(circle); - case DrawableSlider slider: - return new SliderMask(slider); - } - - return base.CreateMaskFor(hitObject); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuHitObjectComposer : HitObjectComposer + { + public OsuHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap, true); + + protected override IReadOnlyList CompositionTools => new ICompositionTool[] + { + new HitObjectCompositionTool(), + new HitObjectCompositionTool(), + new HitObjectCompositionTool() + }; + + protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both }; + + public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) + { + switch (hitObject) + { + case DrawableHitCircle circle: + return new HitCircleMask(circle); + case DrawableSlider slider: + return new SliderMask(slider); + } + + return base.CreateMaskFor(hitObject); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index b2d9844613..b7c4470592 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -1,37 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Judgements -{ - public class OsuJudgement : Judgement - { - public override HitResult MaxResult => HitResult.Great; - - /// - /// The positional hit offset. - /// - public Vector2 PositionOffset; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - case HitResult.Meh: - return 50; - case HitResult.Good: - return 100; - case HitResult.Great: - return 300; - } - } - - public ComboResult Combo; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuJudgement : Judgement + { + public override HitResult MaxResult => HitResult.Great; + + /// + /// The positional hit offset. + /// + public Vector2 PositionOffset; + + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Meh: + return 50; + case HitResult.Good: + return 100; + case HitResult.Great: + return 300; + } + } + + public ComboResult Combo; + } +} diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs index a6e67ea979..c4e265aac9 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Judgements -{ - public class OsuSliderTailJudgement : OsuJudgement - { - public override bool AffectsCombo => false; - protected override int NumericResultFor(HitResult result) => 0; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuSliderTailJudgement : OsuJudgement + { + public override bool AffectsCombo => false; + protected override int NumericResultFor(HitResult result) => 0; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 0c842143e4..3573c133c2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModAutopilot : Mod - { - public override string Name => "Autopilot"; - public override string ShortenedName => "AP"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; - public override string Description => @"Automatic cursor movement - just follow the rhythm."; - public override double ScoreMultiplier => 0; - public override bool Ranked => false; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModAutopilot : Mod + { + public override string Name => "Autopilot"; + public override string ShortenedName => "AP"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; + public override string Description => @"Automatic cursor movement - just follow the rhythm."; + public override double ScoreMultiplier => 0; + public override bool Ranked => false; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index a06390a4ea..1eb2f59010 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModAutoplay : ModAutoplay - { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); - - protected override Score CreateReplayScore(Beatmap beatmap) - { - return new Score - { - Replay = new OsuAutoGenerator(beatmap).Generate() - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModAutoplay : ModAutoplay + { + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + + protected override Score CreateReplayScore(Beatmap beatmap) + { + return new Score + { + Replay = new OsuAutoGenerator(beatmap).Generate() + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs index 987bb28932..ff3f6ea7cf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModDaycore : ModDaycore - { - public override double ScoreMultiplier => 0.3; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModDaycore : ModDaycore + { + public override double ScoreMultiplier => 0.3; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs index 5a835aac75..45b538d506 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModDoubleTime : ModDoubleTime - { - public override double ScoreMultiplier => 1.12; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1.12; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs index d842b607c6..465775b175 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModEasy : ModEasy - { - public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModEasy : ModEasy + { + public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!"; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 342c53b41f..a337439593 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModFlashlight : ModFlashlight - { - public override double ScoreMultiplier => 1.12; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModFlashlight : ModFlashlight + { + public override double ScoreMultiplier => 1.12; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs index 1b9291bcf3..dc3ba592fb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModHalfTime : ModHalfTime - { - public override double ScoreMultiplier => 0.3; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModHalfTime : ModHalfTime + { + public override double ScoreMultiplier => 0.3; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 29bf3e248d..cf71116d47 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -1,40 +1,40 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModHardRock : ModHardRock, IApplicableToHitObject - { - public override double ScoreMultiplier => 1.06; - public override bool Ranked => true; - - public void ApplyToHitObject(OsuHitObject hitObject) - { - hitObject.Position = new Vector2(hitObject.Position.X, OsuPlayfield.BASE_SIZE.Y - hitObject.Y); - - var slider = hitObject as Slider; - if (slider == null) - return; - - slider.HeadCircle.Position = new Vector2(slider.HeadCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.HeadCircle.Position.Y); - slider.TailCircle.Position = new Vector2(slider.TailCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.TailCircle.Position.Y); - - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - - var newControlPoints = new List(); - slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, -c.Y))); - - slider.ControlPoints = newControlPoints; - slider.Curve?.Calculate(); // Recalculate the slider curve - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModHardRock : ModHardRock, IApplicableToHitObject + { + public override double ScoreMultiplier => 1.06; + public override bool Ranked => true; + + public void ApplyToHitObject(OsuHitObject hitObject) + { + hitObject.Position = new Vector2(hitObject.Position.X, OsuPlayfield.BASE_SIZE.Y - hitObject.Y); + + var slider = hitObject as Slider; + if (slider == null) + return; + + slider.HeadCircle.Position = new Vector2(slider.HeadCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.HeadCircle.Position.Y); + slider.TailCircle.Position = new Vector2(slider.TailCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.TailCircle.Position.Y); + + slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); + slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); + + var newControlPoints = new List(); + slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, -c.Y))); + + slider.ControlPoints = newControlPoints; + slider.Curve?.Calculate(); // Recalculate the slider curve + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 1117b5bbd5..e72d667c0b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -1,87 +1,87 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects - { - public override string Description => @"Play with no approach circles and fading circles/sliders."; - public override double ScoreMultiplier => 1.06; - - private const double fade_in_duration_multiplier = 0.4; - private const double fade_out_duration_multiplier = 0.3; - - public void ApplyToDrawableHitObjects(IEnumerable drawables) - { - foreach (var d in drawables.OfType()) - { - d.ApplyCustomUpdateState += ApplyHiddenState; - - d.HitObject.TimeFadein = d.HitObject.TimePreempt * fade_in_duration_multiplier; - foreach (var h in d.HitObject.NestedHitObjects.OfType()) - h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier; - } - } - - protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) - { - if (!(drawable is DrawableOsuHitObject d)) - return; - - var h = d.HitObject; - - var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadein; - var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier; - - // new duration from completed fade in to end (before fading out) - var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime; - - switch (drawable) - { - case DrawableHitCircle circle: - // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - circle.ApproachCircle.Hide(); - - // fade out immediately after fade in. - using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) - circle.FadeOut(fadeOutDuration); - - break; - case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) - slider.Body.FadeOut(longFadeDuration, Easing.Out); - - break; - case DrawableSliderTick sliderTick: - // slider ticks fade out over up to one second - var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); - - using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true)) - sliderTick.FadeOut(tickFadeOutDuration); - - break; - case DrawableSpinner spinner: - // hide elements we don't care about. - spinner.Disc.Hide(); - spinner.Ticks.Hide(); - spinner.Background.Hide(); - - using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true)) - spinner.FadeOut(fadeOutDuration); - - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects + { + public override string Description => @"Play with no approach circles and fading circles/sliders."; + public override double ScoreMultiplier => 1.06; + + private const double fade_in_duration_multiplier = 0.4; + private const double fade_out_duration_multiplier = 0.3; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var d in drawables.OfType()) + { + d.ApplyCustomUpdateState += ApplyHiddenState; + + d.HitObject.TimeFadein = d.HitObject.TimePreempt * fade_in_duration_multiplier; + foreach (var h in d.HitObject.NestedHitObjects.OfType()) + h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier; + } + } + + protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + + var h = d.HitObject; + + var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadein; + var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier; + + // new duration from completed fade in to end (before fading out) + var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime; + + switch (drawable) + { + case DrawableHitCircle circle: + // we don't want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + circle.ApproachCircle.Hide(); + + // fade out immediately after fade in. + using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) + circle.FadeOut(fadeOutDuration); + + break; + case DrawableSlider slider: + using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) + slider.Body.FadeOut(longFadeDuration, Easing.Out); + + break; + case DrawableSliderTick sliderTick: + // slider ticks fade out over up to one second + var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); + + using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true)) + sliderTick.FadeOut(tickFadeOutDuration); + + break; + case DrawableSpinner spinner: + // hide elements we don't care about. + spinner.Disc.Hide(); + spinner.Ticks.Hide(); + spinner.Background.Hide(); + + using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true)) + spinner.FadeOut(fadeOutDuration); + + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs index aa0acff68d..9c1b2d8a35 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModNightcore : ModNightcore - { - public override double ScoreMultiplier => 1.12; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModNightcore : ModNightcore + { + public override double ScoreMultiplier => 1.12; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs index 07128cb8ff..a51cb9d888 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModNoFail : ModNoFail - { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModNoFail : ModNoFail + { + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index 886048cd30..5169c8186b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModPerfect : ModPerfect - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModPerfect : ModPerfect + { + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index ed774f0d0a..9f9c2a09b6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModRelax : ModRelax - { - public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModRelax : ModRelax + { + public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 401e56a3c8..d14af57bab 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModSpunOut : Mod - { - public override string Name => "Spun Out"; - public override string ShortenedName => "SO"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; - public override string Description => @"Spinners will be automatically completed."; - public override double ScoreMultiplier => 0.9; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModSpunOut : Mod + { + public override string Name => "Spun Out"; + public override string ShortenedName => "SO"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; + public override string Description => @"Spinners will be automatically completed."; + public override double ScoreMultiplier => 0.9; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index 6c15095bfe..fdd5099ad6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModSuddenDeath : ModSuddenDeath - { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModSuddenDeath : ModSuddenDeath + { + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 613fbc4e32..ce53857a09 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public class OsuModTarget : Mod - { - public override string Name => "Target"; - public override string ShortenedName => "TP"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; - public override string Description => @"Practice keeping up with the beat of the song."; - public override double ScoreMultiplier => 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModTarget : Mod + { + public override string Name => "Target"; + public override string ShortenedName => "TP"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; + public override string Description => @"Practice keeping up with the beat of the song."; + public override double ScoreMultiplier => 1; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs index c7dd12cb0c..f15be94b8a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections -{ - /// - /// Connects hit objects visually, for example with follow points. - /// - public abstract class ConnectionRenderer : Container - where T : HitObject - { - /// - /// Hit objects to create connections for - /// - public abstract IEnumerable HitObjects { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + /// + /// Connects hit objects visually, for example with follow points. + /// + public abstract class ConnectionRenderer : Container + where T : HitObject + { + /// + /// Hit objects to create connections for + /// + public abstract IEnumerable HitObjects { get; set; } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 8a93f7151f..2c89ddc9cf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections -{ - public class FollowPoint : Container - { - private const float width = 8; - - public override bool RemoveWhenNotAlive => false; - - public FollowPoint() - { - Origin = Anchor.Centre; - - Masking = true; - AutoSizeAxes = Axes.Both; - CornerRadius = width / 2; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(0.2f), - Radius = 4, - }; - - Children = new Drawable[] - { - new Box - { - Size = new Vector2(width), - Blending = BlendingMode.Additive, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Alpha = 0.5f, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + public class FollowPoint : Container + { + private const float width = 8; + + public override bool RemoveWhenNotAlive => false; + + public FollowPoint() + { + Origin = Anchor.Centre; + + Masking = true; + AutoSizeAxes = Axes.Both; + CornerRadius = width / 2; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Color4.White.Opacity(0.2f), + Radius = 4, + }; + + Children = new Drawable[] + { + new Box + { + Size = new Vector2(width), + Blending = BlendingMode.Additive, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Alpha = 0.5f, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index d501df492d..4653f45149 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,114 +1,114 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using OpenTK; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections -{ - public class FollowPointRenderer : ConnectionRenderer - { - private int pointDistance = 32; - /// - /// Determines how much space there is between points. - /// - public int PointDistance - { - get { return pointDistance; } - set - { - if (pointDistance == value) return; - pointDistance = value; - update(); - } - } - - private int preEmpt = 800; - /// - /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time. - /// - public int PreEmpt - { - get { return preEmpt; } - set - { - if (preEmpt == value) return; - preEmpt = value; - update(); - } - } - - private IEnumerable hitObjects; - public override IEnumerable HitObjects - { - get { return hitObjects; } - set - { - hitObjects = value; - update(); - } - } - - public override bool RemoveCompletedTransforms => false; - - private void update() - { - Clear(); - - if (hitObjects == null) - return; - - OsuHitObject prevHitObject = null; - foreach (var currHitObject in hitObjects) - { - if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) - { - Vector2 startPosition = prevHitObject.EndPosition; - Vector2 endPosition = currHitObject.Position; - double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime; - double endTime = currHitObject.StartTime; - - Vector2 distanceVector = endPosition - startPosition; - int distance = (int)distanceVector.Length; - float rotation = (float)Math.Atan2(distanceVector.Y, distanceVector.X); - double duration = endTime - startTime; - - for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance) - { - float fraction = (float)d / distance; - Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; - Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - PreEmpt; - - FollowPoint fp; - - Add(fp = new FollowPoint - { - Position = pointStartPosition, - Rotation = rotation, - Alpha = 0, - Scale = new Vector2(1.5f), - }); - - using (fp.BeginAbsoluteSequence(fadeInTime)) - { - fp.FadeIn(currHitObject.TimeFadein); - fp.ScaleTo(1, currHitObject.TimeFadein, Easing.Out); - - fp.MoveTo(pointEndPosition, currHitObject.TimeFadein, Easing.Out); - - fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadein); - } - - fp.Expire(true); - } - } - prevHitObject = currHitObject; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + public class FollowPointRenderer : ConnectionRenderer + { + private int pointDistance = 32; + /// + /// Determines how much space there is between points. + /// + public int PointDistance + { + get { return pointDistance; } + set + { + if (pointDistance == value) return; + pointDistance = value; + update(); + } + } + + private int preEmpt = 800; + /// + /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time. + /// + public int PreEmpt + { + get { return preEmpt; } + set + { + if (preEmpt == value) return; + preEmpt = value; + update(); + } + } + + private IEnumerable hitObjects; + public override IEnumerable HitObjects + { + get { return hitObjects; } + set + { + hitObjects = value; + update(); + } + } + + public override bool RemoveCompletedTransforms => false; + + private void update() + { + Clear(); + + if (hitObjects == null) + return; + + OsuHitObject prevHitObject = null; + foreach (var currHitObject in hitObjects) + { + if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) + { + Vector2 startPosition = prevHitObject.EndPosition; + Vector2 endPosition = currHitObject.Position; + double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime; + double endTime = currHitObject.StartTime; + + Vector2 distanceVector = endPosition - startPosition; + int distance = (int)distanceVector.Length; + float rotation = (float)Math.Atan2(distanceVector.Y, distanceVector.X); + double duration = endTime - startTime; + + for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance) + { + float fraction = (float)d / distance; + Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; + Vector2 pointEndPosition = startPosition + fraction * distanceVector; + double fadeOutTime = startTime + fraction * duration; + double fadeInTime = fadeOutTime - PreEmpt; + + FollowPoint fp; + + Add(fp = new FollowPoint + { + Position = pointStartPosition, + Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f), + }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(currHitObject.TimeFadein); + fp.ScaleTo(1, currHitObject.TimeFadein, Easing.Out); + + fp.MoveTo(pointEndPosition, currHitObject.TimeFadein, Easing.Out); + + fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadein); + } + + fp.Expire(true); + } + } + prevHitObject = currHitObject; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 9066a9ef92..9fe6dcd46c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -1,155 +1,155 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Scoring; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach - { - public ApproachCircle ApproachCircle; - private readonly CirclePiece circle; - private readonly RingPiece ring; - private readonly FlashPiece flash; - private readonly ExplodePiece explode; - private readonly NumberPiece number; - private readonly GlowPiece glow; - - public DrawableHitCircle(HitCircle h) - : base(h) - { - Origin = Anchor.Centre; - - Position = HitObject.StackedPosition; - Scale = new Vector2(h.Scale); - - InternalChildren = new Drawable[] - { - glow = new GlowPiece(), - circle = new CirclePiece - { - Hit = () => - { - if (AllJudged) - return false; - - UpdateJudgement(true); - return true; - }, - }, - number = new NumberPiece - { - Text = (HitObject.IndexInCurrentCombo + 1).ToString(), - }, - ring = new RingPiece(), - flash = new FlashPiece(), - explode = new ExplodePiece(), - ApproachCircle = new ApproachCircle - { - Alpha = 0, - Scale = new Vector2(4), - } - }; - - //may not be so correct - Size = circle.DrawSize; - - HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; - } - - public override Color4 AccentColour - { - get { return base.AccentColour; } - set - { - base.AccentColour = value; - explode.Colour = AccentColour; - glow.Colour = AccentColour; - circle.Colour = AccentColour; - ApproachCircle.Colour = AccentColour; - } - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered) - { - if (!HitObject.HitWindows.CanBeHit(timeOffset)) - AddJudgement(new OsuJudgement { Result = HitResult.Miss }); - return; - } - - var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) - return; - - AddJudgement(new OsuJudgement - { - Result = result, - PositionOffset = Vector2.Zero //todo: set to correct value - }); - } - - protected override void UpdatePreemptState() - { - base.UpdatePreemptState(); - - ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadein * 2, HitObject.TimePreempt)); - ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); - } - - protected override void UpdateCurrentState(ArmedState state) - { - glow.FadeOut(400); - - switch (state) - { - case ArmedState.Idle: - this.Delay(HitObject.TimePreempt).FadeOut(500); - - Expire(true); - - // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. - LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); - break; - case ArmedState.Miss: - ApproachCircle.FadeOut(50); - this.FadeOut(100); - Expire(); - break; - case ArmedState.Hit: - ApproachCircle.FadeOut(50); - - const double flash_in = 40; - flash.FadeTo(0.8f, flash_in) - .Then() - .FadeOut(100); - - explode.FadeIn(flash_in); - - using (BeginDelayedSequence(flash_in, true)) - { - //after the flash, we can hide some elements that were behind it - ring.FadeOut(); - circle.FadeOut(); - number.FadeOut(); - - this.FadeOut(800) - .ScaleTo(Scale * 1.5f, 400, Easing.OutQuad); - } - - Expire(); - break; - } - } - - public Drawable ProxiedLayer => ApproachCircle; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach + { + public ApproachCircle ApproachCircle; + private readonly CirclePiece circle; + private readonly RingPiece ring; + private readonly FlashPiece flash; + private readonly ExplodePiece explode; + private readonly NumberPiece number; + private readonly GlowPiece glow; + + public DrawableHitCircle(HitCircle h) + : base(h) + { + Origin = Anchor.Centre; + + Position = HitObject.StackedPosition; + Scale = new Vector2(h.Scale); + + InternalChildren = new Drawable[] + { + glow = new GlowPiece(), + circle = new CirclePiece + { + Hit = () => + { + if (AllJudged) + return false; + + UpdateJudgement(true); + return true; + }, + }, + number = new NumberPiece + { + Text = (HitObject.IndexInCurrentCombo + 1).ToString(), + }, + ring = new RingPiece(), + flash = new FlashPiece(), + explode = new ExplodePiece(), + ApproachCircle = new ApproachCircle + { + Alpha = 0, + Scale = new Vector2(4), + } + }; + + //may not be so correct + Size = circle.DrawSize; + + HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + explode.Colour = AccentColour; + glow.Colour = AccentColour; + circle.Colour = AccentColour; + ApproachCircle.Colour = AccentColour; + } + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered) + { + if (!HitObject.HitWindows.CanBeHit(timeOffset)) + AddJudgement(new OsuJudgement { Result = HitResult.Miss }); + return; + } + + var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) + return; + + AddJudgement(new OsuJudgement + { + Result = result, + PositionOffset = Vector2.Zero //todo: set to correct value + }); + } + + protected override void UpdatePreemptState() + { + base.UpdatePreemptState(); + + ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadein * 2, HitObject.TimePreempt)); + ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); + } + + protected override void UpdateCurrentState(ArmedState state) + { + glow.FadeOut(400); + + switch (state) + { + case ArmedState.Idle: + this.Delay(HitObject.TimePreempt).FadeOut(500); + + Expire(true); + + // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. + LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); + break; + case ArmedState.Miss: + ApproachCircle.FadeOut(50); + this.FadeOut(100); + Expire(); + break; + case ArmedState.Hit: + ApproachCircle.FadeOut(50); + + const double flash_in = 40; + flash.FadeTo(0.8f, flash_in) + .Then() + .FadeOut(100); + + explode.FadeIn(flash_in); + + using (BeginDelayedSequence(flash_in, true)) + { + //after the flash, we can hide some elements that were behind it + ring.FadeOut(); + circle.FadeOut(); + number.FadeOut(); + + this.FadeOut(800) + .ScaleTo(Scale * 1.5f, 400, Easing.OutQuad); + } + + Expire(); + break; + } + } + + public Drawable ProxiedLayer => ApproachCircle; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index d4d89c2aa3..7c9503dfe2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -1,72 +1,72 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Framework.Graphics; -using System.Linq; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Skinning; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableOsuHitObject : DrawableHitObject - { - public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt; - - protected DrawableOsuHitObject(OsuHitObject hitObject) - : base(hitObject) - { - Alpha = 0; - } - - protected sealed override void UpdateState(ArmedState state) - { - double transformTime = HitObject.StartTime - HitObject.TimePreempt; - - base.ApplyTransformsAt(transformTime, true); - base.ClearTransformsAfter(transformTime, true); - - using (BeginAbsoluteSequence(transformTime, true)) - { - UpdatePreemptState(); - - using (BeginDelayedSequence(HitObject.TimePreempt + (Judgements.FirstOrDefault()?.TimeOffset ?? 0), true)) - UpdateCurrentState(state); - } - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; - } - - protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein); - - protected virtual void UpdateCurrentState(ArmedState state) - { - } - - // Todo: At some point we need to move these to DrawableHitObject after ensuring that all other Rulesets apply - // transforms in the same way and don't rely on them not being cleared - public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { } - public override void ApplyTransformsAt(double time, bool propagateChildren = false) { } - - private OsuInputManager osuActionInputManager; - internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); - } - - public enum ComboResult - { - [Description(@"")] - None, - [Description(@"Good")] - Good, - [Description(@"Amazing")] - Perfect - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Graphics; +using System.Linq; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableOsuHitObject : DrawableHitObject + { + public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt; + + protected DrawableOsuHitObject(OsuHitObject hitObject) + : base(hitObject) + { + Alpha = 0; + } + + protected sealed override void UpdateState(ArmedState state) + { + double transformTime = HitObject.StartTime - HitObject.TimePreempt; + + base.ApplyTransformsAt(transformTime, true); + base.ClearTransformsAfter(transformTime, true); + + using (BeginAbsoluteSequence(transformTime, true)) + { + UpdatePreemptState(); + + using (BeginDelayedSequence(HitObject.TimePreempt + (Judgements.FirstOrDefault()?.TimeOffset ?? 0), true)) + UpdateCurrentState(state); + } + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + if (HitObject is IHasComboInformation combo) + AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + } + + protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein); + + protected virtual void UpdateCurrentState(ArmedState state) + { + } + + // Todo: At some point we need to move these to DrawableHitObject after ensuring that all other Rulesets apply + // transforms in the same way and don't rely on them not being cleared + public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { } + public override void ApplyTransformsAt(double time, bool propagateChildren = false) { } + + private OsuInputManager osuActionInputManager; + internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); + } + + public enum ComboResult + { + [Description(@"")] + None, + [Description(@"Good")] + Good, + [Description(@"Amazing")] + Perfect + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 1468c82b57..e8743281da 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using OpenTK; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableOsuJudgement : DrawableJudgement - { - public DrawableOsuJudgement(Judgement judgement, DrawableHitObject judgedObject) - : base(judgement, judgedObject) - { - } - - protected override void LoadComplete() - { - if (Judgement.Result != HitResult.Miss) - JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); - - base.LoadComplete(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using OpenTK; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableOsuJudgement : DrawableJudgement + { + public DrawableOsuJudgement(Judgement judgement, DrawableHitObject judgedObject) + : base(judgement, judgedObject) + { + } + + protected override void LoadComplete() + { + if (Judgement.Result != HitResult.Miss) + JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + + base.LoadComplete(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 94179f30d3..b3bc2930d8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -1,100 +1,100 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; -using osu.Game.Graphics; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableRepeatPoint : DrawableOsuHitObject, ITrackSnaking - { - private readonly RepeatPoint repeatPoint; - private readonly DrawableSlider drawableSlider; - - private double animDuration; - - public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider) - : base(repeatPoint) - { - this.repeatPoint = repeatPoint; - this.drawableSlider = drawableSlider; - - Size = new Vector2(45 * repeatPoint.Scale); - - Blending = BlendingMode.Additive; - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - new SpriteIcon - { - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_chevron_right - } - }; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (repeatPoint.StartTime <= Time.Current) - AddJudgement(new OsuJudgement { Result = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss }); - } - - protected override void UpdatePreemptState() - { - animDuration = Math.Min(150, repeatPoint.SpanDuration / 2); - - this.Animate( - d => d.FadeIn(animDuration), - d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 4, Easing.OutElasticHalf) - ); - } - - protected override void UpdateCurrentState(ArmedState state) - { - switch (state) - { - case ArmedState.Idle: - this.Delay(HitObject.TimePreempt).FadeOut(); - break; - case ArmedState.Miss: - this.FadeOut(animDuration); - break; - case ArmedState.Hit: - this.FadeOut(animDuration, Easing.OutQuint) - .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - break; - } - } - - public void UpdateSnakingPosition(Vector2 start, Vector2 end) - { - bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0; - List curve = drawableSlider.Body.CurrentCurve; - - Position = isRepeatAtEnd ? end : start; - - if (curve.Count < 2) - return; - - int searchStart = isRepeatAtEnd ? curve.Count - 1 : 0; - int direction = isRepeatAtEnd ? -1 : 1; - - // find the next vector2 in the curve which is not equal to our current position to infer a rotation. - for (int i = searchStart; i >= 0 && i < curve.Count; i += direction) - { - if (curve[i] == Position) - continue; - - Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X)); - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableRepeatPoint : DrawableOsuHitObject, ITrackSnaking + { + private readonly RepeatPoint repeatPoint; + private readonly DrawableSlider drawableSlider; + + private double animDuration; + + public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider) + : base(repeatPoint) + { + this.repeatPoint = repeatPoint; + this.drawableSlider = drawableSlider; + + Size = new Vector2(45 * repeatPoint.Scale); + + Blending = BlendingMode.Additive; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_chevron_right + } + }; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (repeatPoint.StartTime <= Time.Current) + AddJudgement(new OsuJudgement { Result = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss }); + } + + protected override void UpdatePreemptState() + { + animDuration = Math.Min(150, repeatPoint.SpanDuration / 2); + + this.Animate( + d => d.FadeIn(animDuration), + d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 4, Easing.OutElasticHalf) + ); + } + + protected override void UpdateCurrentState(ArmedState state) + { + switch (state) + { + case ArmedState.Idle: + this.Delay(HitObject.TimePreempt).FadeOut(); + break; + case ArmedState.Miss: + this.FadeOut(animDuration); + break; + case ArmedState.Hit: + this.FadeOut(animDuration, Easing.OutQuint) + .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + break; + } + } + + public void UpdateSnakingPosition(Vector2 start, Vector2 end) + { + bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0; + List curve = drawableSlider.Body.CurrentCurve; + + Position = isRepeatAtEnd ? end : start; + + if (curve.Count < 2) + return; + + int searchStart = isRepeatAtEnd ? curve.Count - 1 : 0; + int direction = isRepeatAtEnd ? -1 : 1; + + // find the next vector2 in the curve which is not equal to our current position to infer a rotation. + for (int i = searchStart; i >= 0 && i < curve.Count; i += direction) + { + if (curve[i] == Position) + continue; + + Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X)); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5373926138..dfab123038 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -1,180 +1,180 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Configuration; -using osu.Game.Rulesets.Scoring; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach - { - private readonly Slider slider; - private readonly List components = new List(); - - public readonly DrawableHitCircle HeadCircle; - public readonly DrawableSliderTail TailCircle; - - public readonly SliderBody Body; - public readonly SliderBall Ball; - - public DrawableSlider(Slider s) - : base(s) - { - slider = s; - - Position = s.StackedPosition; - - Container ticks; - Container repeatPoints; - - InternalChildren = new Drawable[] - { - Body = new SliderBody(s) - { - PathWidth = s.Scale * 64, - }, - ticks = new Container { RelativeSizeAxes = Axes.Both }, - repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, - Ball = new SliderBall(s) - { - BypassAutoSizeAxes = Axes.Both, - Scale = new Vector2(s.Scale), - AlwaysPresent = true, - Alpha = 0 - }, - HeadCircle = new DrawableSliderHead(s, s.HeadCircle), - TailCircle = new DrawableSliderTail(s, s.TailCircle) - }; - - components.Add(Body); - components.Add(Ball); - - AddNested(HeadCircle); - - AddNested(TailCircle); - components.Add(TailCircle); - - foreach (var tick in s.NestedHitObjects.OfType()) - { - var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position }; - - ticks.Add(drawableTick); - components.Add(drawableTick); - AddNested(drawableTick); - } - - foreach (var repeatPoint in s.NestedHitObjects.OfType()) - { - var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position }; - - repeatPoints.Add(drawableRepeatPoint); - components.Add(drawableRepeatPoint); - AddNested(drawableRepeatPoint); - } - - HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; - } - - public override Color4 AccentColour - { - get { return base.AccentColour; } - set - { - base.AccentColour = value; - Body.AccentColour = AccentColour; - Ball.AccentColour = AccentColour; - } - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); - config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); - } - - public bool Tracking; - - protected override void Update() - { - base.Update(); - - Tracking = Ball.Tracking; - - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); - - foreach (var c in components.OfType()) c.UpdateProgress(completionProgress); - foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); - foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; - - Size = Body.Size; - OriginPosition = Body.PathOffset; - - if (DrawSize != Vector2.Zero) - { - var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize); - foreach (var obj in NestedHitObjects) - obj.RelativeAnchorPosition = childAnchorPosition; - Ball.RelativeAnchorPosition = childAnchorPosition; - } - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered && Time.Current >= slider.EndTime) - { - var judgementsCount = NestedHitObjects.Count; - var judgementsHit = NestedHitObjects.Count(h => h.IsHit); - - var hitFraction = (double)judgementsHit / judgementsCount; - if (hitFraction == 1 && HeadCircle.Judgements.Any(j => j.Result == HitResult.Great)) - AddJudgement(new OsuJudgement { Result = HitResult.Great }); - else if (hitFraction >= 0.5 && HeadCircle.Judgements.Any(j => j.Result >= HitResult.Good)) - AddJudgement(new OsuJudgement { Result = HitResult.Good }); - else if (hitFraction > 0) - AddJudgement(new OsuJudgement { Result = HitResult.Meh }); - else - AddJudgement(new OsuJudgement { Result = HitResult.Miss }); - } - } - - protected override void UpdateCurrentState(ArmedState state) - { - Ball.FadeIn(); - Ball.ScaleTo(HitObject.Scale); - - using (BeginDelayedSequence(slider.Duration, true)) - { - const float fade_out_time = 450; - - // intentionally pile on an extra FadeOut to make it happen much faster. - Ball.FadeOut(fade_out_time / 4, Easing.Out); - - switch (state) - { - case ArmedState.Hit: - Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); - break; - } - - this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); - } - - Expire(true); - } - - public Drawable ProxiedLayer => HeadCircle.ApproachCircle; - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Configuration; +using osu.Game.Rulesets.Scoring; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach + { + private readonly Slider slider; + private readonly List components = new List(); + + public readonly DrawableHitCircle HeadCircle; + public readonly DrawableSliderTail TailCircle; + + public readonly SliderBody Body; + public readonly SliderBall Ball; + + public DrawableSlider(Slider s) + : base(s) + { + slider = s; + + Position = s.StackedPosition; + + Container ticks; + Container repeatPoints; + + InternalChildren = new Drawable[] + { + Body = new SliderBody(s) + { + PathWidth = s.Scale * 64, + }, + ticks = new Container { RelativeSizeAxes = Axes.Both }, + repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, + Ball = new SliderBall(s) + { + BypassAutoSizeAxes = Axes.Both, + Scale = new Vector2(s.Scale), + AlwaysPresent = true, + Alpha = 0 + }, + HeadCircle = new DrawableSliderHead(s, s.HeadCircle), + TailCircle = new DrawableSliderTail(s, s.TailCircle) + }; + + components.Add(Body); + components.Add(Ball); + + AddNested(HeadCircle); + + AddNested(TailCircle); + components.Add(TailCircle); + + foreach (var tick in s.NestedHitObjects.OfType()) + { + var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position }; + + ticks.Add(drawableTick); + components.Add(drawableTick); + AddNested(drawableTick); + } + + foreach (var repeatPoint in s.NestedHitObjects.OfType()) + { + var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position }; + + repeatPoints.Add(drawableRepeatPoint); + components.Add(drawableRepeatPoint); + AddNested(drawableRepeatPoint); + } + + HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + } + + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + Body.AccentColour = AccentColour; + Ball.AccentColour = AccentColour; + } + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); + config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); + } + + public bool Tracking; + + protected override void Update() + { + base.Update(); + + Tracking = Ball.Tracking; + + double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + + foreach (var c in components.OfType()) c.UpdateProgress(completionProgress); + foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); + foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; + + Size = Body.Size; + OriginPosition = Body.PathOffset; + + if (DrawSize != Vector2.Zero) + { + var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize); + foreach (var obj in NestedHitObjects) + obj.RelativeAnchorPosition = childAnchorPosition; + Ball.RelativeAnchorPosition = childAnchorPosition; + } + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered && Time.Current >= slider.EndTime) + { + var judgementsCount = NestedHitObjects.Count; + var judgementsHit = NestedHitObjects.Count(h => h.IsHit); + + var hitFraction = (double)judgementsHit / judgementsCount; + if (hitFraction == 1 && HeadCircle.Judgements.Any(j => j.Result == HitResult.Great)) + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + else if (hitFraction >= 0.5 && HeadCircle.Judgements.Any(j => j.Result >= HitResult.Good)) + AddJudgement(new OsuJudgement { Result = HitResult.Good }); + else if (hitFraction > 0) + AddJudgement(new OsuJudgement { Result = HitResult.Meh }); + else + AddJudgement(new OsuJudgement { Result = HitResult.Miss }); + } + } + + protected override void UpdateCurrentState(ArmedState state) + { + Ball.FadeIn(); + Ball.ScaleTo(HitObject.Scale); + + using (BeginDelayedSequence(slider.Duration, true)) + { + const float fade_out_time = 450; + + // intentionally pile on an extra FadeOut to make it happen much faster. + Ball.FadeOut(fade_out_time / 4, Easing.Out); + + switch (state) + { + case ArmedState.Hit: + Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); + break; + } + + this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); + } + + Expire(true); + } + + public Drawable ProxiedLayer => HeadCircle.ApproachCircle; + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos); + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index cf36d5fc14..e823c870f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -1,32 +1,32 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableSliderHead : DrawableHitCircle - { - private readonly Slider slider; - - public DrawableSliderHead(Slider slider, HitCircle h) - : base(h) - { - this.slider = slider; - - Position = HitObject.Position - slider.Position; - } - - protected override void Update() - { - base.Update(); - - double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); - - //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. - if (!IsHit) - Position = slider.CurvePositionAt(completionProgress); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSliderHead : DrawableHitCircle + { + private readonly Slider slider; + + public DrawableSliderHead(Slider slider, HitCircle h) + : base(h) + { + this.slider = slider; + + Position = HitObject.Position - slider.Position; + } + + protected override void Update() + { + base.Update(); + + double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + + //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. + if (!IsHit) + Position = slider.CurvePositionAt(completionProgress); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index b277e7df7a..fee663963e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking - { - /// - /// The judgement text is provided by the . - /// - public override bool DisplayJudgement => false; - - public bool Tracking { get; set; } - - public DrawableSliderTail(Slider slider, HitCircle hitCircle) - : base(hitCircle) - { - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; - - AlwaysPresent = true; - - Position = HitObject.Position - slider.Position; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered && timeOffset >= 0) - AddJudgement(new OsuSliderTailJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking + { + /// + /// The judgement text is provided by the . + /// + public override bool DisplayJudgement => false; + + public bool Tracking { get; set; } + + public DrawableSliderTail(Slider slider, HitCircle hitCircle) + : base(hitCircle) + { + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + + AlwaysPresent = true; + + Position = HitObject.Position - slider.Position; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered && timeOffset >= 0) + AddJudgement(new OsuSliderTailJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 22bf63814c..db75321eb0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -1,75 +1,75 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableSliderTick : DrawableOsuHitObject, IRequireTracking - { - public const double ANIM_DURATION = 150; - - public bool Tracking { get; set; } - - public override bool DisplayJudgement => false; - - public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) - { - Size = new Vector2(16) * sliderTick.Scale; - - Masking = true; - CornerRadius = Size.X / 2; - - Origin = Anchor.Centre; - - BorderThickness = 2; - BorderColour = Color4.White; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = AccentColour, - Alpha = 0.3f, - } - }; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (timeOffset >= 0) - AddJudgement(new OsuJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); - } - - protected override void UpdatePreemptState() - { - this.FadeOut().FadeIn(ANIM_DURATION); - this.ScaleTo(0.5f).ScaleTo(1f, ANIM_DURATION * 4, Easing.OutElasticHalf); - } - - protected override void UpdateCurrentState(ArmedState state) - { - switch (state) - { - case ArmedState.Idle: - this.Delay(HitObject.TimePreempt).FadeOut(); - break; - case ArmedState.Miss: - this.FadeOut(ANIM_DURATION); - this.FadeColour(Color4.Red, ANIM_DURATION / 2); - break; - case ArmedState.Hit: - this.FadeOut(ANIM_DURATION, Easing.OutQuint); - this.ScaleTo(Scale * 1.5f, ANIM_DURATION, Easing.Out); - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSliderTick : DrawableOsuHitObject, IRequireTracking + { + public const double ANIM_DURATION = 150; + + public bool Tracking { get; set; } + + public override bool DisplayJudgement => false; + + public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) + { + Size = new Vector2(16) * sliderTick.Scale; + + Masking = true; + CornerRadius = Size.X / 2; + + Origin = Anchor.Centre; + + BorderThickness = 2; + BorderColour = Color4.White; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = AccentColour, + Alpha = 0.3f, + } + }; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + AddJudgement(new OsuJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); + } + + protected override void UpdatePreemptState() + { + this.FadeOut().FadeIn(ANIM_DURATION); + this.ScaleTo(0.5f).ScaleTo(1f, ANIM_DURATION * 4, Easing.OutElasticHalf); + } + + protected override void UpdateCurrentState(ArmedState state) + { + switch (state) + { + case ArmedState.Idle: + this.Delay(HitObject.TimePreempt).FadeOut(); + break; + case ArmedState.Miss: + this.FadeOut(ANIM_DURATION); + this.FadeColour(Color4.Red, ANIM_DURATION / 2); + break; + case ArmedState.Hit: + this.FadeOut(ANIM_DURATION, Easing.OutQuint); + this.ScaleTo(Scale * 1.5f, ANIM_DURATION, Easing.Out); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2705c213d9..f9d21ea5bb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -1,223 +1,223 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Allocation; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Screens.Ranking; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public class DrawableSpinner : DrawableOsuHitObject - { - protected readonly Spinner Spinner; - - public readonly SpinnerDisc Disc; - public readonly SpinnerTicks Ticks; - private readonly SpinnerSpmCounter spmCounter; - - private readonly Container mainContainer; - - public readonly SpinnerBackground Background; - private readonly Container circleContainer; - private readonly CirclePiece circle; - private readonly GlowPiece glow; - - private readonly SpriteIcon symbol; - - private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c"); - private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c"); - - private Color4 normalColour; - private Color4 completeColour; - - public DrawableSpinner(Spinner s) : base(s) - { - Origin = Anchor.Centre; - Position = s.Position; - - RelativeSizeAxes = Axes.Both; - - // we are slightly bigger than our parent, to clip the top and bottom of the circle - Height = 1.3f; - - Spinner = s; - - InternalChildren = new Drawable[] - { - circleContainer = new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - glow = new GlowPiece(), - circle = new CirclePiece - { - Position = Vector2.Zero, - Anchor = Anchor.Centre, - }, - new RingPiece(), - symbol = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(48), - Icon = FontAwesome.fa_asterisk, - Shadow = false, - }, - } - }, - mainContainer = new AspectContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - Background = new SpinnerBackground - { - Alpha = 0.6f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - Disc = new SpinnerDisc(Spinner) - { - Scale = Vector2.Zero, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - circleContainer.CreateProxy(), - Ticks = new SpinnerTicks - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - }, - spmCounter = new SpinnerSpmCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 120, - Alpha = 0 - } - }; - } - - public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (Time.Current < HitObject.StartTime) return; - - if (Progress >= 1 && !Disc.Complete) - { - Disc.Complete = true; - - const float duration = 200; - - Disc.FadeAccent(completeColour, duration); - - Background.FadeAccent(completeColour, duration); - Background.FadeOut(duration); - - circle.FadeColour(completeColour, duration); - glow.FadeColour(completeColour, duration); - } - - if (!userTriggered && Time.Current >= Spinner.EndTime) - { - if (Progress >= 1) - AddJudgement(new OsuJudgement { Result = HitResult.Great }); - else if (Progress > .9) - AddJudgement(new OsuJudgement { Result = HitResult.Good }); - else if (Progress > .75) - AddJudgement(new OsuJudgement { Result = HitResult.Meh }); - else if (Time.Current >= Spinner.EndTime) - AddJudgement(new OsuJudgement { Result = HitResult.Miss }); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - normalColour = baseColour; - - Background.AccentColour = normalColour; - - completeColour = colours.YellowLight.Opacity(0.75f); - - Disc.AccentColour = fillColour; - circle.Colour = colours.BlueDark; - glow.Colour = colours.BlueDark; - } - - protected override void Update() - { - Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); - if (!spmCounter.IsPresent && Disc.Tracking) - spmCounter.FadeIn(HitObject.TimeFadein); - - base.Update(); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - circle.Rotation = Disc.Rotation; - Ticks.Rotation = Disc.Rotation; - spmCounter.SetRotation(Disc.RotationAbsolute); - - float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; - Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); - - symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); - } - - protected override void UpdatePreemptState() - { - base.UpdatePreemptState(); - - circleContainer.ScaleTo(Spinner.Scale * 0.3f); - circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint); - - Disc.RotateTo(-720); - symbol.RotateTo(-720); - - mainContainer - .ScaleTo(0) - .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint) - .Then() - .ScaleTo(1, 500, Easing.OutQuint); - } - - protected override void UpdateCurrentState(ArmedState state) - { - var sequence = this.Delay(Spinner.Duration).FadeOut(160); - - switch (state) - { - case ArmedState.Hit: - sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); - break; - case ArmedState.Miss: - sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); - break; - } - - Expire(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Screens.Ranking; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSpinner : DrawableOsuHitObject + { + protected readonly Spinner Spinner; + + public readonly SpinnerDisc Disc; + public readonly SpinnerTicks Ticks; + private readonly SpinnerSpmCounter spmCounter; + + private readonly Container mainContainer; + + public readonly SpinnerBackground Background; + private readonly Container circleContainer; + private readonly CirclePiece circle; + private readonly GlowPiece glow; + + private readonly SpriteIcon symbol; + + private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c"); + private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c"); + + private Color4 normalColour; + private Color4 completeColour; + + public DrawableSpinner(Spinner s) : base(s) + { + Origin = Anchor.Centre; + Position = s.Position; + + RelativeSizeAxes = Axes.Both; + + // we are slightly bigger than our parent, to clip the top and bottom of the circle + Height = 1.3f; + + Spinner = s; + + InternalChildren = new Drawable[] + { + circleContainer = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + glow = new GlowPiece(), + circle = new CirclePiece + { + Position = Vector2.Zero, + Anchor = Anchor.Centre, + }, + new RingPiece(), + symbol = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(48), + Icon = FontAwesome.fa_asterisk, + Shadow = false, + }, + } + }, + mainContainer = new AspectContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + Background = new SpinnerBackground + { + Alpha = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + Disc = new SpinnerDisc(Spinner) + { + Scale = Vector2.Zero, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + circleContainer.CreateProxy(), + Ticks = new SpinnerTicks + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + spmCounter = new SpinnerSpmCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 120, + Alpha = 0 + } + }; + } + + public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (Time.Current < HitObject.StartTime) return; + + if (Progress >= 1 && !Disc.Complete) + { + Disc.Complete = true; + + const float duration = 200; + + Disc.FadeAccent(completeColour, duration); + + Background.FadeAccent(completeColour, duration); + Background.FadeOut(duration); + + circle.FadeColour(completeColour, duration); + glow.FadeColour(completeColour, duration); + } + + if (!userTriggered && Time.Current >= Spinner.EndTime) + { + if (Progress >= 1) + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + else if (Progress > .9) + AddJudgement(new OsuJudgement { Result = HitResult.Good }); + else if (Progress > .75) + AddJudgement(new OsuJudgement { Result = HitResult.Meh }); + else if (Time.Current >= Spinner.EndTime) + AddJudgement(new OsuJudgement { Result = HitResult.Miss }); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + normalColour = baseColour; + + Background.AccentColour = normalColour; + + completeColour = colours.YellowLight.Opacity(0.75f); + + Disc.AccentColour = fillColour; + circle.Colour = colours.BlueDark; + glow.Colour = colours.BlueDark; + } + + protected override void Update() + { + Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); + if (!spmCounter.IsPresent && Disc.Tracking) + spmCounter.FadeIn(HitObject.TimeFadein); + + base.Update(); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + circle.Rotation = Disc.Rotation; + Ticks.Rotation = Disc.Rotation; + spmCounter.SetRotation(Disc.RotationAbsolute); + + float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; + Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); + + symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); + } + + protected override void UpdatePreemptState() + { + base.UpdatePreemptState(); + + circleContainer.ScaleTo(Spinner.Scale * 0.3f); + circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint); + + Disc.RotateTo(-720); + symbol.RotateTo(-720); + + mainContainer + .ScaleTo(0) + .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint) + .Then() + .ScaleTo(1, 500, Easing.OutQuint); + } + + protected override void UpdateCurrentState(ArmedState state) + { + var sequence = this.Delay(Spinner.Duration).FadeOut(160); + + switch (state) + { + case ArmedState.Hit: + sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); + break; + case ArmedState.Miss: + sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); + break; + } + + Expire(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs index 98fc686dd3..e89ec1775b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - public interface IRequireTracking - { - /// - /// Whether the is currently being tracked by the user. - /// - bool Tracking { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public interface IRequireTracking + { + /// + /// Whether the is currently being tracked by the user. + /// + bool Tracking { get; set; } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs index b5fd87f60b..a05e51a460 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - /// - /// A component which tracks the current end snaking position of a slider. - /// - public interface ITrackSnaking - { - void UpdateSnakingPosition(Vector2 start, Vector2 end); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + /// + /// A component which tracks the current end snaking position of a slider. + /// + public interface ITrackSnaking + { + void UpdateSnakingPosition(Vector2 start, Vector2 end); + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs index 51f8b7026a..07d99bda42 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class ApproachCircle : Container - { - public ApproachCircle() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class ApproachCircle : Container + { + public ApproachCircle() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index e7b6598cf2..bd7a4ad3f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; -using osu.Game.Skinning; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class CirclePiece : Container, IKeyBindingHandler - { - public Func Hit; - - public CirclePiece() - { - Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2); - Masking = true; - CornerRadius = Size.X / 2; - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - InternalChild = new SkinnableDrawable("Play/osu/hitcircle", _ => new DefaultCirclePiece()); - } - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - return IsHovered && (Hit?.Invoke() ?? false); - } - - return false; - } - - public bool OnReleased(OsuAction action) => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Game.Skinning; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class CirclePiece : Container, IKeyBindingHandler + { + public Func Hit; + + public CirclePiece() + { + Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2); + Masking = true; + CornerRadius = Size.X / 2; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChild = new SkinnableDrawable("Play/osu/hitcircle", _ => new DefaultCirclePiece()); + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + return IsHovered && (Hit?.Invoke() ?? false); + } + + return false; + } + + public bool OnReleased(OsuAction action) => false; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs index 61f73b6d66..86b60c3443 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class DefaultCirclePiece : Container - { - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - RelativeSizeAxes = Axes.Both; - Children = new Drawable[] - { - new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = textures.Get(@"Play/osu/disc"), - }, - new TrianglesPiece - { - RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, - Alpha = 0.5f, - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class DefaultCirclePiece : Container + { + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + RelativeSizeAxes = Axes.Both; + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = textures.Get(@"Play/osu/disc"), + }, + new TrianglesPiece + { + RelativeSizeAxes = Axes.Both, + Blending = BlendingMode.Additive, + Alpha = 0.5f, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 28552e6c36..ed1b042939 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Skinning; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class ExplodePiece : Container - { - public ExplodePiece() - { - Size = new Vector2(128); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Blending = BlendingMode.Additive; - Alpha = 0; - - Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece - { - Blending = BlendingMode.Additive, - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - }, s => s.GetTexture("Play/osu/hitcircle") == null); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Skinning; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class ExplodePiece : Container + { + public ExplodePiece() + { + Size = new Vector2(128); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Blending = BlendingMode.Additive; + Alpha = 0; + + Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece + { + Blending = BlendingMode.Additive, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + }, s => s.GetTexture("Play/osu/hitcircle") == null); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index 50dc473750..0a1339a6ca 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using osu.Framework.Graphics.Shapes; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class FlashPiece : Container - { - public FlashPiece() - { - Size = new Vector2(128); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Blending = BlendingMode.Additive; - Alpha = 0; - - Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Child = new Box - { - RelativeSizeAxes = Axes.Both - } - }, s => s.GetTexture("Play/osu/hitcircle") == null); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Framework.Graphics.Shapes; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class FlashPiece : Container + { + public FlashPiece() + { + Size = new Vector2(128); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Blending = BlendingMode.Additive; + Alpha = 0; + + Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Child = new Box + { + RelativeSizeAxes = Axes.Both + } + }, s => s.GetTexture("Play/osu/hitcircle") == null); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs index 211e138b65..48b78bac40 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class GlowPiece : Container - { - public GlowPiece() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Child = new SkinnableDrawable("Play/osu/ring-glow", name => new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = textures.Get(name), - Blending = BlendingMode.Additive, - Alpha = 0.5f - }, s => s.GetTexture("Play/osu/hitcircle") == null); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class GlowPiece : Container + { + public GlowPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Child = new SkinnableDrawable("Play/osu/ring-glow", name => new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = textures.Get(name), + Blending = BlendingMode.Additive, + Alpha = 0.5f + }, s => s.GetTexture("Play/osu/hitcircle") == null); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 0c1fd4c364..30140484de 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -1,57 +1,57 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class NumberPiece : Container - { - private readonly SpriteText number; - - public string Text - { - get { return number.Text; } - set { number.Text = value; } - } - - public NumberPiece() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Children = new Drawable[] - { - new SkinnableDrawable("Play/osu/number-glow", name => new CircularContainer - { - Masking = true, - Origin = Anchor.Centre, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 60, - Colour = Color4.White.Opacity(0.5f), - }, - Child = new Box() - }, s => s.GetTexture("Play/osu/hitcircle") == null), - number = new OsuSpriteText - { - Text = @"1", - Font = @"Venera", - UseFullGlyphHeight = false, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - TextSize = 40, - Alpha = 1 - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class NumberPiece : Container + { + private readonly SpriteText number; + + public string Text + { + get { return number.Text; } + set { number.Text = value; } + } + + public NumberPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Children = new Drawable[] + { + new SkinnableDrawable("Play/osu/number-glow", name => new CircularContainer + { + Masking = true, + Origin = Anchor.Centre, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 60, + Colour = Color4.White.Opacity(0.5f), + }, + Child = new Box() + }, s => s.GetTexture("Play/osu/hitcircle") == null), + number = new OsuSpriteText + { + Text = @"1", + Font = @"Venera", + UseFullGlyphHeight = false, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 40, + Alpha = 1 + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index 12cc0dc5d9..f3e0a0ef43 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class RingPiece : Container - { - public RingPiece() - { - Size = new Vector2(128); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - InternalChild = new SkinnableDrawable("Play/osu/hitcircleoverlay", _ => new Container - { - Masking = true, - CornerRadius = Size.X / 2, - BorderThickness = 10, - BorderColour = Color4.White, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both - } - } - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class RingPiece : Container + { + public RingPiece() + { + Size = new Vector2(128); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChild = new SkinnableDrawable("Play/osu/hitcircleoverlay", _ => new Container + { + Masking = true, + CornerRadius = Size.X / 2, + BorderThickness = 10, + BorderColour = Color4.White, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both + } + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 1921c51889..894d972e47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -1,148 +1,148 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Rulesets.Objects.Types; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class SliderBall : CircularContainer, ISliderProgress - { - private const float width = 128; - - private Color4 accentColour = Color4.Black; - /// - /// The colour that is used for the slider ball. - /// - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - if (ball != null) - ball.Colour = value; - } - } - - private readonly Slider slider; - public readonly Box FollowCircle; - private readonly Box ball; - - public SliderBall(Slider slider) - { - this.slider = slider; - Masking = true; - AutoSizeAxes = Axes.Both; - Blending = BlendingMode.Additive; - Origin = Anchor.Centre; - BorderThickness = 10; - BorderColour = Color4.Orange; - - Children = new Drawable[] - { - FollowCircle = new Box - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = Color4.Orange, - Width = width, - Height = width, - Alpha = 0, - }, - new CircularContainer - { - Masking = true, - AutoSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - BorderThickness = 10, - BorderColour = Color4.White, - Alpha = 1, - Children = new[] - { - ball = new Box - { - Colour = AccentColour, - Alpha = 0.4f, - Width = width, - Height = width, - }, - } - } - }; - } - - private InputState lastState; - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - lastState = state; - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - lastState = state; - return base.OnMouseUp(state, args); - } - - protected override bool OnMouseMove(InputState state) - { - lastState = state; - return base.OnMouseMove(state); - } - - // If the current time is between the start and end of the slider, we should track mouse input regardless of the cursor position. - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => canCurrentlyTrack || base.ReceiveMouseInputAt(screenSpacePos); - - public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) - { - // Consider the case of rewinding - children's transforms are handled internally, so propagating down - // any further will cause weirdness with the Tracking bool below. Let's not propagate further at this point. - base.ClearTransformsAfter(time, false, targetMember); - } - - private bool tracking; - public bool Tracking - { - get { return tracking; } - private set - { - if (value == tracking) - return; - tracking = value; - - FollowCircle.ScaleTo(tracking ? 2.8f : 1, 300, Easing.OutQuint); - FollowCircle.FadeTo(tracking ? 0.2f : 0, 300, Easing.OutQuint); - } - } - - private bool canCurrentlyTrack => Time.Current >= slider.StartTime && Time.Current < slider.EndTime; - - protected override void Update() - { - base.Update(); - - if (Time.Current < slider.EndTime) - { - // Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position. - Tracking = canCurrentlyTrack - && lastState != null - && base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) - && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); - } - } - - public void UpdateProgress(double completionProgress) - { - Position = slider.CurvePositionAt(completionProgress); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class SliderBall : CircularContainer, ISliderProgress + { + private const float width = 128; + + private Color4 accentColour = Color4.Black; + /// + /// The colour that is used for the slider ball. + /// + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + if (ball != null) + ball.Colour = value; + } + } + + private readonly Slider slider; + public readonly Box FollowCircle; + private readonly Box ball; + + public SliderBall(Slider slider) + { + this.slider = slider; + Masking = true; + AutoSizeAxes = Axes.Both; + Blending = BlendingMode.Additive; + Origin = Anchor.Centre; + BorderThickness = 10; + BorderColour = Color4.Orange; + + Children = new Drawable[] + { + FollowCircle = new Box + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = Color4.Orange, + Width = width, + Height = width, + Alpha = 0, + }, + new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + BorderThickness = 10, + BorderColour = Color4.White, + Alpha = 1, + Children = new[] + { + ball = new Box + { + Colour = AccentColour, + Alpha = 0.4f, + Width = width, + Height = width, + }, + } + } + }; + } + + private InputState lastState; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + lastState = state; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + lastState = state; + return base.OnMouseUp(state, args); + } + + protected override bool OnMouseMove(InputState state) + { + lastState = state; + return base.OnMouseMove(state); + } + + // If the current time is between the start and end of the slider, we should track mouse input regardless of the cursor position. + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => canCurrentlyTrack || base.ReceiveMouseInputAt(screenSpacePos); + + public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) + { + // Consider the case of rewinding - children's transforms are handled internally, so propagating down + // any further will cause weirdness with the Tracking bool below. Let's not propagate further at this point. + base.ClearTransformsAfter(time, false, targetMember); + } + + private bool tracking; + public bool Tracking + { + get { return tracking; } + private set + { + if (value == tracking) + return; + tracking = value; + + FollowCircle.ScaleTo(tracking ? 2.8f : 1, 300, Easing.OutQuint); + FollowCircle.FadeTo(tracking ? 0.2f : 0, 300, Easing.OutQuint); + } + } + + private bool canCurrentlyTrack => Time.Current >= slider.StartTime && Time.Current < slider.EndTime; + + protected override void Update() + { + base.Update(); + + if (Time.Current < slider.EndTime) + { + // Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position. + Tracking = canCurrentlyTrack + && lastState != null + && base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) + && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + } + } + + public void UpdateProgress(double completionProgress) + { + Position = slider.CurvePositionAt(completionProgress); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index c59c22c771..0a6b1b459a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -1,233 +1,233 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.OpenGL.Textures; -using osu.Framework.Graphics.Lines; -using osu.Framework.Graphics.Textures; -using OpenTK; -using OpenTK.Graphics.ES30; -using OpenTK.Graphics; -using osu.Framework.Graphics.Primitives; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class SliderBody : Container, ISliderProgress - { - private readonly Path path; - private readonly BufferedContainer container; - - public float PathWidth - { - get { return path.PathWidth; } - set { path.PathWidth = value; } - } - - /// - /// Offset in absolute coordinates from the start of the curve. - /// - public Vector2 PathOffset { get; private set; } - - public readonly List CurrentCurve = new List(); - - public readonly Bindable SnakingIn = new Bindable(); - public readonly Bindable SnakingOut = new Bindable(); - - public double? SnakedStart { get; private set; } - public double? SnakedEnd { get; private set; } - - private Color4 accentColour = Color4.White; - /// - /// Used to colour the path. - /// - public Color4 AccentColour - { - get { return accentColour; } - set - { - if (accentColour == value) - return; - accentColour = value; - - if (LoadState >= LoadState.Ready) - reloadTexture(); - } - } - - private Color4 borderColour = Color4.White; - /// - /// Used to colour the path border. - /// - public new Color4 BorderColour - { - get { return borderColour; } - set - { - if (borderColour == value) - return; - borderColour = value; - - if (LoadState >= LoadState.Ready) - reloadTexture(); - } - } - - public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; - - private int textureWidth => (int)PathWidth * 2; - - private Vector2 topLeftOffset; - - private readonly Slider slider; - public SliderBody(Slider s) - { - slider = s; - - Children = new Drawable[] - { - container = new BufferedContainer - { - RelativeSizeAxes = Axes.Both, - CacheDrawnFrameBuffer = true, - Children = new Drawable[] - { - path = new Path - { - Blending = BlendingMode.None, - }, - } - }, - }; - - container.Attach(RenderbufferInternalFormat.DepthComponent16); - } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos); - - public void SetRange(double p0, double p1) - { - if (p0 > p1) - MathHelper.Swap(ref p0, ref p1); - - if (updateSnaking(p0, p1)) - { - // The path is generated such that its size encloses it. This change of size causes the path - // to move around while snaking, so we need to offset it to make sure it maintains the - // same position as when it is fully snaked. - var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero); - path.Position = topLeftOffset - newTopLeftOffset; - - container.ForceRedraw(); - } - } - - [BackgroundDependencyLoader] - private void load() - { - reloadTexture(); - computeSize(); - } - - private void reloadTexture() - { - var texture = new Texture(textureWidth, 1); - - //initialise background - var upload = new TextureUpload(textureWidth * 4); - var bytes = upload.Data; - - const float aa_portion = 0.02f; - const float border_portion = 0.128f; - const float gradient_portion = 1 - border_portion; - - const float opacity_at_centre = 0.3f; - const float opacity_at_edge = 0.8f; - - for (int i = 0; i < textureWidth; i++) - { - float progress = (float)i / (textureWidth - 1); - - if (progress <= border_portion) - { - bytes[i * 4] = (byte)(BorderColour.R * 255); - bytes[i * 4 + 1] = (byte)(BorderColour.G * 255); - bytes[i * 4 + 2] = (byte)(BorderColour.B * 255); - bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255)); - } - else - { - progress -= border_portion; - - bytes[i * 4] = (byte)(AccentColour.R * 255); - bytes[i * 4 + 1] = (byte)(AccentColour.G * 255); - bytes[i * 4 + 2] = (byte)(AccentColour.B * 255); - bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (AccentColour.A * 255)); - } - } - - texture.SetData(upload); - path.Texture = texture; - - container.ForceRedraw(); - } - - private void computeSize() - { - // Generate the entire curve - slider.Curve.GetPathToProgress(CurrentCurve, 0, 1); - foreach (Vector2 p in CurrentCurve) - path.AddVertex(p); - - Size = path.Size; - - topLeftOffset = path.PositionInBoundingBox(Vector2.Zero); - PathOffset = path.PositionInBoundingBox(CurrentCurve[0]); - } - - private bool updateSnaking(double p0, double p1) - { - if (SnakedStart == p0 && SnakedEnd == p1) return false; - - SnakedStart = p0; - SnakedEnd = p1; - - slider.Curve.GetPathToProgress(CurrentCurve, p0, p1); - - path.ClearVertices(); - foreach (Vector2 p in CurrentCurve) - path.AddVertex(p); - - return true; - } - - public void UpdateProgress(double completionProgress) - { - var span = slider.SpanAt(completionProgress); - var spanProgress = slider.ProgressAt(completionProgress); - - double start = 0; - double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; - - if (span >= slider.SpanCount() - 1) - { - if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) - { - start = 0; - end = SnakingOut ? spanProgress : 1; - } - else - { - start = SnakingOut ? spanProgress : 0; - } - } - - SetRange(start, end); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Textures; +using OpenTK; +using OpenTK.Graphics.ES30; +using OpenTK.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class SliderBody : Container, ISliderProgress + { + private readonly Path path; + private readonly BufferedContainer container; + + public float PathWidth + { + get { return path.PathWidth; } + set { path.PathWidth = value; } + } + + /// + /// Offset in absolute coordinates from the start of the curve. + /// + public Vector2 PathOffset { get; private set; } + + public readonly List CurrentCurve = new List(); + + public readonly Bindable SnakingIn = new Bindable(); + public readonly Bindable SnakingOut = new Bindable(); + + public double? SnakedStart { get; private set; } + public double? SnakedEnd { get; private set; } + + private Color4 accentColour = Color4.White; + /// + /// Used to colour the path. + /// + public Color4 AccentColour + { + get { return accentColour; } + set + { + if (accentColour == value) + return; + accentColour = value; + + if (LoadState >= LoadState.Ready) + reloadTexture(); + } + } + + private Color4 borderColour = Color4.White; + /// + /// Used to colour the path border. + /// + public new Color4 BorderColour + { + get { return borderColour; } + set + { + if (borderColour == value) + return; + borderColour = value; + + if (LoadState >= LoadState.Ready) + reloadTexture(); + } + } + + public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; + + private int textureWidth => (int)PathWidth * 2; + + private Vector2 topLeftOffset; + + private readonly Slider slider; + public SliderBody(Slider s) + { + slider = s; + + Children = new Drawable[] + { + container = new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + CacheDrawnFrameBuffer = true, + Children = new Drawable[] + { + path = new Path + { + Blending = BlendingMode.None, + }, + } + }, + }; + + container.Attach(RenderbufferInternalFormat.DepthComponent16); + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos); + + public void SetRange(double p0, double p1) + { + if (p0 > p1) + MathHelper.Swap(ref p0, ref p1); + + if (updateSnaking(p0, p1)) + { + // The path is generated such that its size encloses it. This change of size causes the path + // to move around while snaking, so we need to offset it to make sure it maintains the + // same position as when it is fully snaked. + var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero); + path.Position = topLeftOffset - newTopLeftOffset; + + container.ForceRedraw(); + } + } + + [BackgroundDependencyLoader] + private void load() + { + reloadTexture(); + computeSize(); + } + + private void reloadTexture() + { + var texture = new Texture(textureWidth, 1); + + //initialise background + var upload = new TextureUpload(textureWidth * 4); + var bytes = upload.Data; + + const float aa_portion = 0.02f; + const float border_portion = 0.128f; + const float gradient_portion = 1 - border_portion; + + const float opacity_at_centre = 0.3f; + const float opacity_at_edge = 0.8f; + + for (int i = 0; i < textureWidth; i++) + { + float progress = (float)i / (textureWidth - 1); + + if (progress <= border_portion) + { + bytes[i * 4] = (byte)(BorderColour.R * 255); + bytes[i * 4 + 1] = (byte)(BorderColour.G * 255); + bytes[i * 4 + 2] = (byte)(BorderColour.B * 255); + bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255)); + } + else + { + progress -= border_portion; + + bytes[i * 4] = (byte)(AccentColour.R * 255); + bytes[i * 4 + 1] = (byte)(AccentColour.G * 255); + bytes[i * 4 + 2] = (byte)(AccentColour.B * 255); + bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (AccentColour.A * 255)); + } + } + + texture.SetData(upload); + path.Texture = texture; + + container.ForceRedraw(); + } + + private void computeSize() + { + // Generate the entire curve + slider.Curve.GetPathToProgress(CurrentCurve, 0, 1); + foreach (Vector2 p in CurrentCurve) + path.AddVertex(p); + + Size = path.Size; + + topLeftOffset = path.PositionInBoundingBox(Vector2.Zero); + PathOffset = path.PositionInBoundingBox(CurrentCurve[0]); + } + + private bool updateSnaking(double p0, double p1) + { + if (SnakedStart == p0 && SnakedEnd == p1) return false; + + SnakedStart = p0; + SnakedEnd = p1; + + slider.Curve.GetPathToProgress(CurrentCurve, p0, p1); + + path.ClearVertices(); + foreach (Vector2 p in CurrentCurve) + path.AddVertex(p); + + return true; + } + + public void UpdateProgress(double completionProgress) + { + var span = slider.SpanAt(completionProgress); + var spanProgress = slider.ProgressAt(completionProgress); + + double start = 0; + double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; + + if (span >= slider.SpanCount() - 1) + { + if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) + { + start = 0; + end = SnakingOut ? spanProgress : 1; + } + else + { + start = SnakingOut ? spanProgress : 0; + } + } + + SetRange(start, end); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index c44d7594ad..1a7455838f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -1,56 +1,56 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class SpinnerBackground : CircularContainer, IHasAccentColour - { - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - - protected Box Disc; - - public Color4 AccentColour - { - get - { - return Disc.Colour; - } - set - { - Disc.Colour = value; - - EdgeEffect = new EdgeEffectParameters - { - Hollow = true, - Type = EdgeEffectType.Glow, - Radius = 40, - Colour = value, - }; - } - } - - public SpinnerBackground() - { - RelativeSizeAxes = Axes.Both; - Masking = true; - - Children = new Drawable[] - { - Disc = new Box - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 1, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class SpinnerBackground : CircularContainer, IHasAccentColour + { + public override bool HandleKeyboardInput => false; + public override bool HandleMouseInput => false; + + protected Box Disc; + + public Color4 AccentColour + { + get + { + return Disc.Colour; + } + set + { + Disc.Colour = value; + + EdgeEffect = new EdgeEffectParameters + { + Hollow = true, + Type = EdgeEffectType.Glow, + Radius = 40, + Colour = value, + }; + } + } + + public SpinnerBackground() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + Children = new Drawable[] + { + Disc = new Box + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 1, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 971914ca13..bb5fa1b575 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -1,127 +1,127 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class SpinnerDisc : CircularContainer, IHasAccentColour - { - private readonly Spinner spinner; - - public Color4 AccentColour - { - get { return background.AccentColour; } - set { background.AccentColour = value; } - } - - private readonly SpinnerBackground background; - - private const float idle_alpha = 0.2f; - private const float tracking_alpha = 0.4f; - - public override bool IsPresent => true; // handle input when hidden - - public SpinnerDisc(Spinner s) - { - spinner = s; - - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - background = new SpinnerBackground { Alpha = idle_alpha }, - }; - } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - - private bool tracking; - public bool Tracking - { - get { return tracking; } - set - { - if (value == tracking) return; - tracking = value; - - background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); - } - } - - private bool complete; - public bool Complete - { - get { return complete; } - set - { - if (value == complete) return; - complete = value; - - updateCompleteTick(); - } - } - - protected override bool OnMouseMove(InputState state) - { - mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position); - return base.OnMouseMove(state); - } - - private Vector2 mousePosition; - - private float lastAngle; - private float currentRotation; - public float RotationAbsolute; - private int completeTick; - - private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); - - private bool rotationTransferred; - - protected override void Update() - { - base.Update(); - - var thisAngle = -(float)MathHelper.RadiansToDegrees(Math.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); - - bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; - - if (validAndTracking) - { - if (!rotationTransferred) - { - currentRotation = Rotation * 2; - rotationTransferred = true; - } - - if (thisAngle - lastAngle > 180) - lastAngle += 360; - else if (lastAngle - thisAngle > 180) - lastAngle -= 360; - - currentRotation += thisAngle - lastAngle; - RotationAbsolute += Math.Abs(thisAngle - lastAngle); - } - - lastAngle = thisAngle; - - if (Complete && updateCompleteTick()) - { - background.FinishTransforms(false, nameof(Alpha)); - background - .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) - .Then() - .FadeTo(tracking_alpha, 250, Easing.OutQuint); - } - - this.RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, Easing.OutExpo); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class SpinnerDisc : CircularContainer, IHasAccentColour + { + private readonly Spinner spinner; + + public Color4 AccentColour + { + get { return background.AccentColour; } + set { background.AccentColour = value; } + } + + private readonly SpinnerBackground background; + + private const float idle_alpha = 0.2f; + private const float tracking_alpha = 0.4f; + + public override bool IsPresent => true; // handle input when hidden + + public SpinnerDisc(Spinner s) + { + spinner = s; + + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + background = new SpinnerBackground { Alpha = idle_alpha }, + }; + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + + private bool tracking; + public bool Tracking + { + get { return tracking; } + set + { + if (value == tracking) return; + tracking = value; + + background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); + } + } + + private bool complete; + public bool Complete + { + get { return complete; } + set + { + if (value == complete) return; + complete = value; + + updateCompleteTick(); + } + } + + protected override bool OnMouseMove(InputState state) + { + mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position); + return base.OnMouseMove(state); + } + + private Vector2 mousePosition; + + private float lastAngle; + private float currentRotation; + public float RotationAbsolute; + private int completeTick; + + private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); + + private bool rotationTransferred; + + protected override void Update() + { + base.Update(); + + var thisAngle = -(float)MathHelper.RadiansToDegrees(Math.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); + + bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + + if (validAndTracking) + { + if (!rotationTransferred) + { + currentRotation = Rotation * 2; + rotationTransferred = true; + } + + if (thisAngle - lastAngle > 180) + lastAngle += 360; + else if (lastAngle - thisAngle > 180) + lastAngle -= 360; + + currentRotation += thisAngle - lastAngle; + RotationAbsolute += Math.Abs(thisAngle - lastAngle); + } + + lastAngle = thisAngle; + + if (Complete && updateCompleteTick()) + { + background.FinishTransforms(false, nameof(Alpha)); + background + .FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo) + .Then() + .FadeTo(tracking_alpha, 250, Easing.OutQuint); + } + + this.RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, Easing.OutExpo); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs index 182cc9d56a..0bdd4296fb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs @@ -1,80 +1,80 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class SpinnerSpmCounter : Container - { - private readonly OsuSpriteText spmText; - - public SpinnerSpmCounter() - { - Children = new Drawable[] - { - spmText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"0", - Font = @"Venera", - TextSize = 24 - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"SPINS PER MINUTE", - Font = @"Venera", - TextSize = 12, - Y = 30 - } - }; - } - - private double spm; - - public double SpinsPerMinute - { - get { return spm; } - private set - { - if (value == spm) return; - spm = value; - spmText.Text = Math.Truncate(value).ToString(@"#0"); - } - } - - private struct RotationRecord - { - public float Rotation; - public double Time; - } - - private readonly Queue records = new Queue(); - private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues - - public void SetRotation(float currentRotation) - { - // If we've gone back in time, it's fine to work with a fresh set of records for now - if (records.Count > 0 && Time.Current < records.Last().Time) - records.Clear(); - - if (records.Count > 0) - { - var record = records.Peek(); - while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration) - record = records.Dequeue(); - SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; - } - - records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class SpinnerSpmCounter : Container + { + private readonly OsuSpriteText spmText; + + public SpinnerSpmCounter() + { + Children = new Drawable[] + { + spmText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"0", + Font = @"Venera", + TextSize = 24 + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"SPINS PER MINUTE", + Font = @"Venera", + TextSize = 12, + Y = 30 + } + }; + } + + private double spm; + + public double SpinsPerMinute + { + get { return spm; } + private set + { + if (value == spm) return; + spm = value; + spmText.Text = Math.Truncate(value).ToString(@"#0"); + } + } + + private struct RotationRecord + { + public float Rotation; + public double Time; + } + + private readonly Queue records = new Queue(); + private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues + + public void SetRotation(float currentRotation) + { + // If we've gone back in time, it's fine to work with a fresh set of records for now + if (records.Count > 0 && Time.Current < records.Last().Time) + records.Clear(); + + if (records.Count > 0) + { + var record = records.Peek(); + while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration) + record = records.Dequeue(); + SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; + } + + records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index 135fbbd8db..61387d796e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -1,57 +1,57 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class SpinnerTicks : Container - { - public SpinnerTicks() - { - Origin = Anchor.Centre; - Anchor = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - - const int count = 18; - - for (int i = 0; i < count; i++) - { - Add(new Container - { - Colour = Color4.Black, - Alpha = 0.4f, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 10, - Colour = Color4.Gray.Opacity(0.2f), - }, - RelativePositionAxes = Axes.Both, - Masking = true, - CornerRadius = 5, - Size = new Vector2(60, 10), - Origin = Anchor.Centre, - Position = new Vector2( - 0.5f + (float)Math.Sin((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f, - 0.5f + (float)Math.Cos((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f - ), - Rotation = -(float)i / count * 360 + 90, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - } - } - }); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class SpinnerTicks : Container + { + public SpinnerTicks() + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + + const int count = 18; + + for (int i = 0; i < count; i++) + { + Add(new Container + { + Colour = Color4.Black, + Alpha = 0.4f, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 10, + Colour = Color4.Gray.Opacity(0.2f), + }, + RelativePositionAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Size = new Vector2(60, 10), + Origin = Anchor.Centre, + Position = new Vector2( + 0.5f + (float)Math.Sin((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f, + 0.5f + (float)Math.Cos((float)i / count * 2 * MathHelper.Pi) / 2 * 0.86f + ), + Rotation = -(float)i / count * 360 + 90, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index 4d3397de3e..59b204bdaf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics.Backgrounds; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces -{ - public class TrianglesPiece : Triangles - { - protected override bool ExpireOffScreenTriangles => false; - protected override bool CreateNewTriangles => false; - protected override float SpawnRatio => 0.5f; - - public TrianglesPiece() - { - TriangleScale = 1.2f; - HideAlphaDiscrepancies = false; - } - - protected override void Update() - { - if (IsPresent) - base.Update(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public class TrianglesPiece : Triangles + { + protected override bool ExpireOffScreenTriangles => false; + protected override bool CreateNewTriangles => false; + protected override float SpawnRatio => 0.5f; + + public TrianglesPiece() + { + TriangleScale = 1.2f; + HideAlphaDiscrepancies = false; + } + + protected override void Update() + { + if (IsPresent) + base.Update(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs index 9f6041cb70..9e309a376d 100644 --- a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Osu.Objects -{ - public class HitCircle : OsuHitObject - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class HitCircle : OsuHitObject + { + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs index a0566eaf17..43ec7ae006 100644 --- a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs +++ b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Osu.Objects -{ - public interface ISliderProgress - { - /// - /// Updates the progress of this element along the slider. - /// - /// Amount of the slider completed. - void UpdateProgress(double completionProgress); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Osu.Objects +{ + public interface ISliderProgress + { + /// + /// Updates the progress of this element along the slider. + /// + /// Amount of the slider completed. + void UpdateProgress(double completionProgress); + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index f64db6ba9e..2b7b7783e2 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -1,75 +1,75 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using OpenTK; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit.Types; - -namespace osu.Game.Rulesets.Osu.Objects -{ - public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasEditablePosition - { - public const double OBJECT_RADIUS = 64; - - public event Action PositionChanged; - - public double TimePreempt = 600; - public double TimeFadein = 400; - - private Vector2 position; - - public Vector2 Position - { - get => position; - set - { - if (position == value) - return; - position = value; - - PositionChanged?.Invoke(value); - } - } - - public float X => Position.X; - public float Y => Position.Y; - - public Vector2 StackedPosition => Position + StackOffset; - - public virtual Vector2 EndPosition => Position; - - public Vector2 StackedEndPosition => EndPosition + StackOffset; - - public virtual int StackHeight { get; set; } - - public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); - - public double Radius => OBJECT_RADIUS * Scale; - - public float Scale { get; set; } = 1; - - public virtual bool NewCombo { get; set; } - - public int IndexInCurrentCombo { get; set; } - - public int ComboIndex { get; set; } - - public bool LastInCombo { get; set; } - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); - - Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; - } - - public virtual void OffsetPosition(Vector2 offset) => Position += offset; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using OpenTK; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Types; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasEditablePosition + { + public const double OBJECT_RADIUS = 64; + + public event Action PositionChanged; + + public double TimePreempt = 600; + public double TimeFadein = 400; + + private Vector2 position; + + public Vector2 Position + { + get => position; + set + { + if (position == value) + return; + position = value; + + PositionChanged?.Invoke(value); + } + } + + public float X => Position.X; + public float Y => Position.Y; + + public Vector2 StackedPosition => Position + StackOffset; + + public virtual Vector2 EndPosition => Position; + + public Vector2 StackedEndPosition => EndPosition + StackOffset; + + public virtual int StackHeight { get; set; } + + public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); + + public double Radius => OBJECT_RADIUS * Scale; + + public float Scale { get; set; } = 1; + + public virtual bool NewCombo { get; set; } + + public int IndexInCurrentCombo { get; set; } + + public int ComboIndex { get; set; } + + public bool LastInCombo { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); + TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); + + Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; + } + + public virtual void OffsetPosition(Vector2 offset) => Position += offset; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs index eaaa8d7a7e..0b729f0956 100644 --- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Osu.Objects -{ - public class RepeatPoint : OsuHitObject - { - public int RepeatIndex { get; set; } - public double SpanDuration { get; set; } - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders - // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. - if (RepeatIndex > 0) - TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class RepeatPoint : OsuHitObject + { + public int RepeatIndex { get; set; } + public double SpanDuration { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders + // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. + if (RepeatIndex > 0) + TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 469c4ddcb4..7f4407370f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -1,186 +1,186 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Rulesets.Objects.Types; -using System.Collections.Generic; -using osu.Game.Rulesets.Objects; -using System.Linq; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Osu.Objects -{ - public class Slider : OsuHitObject, IHasCurve - { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; - public double Duration => EndTime - StartTime; - - public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public override Vector2 EndPosition => Position + this.CurvePositionAt(1); - - public SliderCurve Curve { get; } = new SliderCurve(); - - public List ControlPoints - { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } - } - - public CurveType CurveType - { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } - } - - public double Distance - { - get { return Curve.Distance; } - set { Curve.Distance = value; } - } - - /// - /// The position of the cursor at the point of completion of this if it was hit - /// with as few movements as possible. This is set and used by difficulty calculation. - /// - internal Vector2? LazyEndPosition; - - /// - /// The distance travelled by the cursor upon completion of this if it was hit - /// with as few movements as possible. This is set and used by difficulty calculation. - /// - internal float LazyTravelDistance; - - public List> RepeatSamples { get; set; } = new List>(); - public int RepeatCount { get; set; } - - /// - /// The length of one span of this . - /// - public double SpanDuration => Duration / this.SpanCount(); - - public double Velocity; - public double TickDistance; - - public HitCircle HeadCircle; - public HitCircle TailCircle; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = scoringDistance / difficulty.SliderTickRate; - } - - protected override void CreateNestedHitObjects() - { - base.CreateNestedHitObjects(); - - createSliderEnds(); - createTicks(); - createRepeatPoints(); - } - - private void createSliderEnds() - { - HeadCircle = new SliderCircle(this) - { - StartTime = StartTime, - Position = Position, - Samples = Samples, - SampleControlPoint = SampleControlPoint, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; - - TailCircle = new SliderCircle(this) - { - StartTime = EndTime, - Position = EndPosition, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; - - AddNested(HeadCircle); - AddNested(TailCircle); - } - - private void createTicks() - { - var length = Curve.Distance; - var tickDistance = MathHelper.Clamp(TickDistance, 0, length); - - if (tickDistance == 0) return; - - var minDistanceFromEnd = Velocity * 0.01; - - var spanCount = this.SpanCount(); - - for (var span = 0; span < spanCount; span++) - { - var spanStartTime = StartTime + span * SpanDuration; - var reversed = span % 2 == 1; - - for (var d = tickDistance; d <= length; d += tickDistance) - { - if (d > length - minDistanceFromEnd) - break; - - var distanceProgress = d / length; - var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - - var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - var sampleList = new List(); - - if (firstSample != null) - sampleList.Add(new SampleInfo - { - Bank = firstSample.Bank, - Volume = firstSample.Volume, - Name = @"slidertick", - }); - - AddNested(new SliderTick - { - SpanIndex = span, - SpanStartTime = spanStartTime, - StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Position + Curve.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - Samples = sampleList - }); - } - } - } - - private void createRepeatPoints() - { - for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) - { - AddNested(new RepeatPoint - { - RepeatIndex = repeatIndex, - SpanDuration = SpanDuration, - StartTime = StartTime + repeat * SpanDuration, - Position = Position + Curve.PositionAt(repeat % 2), - StackHeight = StackHeight, - Scale = Scale, - Samples = new List(RepeatSamples[repeatIndex]) - }); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class Slider : OsuHitObject, IHasCurve + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + public double Duration => EndTime - StartTime; + + public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); + public override Vector2 EndPosition => Position + this.CurvePositionAt(1); + + public SliderCurve Curve { get; } = new SliderCurve(); + + public List ControlPoints + { + get { return Curve.ControlPoints; } + set { Curve.ControlPoints = value; } + } + + public CurveType CurveType + { + get { return Curve.CurveType; } + set { Curve.CurveType = value; } + } + + public double Distance + { + get { return Curve.Distance; } + set { Curve.Distance = value; } + } + + /// + /// The position of the cursor at the point of completion of this if it was hit + /// with as few movements as possible. This is set and used by difficulty calculation. + /// + internal Vector2? LazyEndPosition; + + /// + /// The distance travelled by the cursor upon completion of this if it was hit + /// with as few movements as possible. This is set and used by difficulty calculation. + /// + internal float LazyTravelDistance; + + public List> RepeatSamples { get; set; } = new List>(); + public int RepeatCount { get; set; } + + /// + /// The length of one span of this . + /// + public double SpanDuration => Duration / this.SpanCount(); + + public double Velocity; + public double TickDistance; + + public HitCircle HeadCircle; + public HitCircle TailCircle; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + Velocity = scoringDistance / timingPoint.BeatLength; + TickDistance = scoringDistance / difficulty.SliderTickRate; + } + + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + + createSliderEnds(); + createTicks(); + createRepeatPoints(); + } + + private void createSliderEnds() + { + HeadCircle = new SliderCircle(this) + { + StartTime = StartTime, + Position = Position, + Samples = Samples, + SampleControlPoint = SampleControlPoint, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboIndex = ComboIndex, + }; + + TailCircle = new SliderCircle(this) + { + StartTime = EndTime, + Position = EndPosition, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboIndex = ComboIndex, + }; + + AddNested(HeadCircle); + AddNested(TailCircle); + } + + private void createTicks() + { + var length = Curve.Distance; + var tickDistance = MathHelper.Clamp(TickDistance, 0, length); + + if (tickDistance == 0) return; + + var minDistanceFromEnd = Velocity * 0.01; + + var spanCount = this.SpanCount(); + + for (var span = 0; span < spanCount; span++) + { + var spanStartTime = StartTime + span * SpanDuration; + var reversed = span % 2 == 1; + + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d > length - minDistanceFromEnd) + break; + + var distanceProgress = d / length; + var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; + + var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + var sampleList = new List(); + + if (firstSample != null) + sampleList.Add(new SampleInfo + { + Bank = firstSample.Bank, + Volume = firstSample.Volume, + Name = @"slidertick", + }); + + AddNested(new SliderTick + { + SpanIndex = span, + SpanStartTime = spanStartTime, + StartTime = spanStartTime + timeProgress * SpanDuration, + Position = Position + Curve.PositionAt(distanceProgress), + StackHeight = StackHeight, + Scale = Scale, + Samples = sampleList + }); + } + } + } + + private void createRepeatPoints() + { + for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) + { + AddNested(new RepeatPoint + { + RepeatIndex = repeatIndex, + SpanDuration = SpanDuration, + StartTime = StartTime + repeat * SpanDuration, + Position = Position + Curve.PositionAt(repeat % 2), + StackHeight = StackHeight, + Scale = Scale, + Samples = new List(RepeatSamples[repeatIndex]) + }); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs index 1e83d02735..1bdd16c9df 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Objects -{ - public class SliderCircle : HitCircle - { - private readonly Slider slider; - - public SliderCircle(Slider slider) - { - this.slider = slider; - } - - public override void OffsetPosition(Vector2 offset) => slider.OffsetPosition(offset); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class SliderCircle : HitCircle + { + private readonly Slider slider; + + public SliderCircle(Slider slider) + { + this.slider = slider; + } + + public override void OffsetPosition(Vector2 offset) => slider.OffsetPosition(offset); + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 966db73eb9..4db6eb9883 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -1,30 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Osu.Objects -{ - public class SliderTick : OsuHitObject - { - public int SpanIndex { get; set; } - public double SpanStartTime { get; set; } - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - double offset; - - if (SpanIndex > 0) - // Adding 200 to include the offset stable used. - // This is so on repeats ticks don't appear too late to be visually processed by the player. - offset = 200; - else - offset = TimeFadein * 0.66f; - - TimePreempt = (StartTime - SpanStartTime) / 2 + offset; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class SliderTick : OsuHitObject + { + public int SpanIndex { get; set; } + public double SpanStartTime { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + double offset; + + if (SpanIndex > 0) + // Adding 200 to include the offset stable used. + // This is so on repeats ticks don't appear too late to be visually processed by the player. + offset = 200; + else + offset = TimeFadein * 0.66f; + + TimePreempt = (StartTime - SpanStartTime) / 2 + offset; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index b30e4cb932..503ad85674 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Osu.Objects -{ - public class Spinner : OsuHitObject, IHasEndTime - { - public double EndTime { get; set; } - public double Duration => EndTime - StartTime; - - /// - /// Number of spins required to finish the spinner without miss. - /// - public int SpinsRequired { get; protected set; } = 1; - - public override bool NewCombo => true; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5)); - - // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being. - SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class Spinner : OsuHitObject, IHasEndTime + { + public double EndTime { get; set; } + public double Duration => EndTime - StartTime; + + /// + /// Number of spins required to finish the spinner without miss. + /// + public int SpinsRequired { get; protected set; } = 1; + + public override bool NewCombo => true; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5)); + + // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being. + SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs index fb5acbb643..926a7975f3 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs @@ -1,78 +1,78 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; -using osu.Game.Rulesets.Osu.OsuDifficulty.Skills; - -namespace osu.Game.Rulesets.Osu.OsuDifficulty -{ - public class OsuDifficultyCalculator : DifficultyCalculator - { - private const int section_length = 400; - private const double difficulty_multiplier = 0.0675; - - public OsuDifficultyCalculator(Beatmap beatmap) - : base(beatmap) - { - } - - public OsuDifficultyCalculator(Beatmap beatmap, Mod[] mods) - : base(beatmap, mods) - { - } - - protected override void PreprocessHitObjects() - { - new OsuBeatmapProcessor().PostProcess(Beatmap); - } - - public override double Calculate(Dictionary categoryDifficulty = null) - { - OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate); - Skill[] skills = - { - new Aim(), - new Speed() - }; - - double sectionEnd = section_length / TimeRate; - foreach (OsuDifficultyHitObject h in beatmap) - { - while (h.BaseObject.StartTime > sectionEnd) - { - foreach (Skill s in skills) - { - s.SaveCurrentPeak(); - s.StartNewSectionFrom(sectionEnd); - } - - sectionEnd += section_length; - } - - foreach (Skill s in skills) - s.Process(h); - } - - double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - - double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; - - if (categoryDifficulty != null) - { - categoryDifficulty.Add("Aim", aimRating); - categoryDifficulty.Add("Speed", speedRating); - } - - return starRating; - } - - protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new OsuBeatmapConverter(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; +using osu.Game.Rulesets.Osu.OsuDifficulty.Skills; + +namespace osu.Game.Rulesets.Osu.OsuDifficulty +{ + public class OsuDifficultyCalculator : DifficultyCalculator + { + private const int section_length = 400; + private const double difficulty_multiplier = 0.0675; + + public OsuDifficultyCalculator(Beatmap beatmap) + : base(beatmap) + { + } + + public OsuDifficultyCalculator(Beatmap beatmap, Mod[] mods) + : base(beatmap, mods) + { + } + + protected override void PreprocessHitObjects() + { + new OsuBeatmapProcessor().PostProcess(Beatmap); + } + + public override double Calculate(Dictionary categoryDifficulty = null) + { + OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate); + Skill[] skills = + { + new Aim(), + new Speed() + }; + + double sectionEnd = section_length / TimeRate; + foreach (OsuDifficultyHitObject h in beatmap) + { + while (h.BaseObject.StartTime > sectionEnd) + { + foreach (Skill s in skills) + { + s.SaveCurrentPeak(); + s.StartNewSectionFrom(sectionEnd); + } + + sectionEnd += section_length; + } + + foreach (Skill s in skills) + s.Process(h); + } + + double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + + double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; + + if (categoryDifficulty != null) + { + categoryDifficulty.Add("Aim", aimRating); + categoryDifficulty.Add("Speed", speedRating); + } + + return starRating; + } + + protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new OsuBeatmapConverter(); + } +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs index a1a2687cea..5c8ab0f3d4 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs @@ -1,94 +1,94 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections; -using System.Collections.Generic; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing -{ - /// - /// An enumerable container wrapping input as - /// which contains extra data required for difficulty calculation. - /// - public class OsuDifficultyBeatmap : IEnumerable - { - private readonly IEnumerator difficultyObjects; - private readonly Queue onScreen = new Queue(); - - /// - /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as - /// which contains extra data required for difficulty calculation. - /// - public OsuDifficultyBeatmap(List objects, double timeRate) - { - // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases. - // This should probably happen before the objects reach the difficulty calculator. - objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime)); - difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate); - } - - /// - /// Returns an enumerator that enumerates all s in the . - /// The inner loop adds objects that appear on screen into a queue until we need to hit the next object. - /// The outer loop returns objects from this queue one at a time, only after they had to be hit, and should no longer be on screen. - /// This means that we can loop through every object that is on screen at the time when a new one appears, - /// allowing us to determine a reading strain for the object that just appeared. - /// - public IEnumerator GetEnumerator() - { - while (true) - { - // Add upcoming objects to the queue until we have at least one object that had been hit and can be dequeued. - // This means there is always at least one object in the queue unless we reached the end of the map. - do - { - if (!difficultyObjects.MoveNext()) - break; // New objects can't be added anymore, but we still need to dequeue and return the ones already on screen. - - OsuDifficultyHitObject latest = difficultyObjects.Current; - // Calculate flow values here - - foreach (OsuDifficultyHitObject h in onScreen) - { - // ReSharper disable once PossibleNullReferenceException (resharper not smart enough to understand IEnumerator.MoveNext()) - h.TimeUntilHit -= latest.DeltaTime; - // Calculate reading strain here - } - - onScreen.Enqueue(latest); - } - while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new objects on screen while there is still time before we have to hit the next one. - - if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the objects. - yield return onScreen.Dequeue(); // Remove and return objects one by one that had to be hit before the latest one appeared. - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate) - { - // We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object. - OsuHitObject[] triangle = new OsuHitObject[3]; - - // OsuDifficultyHitObject construction requires three components, an extra copy of the first OsuHitObject is used at the beginning. - if (objects.Count > 1) - { - triangle[1] = objects[0]; // This copy will get shifted to the last spot in the triangle. - triangle[0] = objects[0]; // This component corresponds to the real first OsuHitOject. - } - - // The final component of the first triangle will be the second OsuHitOject of the map, which forms the first jump. - // If the map has less than two OsuHitObjects, the enumerator will not return anything. - for (int i = 1; i < objects.Count; ++i) - { - triangle[2] = triangle[1]; - triangle[1] = triangle[0]; - triangle[0] = objects[i]; - - yield return new OsuDifficultyHitObject(triangle, timeRate); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing +{ + /// + /// An enumerable container wrapping input as + /// which contains extra data required for difficulty calculation. + /// + public class OsuDifficultyBeatmap : IEnumerable + { + private readonly IEnumerator difficultyObjects; + private readonly Queue onScreen = new Queue(); + + /// + /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as + /// which contains extra data required for difficulty calculation. + /// + public OsuDifficultyBeatmap(List objects, double timeRate) + { + // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases. + // This should probably happen before the objects reach the difficulty calculator. + objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime)); + difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate); + } + + /// + /// Returns an enumerator that enumerates all s in the . + /// The inner loop adds objects that appear on screen into a queue until we need to hit the next object. + /// The outer loop returns objects from this queue one at a time, only after they had to be hit, and should no longer be on screen. + /// This means that we can loop through every object that is on screen at the time when a new one appears, + /// allowing us to determine a reading strain for the object that just appeared. + /// + public IEnumerator GetEnumerator() + { + while (true) + { + // Add upcoming objects to the queue until we have at least one object that had been hit and can be dequeued. + // This means there is always at least one object in the queue unless we reached the end of the map. + do + { + if (!difficultyObjects.MoveNext()) + break; // New objects can't be added anymore, but we still need to dequeue and return the ones already on screen. + + OsuDifficultyHitObject latest = difficultyObjects.Current; + // Calculate flow values here + + foreach (OsuDifficultyHitObject h in onScreen) + { + // ReSharper disable once PossibleNullReferenceException (resharper not smart enough to understand IEnumerator.MoveNext()) + h.TimeUntilHit -= latest.DeltaTime; + // Calculate reading strain here + } + + onScreen.Enqueue(latest); + } + while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new objects on screen while there is still time before we have to hit the next one. + + if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the objects. + yield return onScreen.Dequeue(); // Remove and return objects one by one that had to be hit before the latest one appeared. + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate) + { + // We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object. + OsuHitObject[] triangle = new OsuHitObject[3]; + + // OsuDifficultyHitObject construction requires three components, an extra copy of the first OsuHitObject is used at the beginning. + if (objects.Count > 1) + { + triangle[1] = objects[0]; // This copy will get shifted to the last spot in the triangle. + triangle[0] = objects[0]; // This component corresponds to the real first OsuHitOject. + } + + // The final component of the first triangle will be the second OsuHitOject of the map, which forms the first jump. + // If the map has less than two OsuHitObjects, the enumerator will not return anything. + for (int i = 1; i < objects.Count; ++i) + { + triangle[2] = triangle[1]; + triangle[1] = triangle[0]; + triangle[0] = objects[i]; + + yield return new OsuDifficultyHitObject(triangle, timeRate); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs index c817cd0ff3..415f76ced8 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -1,116 +1,116 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using OpenTK; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing -{ - /// - /// A wrapper around extending it with additional data required for difficulty calculation. - /// - public class OsuDifficultyHitObject - { - /// - /// The this refers to. - /// - public OsuHitObject BaseObject { get; } - - /// - /// Normalized distance from the of the previous . - /// - public double Distance { get; private set; } - - /// - /// Milliseconds elapsed since the StartTime of the previous . - /// - public double DeltaTime { get; private set; } - - /// - /// Number of milliseconds until the has to be hit. - /// - public double TimeUntilHit { get; set; } - - private const int normalized_radius = 52; - - private readonly double timeRate; - - private readonly OsuHitObject[] t; - - /// - /// Initializes the object calculating extra data required for difficulty calculation. - /// - public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate) - { - this.timeRate = timeRate; - - t = triangle; - BaseObject = t[0]; - setDistances(); - setTimingValues(); - // Calculate angle here - } - - private void setDistances() - { - // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. - double scalingFactor = normalized_radius / BaseObject.Radius; - if (BaseObject.Radius < 30) - { - double smallCircleBonus = Math.Min(30 - BaseObject.Radius, 5) / 50; - scalingFactor *= 1 + smallCircleBonus; - } - - Vector2 lastCursorPosition = t[1].StackedPosition; - float lastTravelDistance = 0; - - var lastSlider = t[1] as Slider; - if (lastSlider != null) - { - computeSliderCursorPosition(lastSlider); - lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition; - lastTravelDistance = lastSlider.LazyTravelDistance; - } - - Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor; - } - - private void setTimingValues() - { - // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure. - DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate); - TimeUntilHit = 450; // BaseObject.PreEmpt; - } - - private void computeSliderCursorPosition(Slider slider) - { - if (slider.LazyEndPosition != null) - return; - slider.LazyEndPosition = slider.StackedPosition; - - float approxFollowCircleRadius = (float)(slider.Radius * 3); - var computeVertex = new Action(t => - { - // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) - var diff = slider.StackedPositionAt(t) - slider.LazyEndPosition.Value; - float dist = diff.Length; - - if (dist > approxFollowCircleRadius) - { - // The cursor would be outside the follow circle, we need to move it - diff.Normalize(); // Obtain direction of diff - dist -= approxFollowCircleRadius; - slider.LazyEndPosition += diff * dist; - slider.LazyTravelDistance += dist; - } - }); - - var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime); - foreach (var time in scoringTimes) - computeVertex(time); - computeVertex(slider.EndTime); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing +{ + /// + /// A wrapper around extending it with additional data required for difficulty calculation. + /// + public class OsuDifficultyHitObject + { + /// + /// The this refers to. + /// + public OsuHitObject BaseObject { get; } + + /// + /// Normalized distance from the of the previous . + /// + public double Distance { get; private set; } + + /// + /// Milliseconds elapsed since the StartTime of the previous . + /// + public double DeltaTime { get; private set; } + + /// + /// Number of milliseconds until the has to be hit. + /// + public double TimeUntilHit { get; set; } + + private const int normalized_radius = 52; + + private readonly double timeRate; + + private readonly OsuHitObject[] t; + + /// + /// Initializes the object calculating extra data required for difficulty calculation. + /// + public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate) + { + this.timeRate = timeRate; + + t = triangle; + BaseObject = t[0]; + setDistances(); + setTimingValues(); + // Calculate angle here + } + + private void setDistances() + { + // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. + double scalingFactor = normalized_radius / BaseObject.Radius; + if (BaseObject.Radius < 30) + { + double smallCircleBonus = Math.Min(30 - BaseObject.Radius, 5) / 50; + scalingFactor *= 1 + smallCircleBonus; + } + + Vector2 lastCursorPosition = t[1].StackedPosition; + float lastTravelDistance = 0; + + var lastSlider = t[1] as Slider; + if (lastSlider != null) + { + computeSliderCursorPosition(lastSlider); + lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition; + lastTravelDistance = lastSlider.LazyTravelDistance; + } + + Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor; + } + + private void setTimingValues() + { + // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure. + DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate); + TimeUntilHit = 450; // BaseObject.PreEmpt; + } + + private void computeSliderCursorPosition(Slider slider) + { + if (slider.LazyEndPosition != null) + return; + slider.LazyEndPosition = slider.StackedPosition; + + float approxFollowCircleRadius = (float)(slider.Radius * 3); + var computeVertex = new Action(t => + { + // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) + var diff = slider.StackedPositionAt(t) - slider.LazyEndPosition.Value; + float dist = diff.Length; + + if (dist > approxFollowCircleRadius) + { + // The cursor would be outside the follow circle, we need to move it + diff.Normalize(); // Obtain direction of diff + dist -= approxFollowCircleRadius; + slider.LazyEndPosition += diff * dist; + slider.LazyTravelDistance += dist; + } + }); + + var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime); + foreach (var time in scoringTimes) + computeVertex(time); + computeVertex(slider.EndTime); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs index 361b31c7bd..5c736d7bb5 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; - -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills -{ - /// - /// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances. - /// - public class Aim : Skill - { - protected override double SkillMultiplier => 26.25; - protected override double StrainDecayBase => 0.15; - - protected override double StrainValueOf(OsuDifficultyHitObject current) => Math.Pow(current.Distance, 0.99) / current.DeltaTime; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; + +namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills +{ + /// + /// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances. + /// + public class Aim : Skill + { + protected override double SkillMultiplier => 26.25; + protected override double StrainDecayBase => 0.15; + + protected override double StrainValueOf(OsuDifficultyHitObject current) => Math.Pow(current.Distance, 0.99) / current.DeltaTime; + } +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs index db9658087b..983599432f 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs @@ -1,100 +1,100 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; -using osu.Game.Rulesets.Osu.OsuDifficulty.Utils; - -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills -{ - /// - /// Used to processes strain values of s, keep track of strain levels caused by the processed objects - /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. - /// - public abstract class Skill - { - /// - /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. - /// - protected abstract double SkillMultiplier { get; } - - /// - /// Determines how quickly strain decays for the given skill. - /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. - /// - protected abstract double StrainDecayBase { get; } - - /// - /// s that were processed previously. They can affect the strain values of the following objects. - /// - protected readonly History Previous = new History(2); // Contained objects not used yet - - private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. - private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. - private readonly List strainPeaks = new List(); - - /// - /// Process an and update current strain values accordingly. - /// - public void Process(OsuDifficultyHitObject current) - { - currentStrain *= strainDecay(current.DeltaTime); - if (!(current.BaseObject is Spinner)) - currentStrain += StrainValueOf(current) * SkillMultiplier; - - currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); - - Previous.Push(current); - } - - /// - /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. - /// - public void SaveCurrentPeak() - { - if (Previous.Count > 0) - strainPeaks.Add(currentSectionPeak); - } - - /// - /// Sets the initial strain level for a new section. - /// - /// The beginning of the new section in milliseconds - public void StartNewSectionFrom(double offset) - { - // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. - // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. - if (Previous.Count > 0) - currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime); - } - - /// - /// Returns the calculated difficulty value representing all processed s. - /// - public double DifficultyValue() - { - strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - double difficulty = 0; - double weight = 1; - - // Difficulty is the weighted sum of the highest strains from every section. - foreach (double strain in strainPeaks) - { - difficulty += strain * weight; - weight *= 0.9; - } - - return difficulty; - } - - /// - /// Calculates the strain value of an . This value is affected by previously processed objects. - /// - protected abstract double StrainValueOf(OsuDifficultyHitObject current); - - private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; +using osu.Game.Rulesets.Osu.OsuDifficulty.Utils; + +namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills +{ + /// + /// Used to processes strain values of s, keep track of strain levels caused by the processed objects + /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. + /// + public abstract class Skill + { + /// + /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. + /// + protected abstract double SkillMultiplier { get; } + + /// + /// Determines how quickly strain decays for the given skill. + /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. + /// + protected abstract double StrainDecayBase { get; } + + /// + /// s that were processed previously. They can affect the strain values of the following objects. + /// + protected readonly History Previous = new History(2); // Contained objects not used yet + + private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. + private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. + private readonly List strainPeaks = new List(); + + /// + /// Process an and update current strain values accordingly. + /// + public void Process(OsuDifficultyHitObject current) + { + currentStrain *= strainDecay(current.DeltaTime); + if (!(current.BaseObject is Spinner)) + currentStrain += StrainValueOf(current) * SkillMultiplier; + + currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); + + Previous.Push(current); + } + + /// + /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. + /// + public void SaveCurrentPeak() + { + if (Previous.Count > 0) + strainPeaks.Add(currentSectionPeak); + } + + /// + /// Sets the initial strain level for a new section. + /// + /// The beginning of the new section in milliseconds + public void StartNewSectionFrom(double offset) + { + // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. + // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. + if (Previous.Count > 0) + currentSectionPeak = currentStrain * strainDecay(offset - Previous[0].BaseObject.StartTime); + } + + /// + /// Returns the calculated difficulty value representing all processed s. + /// + public double DifficultyValue() + { + strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + double difficulty = 0; + double weight = 1; + + // Difficulty is the weighted sum of the highest strains from every section. + foreach (double strain in strainPeaks) + { + difficulty += strain * weight; + weight *= 0.9; + } + + return difficulty; + } + + /// + /// Calculates the strain value of an . This value is affected by previously processed objects. + /// + protected abstract double StrainValueOf(OsuDifficultyHitObject current); + + private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); + } +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs index b85904acf9..ae3caa1e66 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; - -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills -{ - /// - /// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit. - /// - public class Speed : Skill - { - protected override double SkillMultiplier => 1400; - protected override double StrainDecayBase => 0.3; - - private const double single_spacing_threshold = 125; - private const double stream_spacing_threshold = 110; - private const double almost_diameter = 90; - - protected override double StrainValueOf(OsuDifficultyHitObject current) - { - double distance = current.Distance; - - double speedValue; - if (distance > single_spacing_threshold) - speedValue = 2.5; - else if (distance > stream_spacing_threshold) - speedValue = 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold); - else if (distance > almost_diameter) - speedValue = 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter); - else if (distance > almost_diameter / 2) - speedValue = 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2); - else - speedValue = 0.95; - - return speedValue / current.DeltaTime; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing; + +namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills +{ + /// + /// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit. + /// + public class Speed : Skill + { + protected override double SkillMultiplier => 1400; + protected override double StrainDecayBase => 0.3; + + private const double single_spacing_threshold = 125; + private const double stream_spacing_threshold = 110; + private const double almost_diameter = 90; + + protected override double StrainValueOf(OsuDifficultyHitObject current) + { + double distance = current.Distance; + + double speedValue; + if (distance > single_spacing_threshold) + speedValue = 2.5; + else if (distance > stream_spacing_threshold) + speedValue = 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold); + else if (distance > almost_diameter) + speedValue = 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter); + else if (distance > almost_diameter / 2) + speedValue = 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2); + else + speedValue = 0.95; + + return speedValue / current.DeltaTime; + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs index 9f07248abf..f6933a3e5d 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs @@ -1,86 +1,86 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils -{ - /// - /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. - /// Indexing starts at the top of the stack. - /// - public class History : IEnumerable - { - public int Count { get; private set; } - - private readonly T[] array; - private readonly int capacity; - private int marker; // Marks the position of the most recently added item. - - /// - /// Initializes a new instance of the History class that is empty and has the specified capacity. - /// - /// The number of items the History can hold. - public History(int capacity) - { - if (capacity < 0) - throw new ArgumentOutOfRangeException(); - - this.capacity = capacity; - array = new T[capacity]; - marker = capacity; // Set marker to the end of the array, outside of the indexed range by one. - } - - /// - /// The most recently added item is returned at index 0. - /// - public T this[int i] - { - get - { - if (i < 0 || i > Count - 1) - throw new IndexOutOfRangeException(); - - i += marker; - if (i > capacity - 1) - i -= capacity; - - return array[i]; - } - } - - /// - /// Adds the item as the most recent one in the history. - /// The oldest item is disposed if the history is full. - /// - public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition. - { - if (marker == 0) - marker = capacity - 1; - else - --marker; - - array[marker] = item; - - if (Count < capacity) - ++Count; - } - - /// - /// Returns an enumerator which enumerates items in the history starting from the most recently added one. - /// - public IEnumerator GetEnumerator() - { - for (int i = marker; i < capacity; ++i) - yield return array[i]; - - if (Count == capacity) - for (int i = 0; i < marker; ++i) - yield return array[i]; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils +{ + /// + /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. + /// Indexing starts at the top of the stack. + /// + public class History : IEnumerable + { + public int Count { get; private set; } + + private readonly T[] array; + private readonly int capacity; + private int marker; // Marks the position of the most recently added item. + + /// + /// Initializes a new instance of the History class that is empty and has the specified capacity. + /// + /// The number of items the History can hold. + public History(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(); + + this.capacity = capacity; + array = new T[capacity]; + marker = capacity; // Set marker to the end of the array, outside of the indexed range by one. + } + + /// + /// The most recently added item is returned at index 0. + /// + public T this[int i] + { + get + { + if (i < 0 || i > Count - 1) + throw new IndexOutOfRangeException(); + + i += marker; + if (i > capacity - 1) + i -= capacity; + + return array[i]; + } + } + + /// + /// Adds the item as the most recent one in the history. + /// The oldest item is disposed if the history is full. + /// + public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition. + { + if (marker == 0) + marker = capacity - 1; + else + --marker; + + array[marker] = item; + + if (Count < capacity) + ++Count; + } + + /// + /// Returns an enumerator which enumerates items in the history starting from the most recently added one. + /// + public IEnumerator GetEnumerator() + { + for (int i = marker; i < capacity; ++i) + yield return array[i]; + + if (Count == capacity) + for (int i = 0; i < marker; ++i) + yield return array[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 1a7f00ea51..d9ae836e0a 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.ComponentModel; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Osu -{ - public class OsuInputManager : RulesetInputManager - { - public IEnumerable PressedActions => KeyBindingContainer.PressedActions; - - public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) - { - } - } - - public enum OsuAction - { - [Description("Left Button")] - LeftButton, - [Description("Right Button")] - RightButton - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.ComponentModel; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu +{ + public class OsuInputManager : RulesetInputManager + { + public IEnumerable PressedActions => KeyBindingContainer.PressedActions; + + public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum OsuAction + { + [Description("Left Button")] + LeftButton, + [Description("Right Button")] + RightButton + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d407835a96..37e6ec3817 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -1,157 +1,157 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.OsuDifficulty; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Overlays.Settings; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Replays.Types; - -namespace osu.Game.Rulesets.Osu -{ - public class OsuRuleset : Ruleset - { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new OsuRulesetContainer(this, beatmap, isForCurrentRuleset); - - public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] - { - new KeyBinding(InputKey.Z, OsuAction.LeftButton), - new KeyBinding(InputKey.X, OsuAction.RightButton), - new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton), - new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), - }; - - public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) - { - IEnumerable hitObjects = beatmap.Beatmap.HitObjects; - IEnumerable circles = hitObjects.Where(c => !(c is IHasEndTime)); - IEnumerable sliders = hitObjects.Where(s => s is IHasCurve); - IEnumerable spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve)); - - return new[] - { - new BeatmapStatistic - { - Name = @"Circle Count", - Content = circles.Count().ToString(), - Icon = FontAwesome.fa_circle_o - }, - new BeatmapStatistic - { - Name = @"Slider Count", - Content = sliders.Count().ToString(), - Icon = FontAwesome.fa_circle - }, - new BeatmapStatistic - { - Name = @"Spinner Count", - Content = spinners.Count().ToString(), - Icon = FontAwesome.fa_circle - } - }; - } - - public override IEnumerable GetModsFor(ModType type) - { - switch (type) - { - case ModType.DifficultyReduction: - return new Mod[] - { - new OsuModEasy(), - new OsuModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new OsuModHalfTime(), - new OsuModDaycore(), - }, - }, - }; - - case ModType.DifficultyIncrease: - return new Mod[] - { - new OsuModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new OsuModSuddenDeath(), - new OsuModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModNightcore(), - }, - }, - new OsuModHidden(), - new OsuModFlashlight(), - }; - - case ModType.Special: - return new Mod[] - { - new OsuModRelax(), - new OsuModAutopilot(), - new OsuModSpunOut(), - new MultiMod - { - Mods = new Mod[] - { - new OsuModAutoplay(), - new ModCinema(), - }, - }, - new OsuModTarget(), - }; - - default: - return new Mod[] { }; - } - } - - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; - - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods); - - public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); - - public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); - - public override string Description => "osu!"; - - public override string ShortName => "osu"; - - public override SettingsSubsection CreateSettings() => new OsuSettings(); - - public override int? LegacyID => 0; - - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); - - public OsuRuleset(RulesetInfo rulesetInfo = null) - : base(rulesetInfo) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.OsuDifficulty; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays.Types; + +namespace osu.Game.Rulesets.Osu +{ + public class OsuRuleset : Ruleset + { + public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new OsuRulesetContainer(this, beatmap, isForCurrentRuleset); + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.Z, OsuAction.LeftButton), + new KeyBinding(InputKey.X, OsuAction.RightButton), + new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton), + new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), + }; + + public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) + { + IEnumerable hitObjects = beatmap.Beatmap.HitObjects; + IEnumerable circles = hitObjects.Where(c => !(c is IHasEndTime)); + IEnumerable sliders = hitObjects.Where(s => s is IHasCurve); + IEnumerable spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve)); + + return new[] + { + new BeatmapStatistic + { + Name = @"Circle Count", + Content = circles.Count().ToString(), + Icon = FontAwesome.fa_circle_o + }, + new BeatmapStatistic + { + Name = @"Slider Count", + Content = sliders.Count().ToString(), + Icon = FontAwesome.fa_circle + }, + new BeatmapStatistic + { + Name = @"Spinner Count", + Content = spinners.Count().ToString(), + Icon = FontAwesome.fa_circle + } + }; + } + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.DifficultyReduction: + return new Mod[] + { + new OsuModEasy(), + new OsuModNoFail(), + new MultiMod + { + Mods = new Mod[] + { + new OsuModHalfTime(), + new OsuModDaycore(), + }, + }, + }; + + case ModType.DifficultyIncrease: + return new Mod[] + { + new OsuModHardRock(), + new MultiMod + { + Mods = new Mod[] + { + new OsuModSuddenDeath(), + new OsuModPerfect(), + }, + }, + new MultiMod + { + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModNightcore(), + }, + }, + new OsuModHidden(), + new OsuModFlashlight(), + }; + + case ModType.Special: + return new Mod[] + { + new OsuModRelax(), + new OsuModAutopilot(), + new OsuModSpunOut(), + new MultiMod + { + Mods = new Mod[] + { + new OsuModAutoplay(), + new ModCinema(), + }, + }, + new OsuModTarget(), + }; + + default: + return new Mod[] { }; + } + } + + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods); + + public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); + + public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); + + public override string Description => "osu!"; + + public override string ShortName => "osu"; + + public override SettingsSubsection CreateSettings() => new OsuSettings(); + + public override int? LegacyID => 0; + + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); + + public OsuRuleset(RulesetInfo rulesetInfo = null) + : base(rulesetInfo) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs index ea2c2c6729..b61c53be6a 100644 --- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Runtime.CompilerServices; - -// We publish our internal attributes to other sub-projects of the framework. -// Note, that we omit visual tests as they are meant to test the framework -// behavior "in the wild". - -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")] -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")] +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Runtime.CompilerServices; + +// We publish our internal attributes to other sub-projects of the framework. +// Note, that we omit visual tests as they are meant to test the framework +// behavior "in the wild". + +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")] diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 699cdf75a2..ad4ea343d2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -1,335 +1,335 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; -using System; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Replays -{ - public class OsuAutoGenerator : OsuAutoGeneratorBase - { - #region Parameters - - /// - /// If delayed movements should be used, causing the cursor to stay on each hitobject for as long as possible. - /// Mainly for Autopilot. - /// - public bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2); - - #endregion - - #region Constants - - /// - /// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it. - /// - private readonly double reactionTime; - - /// - /// What easing to use when moving between hitobjects - /// - private Easing preferredEasing => DelayedMovements ? Easing.InOutCubic : Easing.Out; - - #endregion - - #region Construction / Initialisation - - public OsuAutoGenerator(Beatmap beatmap) - : base(beatmap) - { - // Already superhuman, but still somewhat realistic - reactionTime = ApplyModsToRate(100); - } - - #endregion - - #region Generator - - /// - /// Which button (left or right) to use for the current hitobject. - /// Even means LMB will be used to click, odd means RMB will be used. - /// This keeps track of the button previously used for alt/singletap logic. - /// - private int buttonIndex; - - public override Replay Generate() - { - buttonIndex = 0; - - AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); - AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); - AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); - - for (int i = 0; i < Beatmap.HitObjects.Count; i++) - { - OsuHitObject h = Beatmap.HitObjects[i]; - - if (DelayedMovements && i > 0) - { - OsuHitObject prev = Beatmap.HitObjects[i - 1]; - addDelayedMovements(h, prev); - } - - addHitObjectReplay(h); - } - - return Replay; - } - - private void addDelayedMovements(OsuHitObject h, OsuHitObject prev) - { - double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime; - - // Make the cursor stay at a hitObject as long as possible (mainly for autopilot). - if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) - { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); - } - else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) - { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); - } - else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good) > endTime + h.HitWindows.HalfWindowFor(HitResult.Good) + 50) - { - if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); - if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); - } - } - - private void addHitObjectReplay(OsuHitObject h) - { - // Default values for circles/sliders - Vector2 startPosition = h.StackedPosition; - Easing easing = preferredEasing; - float spinnerDirection = -1; - - // The startPosition for the slider should not be its .Position, but the point on the circle whose tangent crosses the current cursor position - // We also modify spinnerDirection so it spins in the direction it enters the spin circle, to make a smooth transition. - // TODO: Shouldn't the spinner always spin in the same direction? - if (h is Spinner) - { - calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection); - - Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position; - - if (spinCentreOffset.Length > SPIN_RADIUS) - { - // If moving in from the outside, don't ease out (default eases out). This means auto will "start" spinning immediately after moving into position. - easing = Easing.In; - } - } - - // Do some nice easing for cursor movements - if (Frames.Count > 0) - { - moveToHitObject(h, startPosition, easing); - } - - // Add frames to click the hitobject - addHitObjectClickFrames(h, startPosition, spinnerDirection); - } - - #endregion - - #region Helper subroutines - - private static void calcSpinnerStartPosAndDirection(Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection) - { - Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos; - float distFromCentre = spinCentreOffset.Length; - float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS); - - if (distFromCentre > SPIN_RADIUS) - { - // Previous cursor position was outside spin circle, set startPosition to the tangent point. - - // Angle between centre offset and tangent point offset. - float angle = (float)Math.Asin(SPIN_RADIUS / distFromCentre); - - if (angle > 0) - { - spinnerDirection = -1; - } - else - { - spinnerDirection = 1; - } - - // Rotate by angle so it's parallel to tangent line - spinCentreOffset.X = spinCentreOffset.X * (float)Math.Cos(angle) - spinCentreOffset.Y * (float)Math.Sin(angle); - spinCentreOffset.Y = spinCentreOffset.X * (float)Math.Sin(angle) + spinCentreOffset.Y * (float)Math.Cos(angle); - - // Set length to distToTangentPoint - spinCentreOffset.Normalize(); - spinCentreOffset *= distToTangentPoint; - - // Move along the tangent line, now startPosition is at the tangent point. - startPosition = prevPos + spinCentreOffset; - } - else if (spinCentreOffset.Length > 0) - { - // Previous cursor position was inside spin circle, set startPosition to the nearest point on spin circle. - startPosition = SPINNER_CENTRE - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length); - spinnerDirection = 1; - } - else - { - // Degenerate case where cursor position is exactly at the centre of the spin circle. - startPosition = SPINNER_CENTRE + new Vector2(0, -SPIN_RADIUS); - spinnerDirection = 1; - } - } - - private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing) - { - OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1]; - - // Wait until Auto could "see and react" to the next note. - double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); - if (waitTime > lastFrame.Time) - { - lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; - AddFrameToReplay(lastFrame); - } - - Vector2 lastPosition = lastFrame.Position; - - double timeDifference = ApplyModsToTime(h.StartTime - lastFrame.Time); - - // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. - if (timeDifference > 0 && // Sanity checks - ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough - timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. - { - // Perform eased movement - for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay) - { - Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); - AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); - } - - buttonIndex = 0; - } - else - { - buttonIndex++; - } - } - - // Add frames to click the hitobject - private void addHitObjectClickFrames(OsuHitObject h, Vector2 startPosition, float spinnerDirection) - { - // Time to insert the first frame which clicks the object - // Here we mainly need to determine which button to use - var action = buttonIndex % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton; - - var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action); - - // TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime. - double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY; - int endDelay = h is Spinner ? 1 : 0; - var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y)); - - // Decrement because we want the previous frame, not the next one - int index = FindInsertionIndex(startFrame) - 1; - - // If the previous frame has a button pressed, force alternation. - // If there are frames ahead, modify those to use the new button press. - // Do we have a previous frame? No need to check for < replay.Count since we decremented! - if (index >= 0) - { - var previousFrame = (OsuReplayFrame)Frames[index]; - var previousActions = previousFrame.Actions; - - // If a button is already held, then we simply alternate - if (previousActions.Any()) - { - // Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button. - if (previousActions.Contains(action)) - { - action = action == OsuAction.LeftButton ? OsuAction.RightButton : OsuAction.LeftButton; - startFrame.Actions.Clear(); - startFrame.Actions.Add(action); - } - - // We always follow the most recent slider / spinner, so remove any other frames that occur while it exists. - int endIndex = FindInsertionIndex(endFrame); - - if (index < Frames.Count - 1) - Frames.RemoveRange(index + 1, Math.Max(0, endIndex - (index + 1))); - - // After alternating we need to keep holding the other button in the future rather than the previous one. - for (int j = index + 1; j < Frames.Count; ++j) - { - var frame = (OsuReplayFrame)Frames[j]; - - // Don't affect frames which stop pressing a button! - if (j < Frames.Count - 1 || frame.Actions.SequenceEqual(previousActions)) - { - frame.Actions.Clear(); - frame.Actions.Add(action); - } - } - } - } - - AddFrameToReplay(startFrame); - - // We add intermediate frames for spinning / following a slider here. - if (h is Spinner) - { - Spinner s = h as Spinner; - - Vector2 difference = startPosition - SPINNER_CENTRE; - - float radius = difference.Length; - float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X); - - double t; - - for (double j = h.StartTime + FrameDelay; j < s.EndTime; j += FrameDelay) - { - t = ApplyModsToTime(j - h.StartTime) * spinnerDirection; - - Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); - AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action)); - } - - t = ApplyModsToTime(s.EndTime - h.StartTime) * spinnerDirection; - Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); - - AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); - - endFrame.Position = endPosition; - } - else if (h is Slider) - { - Slider s = h as Slider; - - for (double j = FrameDelay; j < s.Duration; j += FrameDelay) - { - Vector2 pos = s.StackedPositionAt(j / s.Duration); - AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action)); - } - - AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(s.StackedEndPosition.X, s.StackedEndPosition.Y), action)); - } - - // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! - if (Frames[Frames.Count - 1].Time <= endFrame.Time) - AddFrameToReplay(endFrame); - } - - #endregion - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Replays +{ + public class OsuAutoGenerator : OsuAutoGeneratorBase + { + #region Parameters + + /// + /// If delayed movements should be used, causing the cursor to stay on each hitobject for as long as possible. + /// Mainly for Autopilot. + /// + public bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2); + + #endregion + + #region Constants + + /// + /// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it. + /// + private readonly double reactionTime; + + /// + /// What easing to use when moving between hitobjects + /// + private Easing preferredEasing => DelayedMovements ? Easing.InOutCubic : Easing.Out; + + #endregion + + #region Construction / Initialisation + + public OsuAutoGenerator(Beatmap beatmap) + : base(beatmap) + { + // Already superhuman, but still somewhat realistic + reactionTime = ApplyModsToRate(100); + } + + #endregion + + #region Generator + + /// + /// Which button (left or right) to use for the current hitobject. + /// Even means LMB will be used to click, odd means RMB will be used. + /// This keeps track of the button previously used for alt/singletap logic. + /// + private int buttonIndex; + + public override Replay Generate() + { + buttonIndex = 0; + + AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); + AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); + AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); + + for (int i = 0; i < Beatmap.HitObjects.Count; i++) + { + OsuHitObject h = Beatmap.HitObjects[i]; + + if (DelayedMovements && i > 0) + { + OsuHitObject prev = Beatmap.HitObjects[i - 1]; + addDelayedMovements(h, prev); + } + + addHitObjectReplay(h); + } + + return Replay; + } + + private void addDelayedMovements(OsuHitObject h, OsuHitObject prev) + { + double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime; + + // Make the cursor stay at a hitObject as long as possible (mainly for autopilot). + if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) + { + if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + } + else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50) + { + if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + } + else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good) > endTime + h.HitWindows.HalfWindowFor(HitResult.Good) + 50) + { + if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + } + } + + private void addHitObjectReplay(OsuHitObject h) + { + // Default values for circles/sliders + Vector2 startPosition = h.StackedPosition; + Easing easing = preferredEasing; + float spinnerDirection = -1; + + // The startPosition for the slider should not be its .Position, but the point on the circle whose tangent crosses the current cursor position + // We also modify spinnerDirection so it spins in the direction it enters the spin circle, to make a smooth transition. + // TODO: Shouldn't the spinner always spin in the same direction? + if (h is Spinner) + { + calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection); + + Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position; + + if (spinCentreOffset.Length > SPIN_RADIUS) + { + // If moving in from the outside, don't ease out (default eases out). This means auto will "start" spinning immediately after moving into position. + easing = Easing.In; + } + } + + // Do some nice easing for cursor movements + if (Frames.Count > 0) + { + moveToHitObject(h, startPosition, easing); + } + + // Add frames to click the hitobject + addHitObjectClickFrames(h, startPosition, spinnerDirection); + } + + #endregion + + #region Helper subroutines + + private static void calcSpinnerStartPosAndDirection(Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection) + { + Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos; + float distFromCentre = spinCentreOffset.Length; + float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS); + + if (distFromCentre > SPIN_RADIUS) + { + // Previous cursor position was outside spin circle, set startPosition to the tangent point. + + // Angle between centre offset and tangent point offset. + float angle = (float)Math.Asin(SPIN_RADIUS / distFromCentre); + + if (angle > 0) + { + spinnerDirection = -1; + } + else + { + spinnerDirection = 1; + } + + // Rotate by angle so it's parallel to tangent line + spinCentreOffset.X = spinCentreOffset.X * (float)Math.Cos(angle) - spinCentreOffset.Y * (float)Math.Sin(angle); + spinCentreOffset.Y = spinCentreOffset.X * (float)Math.Sin(angle) + spinCentreOffset.Y * (float)Math.Cos(angle); + + // Set length to distToTangentPoint + spinCentreOffset.Normalize(); + spinCentreOffset *= distToTangentPoint; + + // Move along the tangent line, now startPosition is at the tangent point. + startPosition = prevPos + spinCentreOffset; + } + else if (spinCentreOffset.Length > 0) + { + // Previous cursor position was inside spin circle, set startPosition to the nearest point on spin circle. + startPosition = SPINNER_CENTRE - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length); + spinnerDirection = 1; + } + else + { + // Degenerate case where cursor position is exactly at the centre of the spin circle. + startPosition = SPINNER_CENTRE + new Vector2(0, -SPIN_RADIUS); + spinnerDirection = 1; + } + } + + private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing) + { + OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1]; + + // Wait until Auto could "see and react" to the next note. + double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); + if (waitTime > lastFrame.Time) + { + lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; + AddFrameToReplay(lastFrame); + } + + Vector2 lastPosition = lastFrame.Position; + + double timeDifference = ApplyModsToTime(h.StartTime - lastFrame.Time); + + // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. + if (timeDifference > 0 && // Sanity checks + ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough + timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. + { + // Perform eased movement + for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay) + { + Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); + AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); + } + + buttonIndex = 0; + } + else + { + buttonIndex++; + } + } + + // Add frames to click the hitobject + private void addHitObjectClickFrames(OsuHitObject h, Vector2 startPosition, float spinnerDirection) + { + // Time to insert the first frame which clicks the object + // Here we mainly need to determine which button to use + var action = buttonIndex % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton; + + var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action); + + // TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime. + double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY; + int endDelay = h is Spinner ? 1 : 0; + var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y)); + + // Decrement because we want the previous frame, not the next one + int index = FindInsertionIndex(startFrame) - 1; + + // If the previous frame has a button pressed, force alternation. + // If there are frames ahead, modify those to use the new button press. + // Do we have a previous frame? No need to check for < replay.Count since we decremented! + if (index >= 0) + { + var previousFrame = (OsuReplayFrame)Frames[index]; + var previousActions = previousFrame.Actions; + + // If a button is already held, then we simply alternate + if (previousActions.Any()) + { + // Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button. + if (previousActions.Contains(action)) + { + action = action == OsuAction.LeftButton ? OsuAction.RightButton : OsuAction.LeftButton; + startFrame.Actions.Clear(); + startFrame.Actions.Add(action); + } + + // We always follow the most recent slider / spinner, so remove any other frames that occur while it exists. + int endIndex = FindInsertionIndex(endFrame); + + if (index < Frames.Count - 1) + Frames.RemoveRange(index + 1, Math.Max(0, endIndex - (index + 1))); + + // After alternating we need to keep holding the other button in the future rather than the previous one. + for (int j = index + 1; j < Frames.Count; ++j) + { + var frame = (OsuReplayFrame)Frames[j]; + + // Don't affect frames which stop pressing a button! + if (j < Frames.Count - 1 || frame.Actions.SequenceEqual(previousActions)) + { + frame.Actions.Clear(); + frame.Actions.Add(action); + } + } + } + } + + AddFrameToReplay(startFrame); + + // We add intermediate frames for spinning / following a slider here. + if (h is Spinner) + { + Spinner s = h as Spinner; + + Vector2 difference = startPosition - SPINNER_CENTRE; + + float radius = difference.Length; + float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X); + + double t; + + for (double j = h.StartTime + FrameDelay; j < s.EndTime; j += FrameDelay) + { + t = ApplyModsToTime(j - h.StartTime) * spinnerDirection; + + Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); + AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action)); + } + + t = ApplyModsToTime(s.EndTime - h.StartTime) * spinnerDirection; + Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); + + AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); + + endFrame.Position = endPosition; + } + else if (h is Slider) + { + Slider s = h as Slider; + + for (double j = FrameDelay; j < s.Duration; j += FrameDelay) + { + Vector2 pos = s.StackedPositionAt(j / s.Duration); + AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action)); + } + + AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(s.StackedEndPosition.X, s.StackedEndPosition.Y), action)); + } + + // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! + if (Frames[Frames.Count - 1].Time <= endFrame.Time) + AddFrameToReplay(endFrame); + } + + #endregion + } +} diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index b2adeda73b..324d0502c1 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -1,96 +1,96 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; -using System; -using System.Collections.Generic; -using osu.Game.Rulesets.Replays; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Osu.Replays -{ - public abstract class OsuAutoGeneratorBase : AutoGenerator - { - #region Constants - - /// - /// Constants (for spinners). - /// - protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192); - protected const float SPIN_RADIUS = 50; - - /// - /// The time in ms between each ReplayFrame. - /// - protected readonly double FrameDelay; - - #endregion - - #region Construction / Initialisation - - protected Replay Replay; - protected List Frames => Replay.Frames; - - protected OsuAutoGeneratorBase(Beatmap beatmap) - : base(beatmap) - { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; - - // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. - FrameDelay = ApplyModsToRate(1000.0 / 60.0); - } - - #endregion - - #region Utilities - protected double ApplyModsToTime(double v) => v; - protected double ApplyModsToRate(double v) => v; - - private class ReplayFrameComparer : IComparer - { - public int Compare(ReplayFrame f1, ReplayFrame f2) - { - if (f1 == null) throw new ArgumentNullException(nameof(f1)); - if (f2 == null) throw new ArgumentNullException(nameof(f2)); - - return f1.Time.CompareTo(f2.Time); - } - } - - private static readonly IComparer replay_frame_comparer = new ReplayFrameComparer(); - - protected int FindInsertionIndex(ReplayFrame frame) - { - int index = Frames.BinarySearch(frame, replay_frame_comparer); - - if (index < 0) - { - index = ~index; - } - else - { - // Go to the first index which is actually bigger - while (index < Frames.Count && frame.Time == Frames[index].Time) - { - ++index; - } - } - - return index; - } - - protected void AddFrameToReplay(ReplayFrame frame) => Frames.Insert(FindInsertionIndex(frame), frame); - - protected static Vector2 CirclePosition(double t, double radius) => new Vector2((float)(Math.Cos(t) * radius), (float)(Math.Sin(t) * radius)); - - #endregion - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Replays; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Osu.Replays +{ + public abstract class OsuAutoGeneratorBase : AutoGenerator + { + #region Constants + + /// + /// Constants (for spinners). + /// + protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192); + protected const float SPIN_RADIUS = 50; + + /// + /// The time in ms between each ReplayFrame. + /// + protected readonly double FrameDelay; + + #endregion + + #region Construction / Initialisation + + protected Replay Replay; + protected List Frames => Replay.Frames; + + protected OsuAutoGeneratorBase(Beatmap beatmap) + : base(beatmap) + { + Replay = new Replay + { + User = new User + { + Username = @"Autoplay", + } + }; + + // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. + FrameDelay = ApplyModsToRate(1000.0 / 60.0); + } + + #endregion + + #region Utilities + protected double ApplyModsToTime(double v) => v; + protected double ApplyModsToRate(double v) => v; + + private class ReplayFrameComparer : IComparer + { + public int Compare(ReplayFrame f1, ReplayFrame f2) + { + if (f1 == null) throw new ArgumentNullException(nameof(f1)); + if (f2 == null) throw new ArgumentNullException(nameof(f2)); + + return f1.Time.CompareTo(f2.Time); + } + } + + private static readonly IComparer replay_frame_comparer = new ReplayFrameComparer(); + + protected int FindInsertionIndex(ReplayFrame frame) + { + int index = Frames.BinarySearch(frame, replay_frame_comparer); + + if (index < 0) + { + index = ~index; + } + else + { + // Go to the first index which is actually bigger + while (index < Frames.Count && frame.Time == Frames[index].Time) + { + ++index; + } + } + + return index; + } + + protected void AddFrameToReplay(ReplayFrame frame) => Frames.Insert(FindInsertionIndex(frame), frame); + + protected static Vector2 CirclePosition(double t, double radius) => new Vector2((float)(Math.Cos(t) * radius), (float)(Math.Sin(t) * radius)); + + #endregion + } +} diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index bcdfe07417..6f2512cc33 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Rulesets.Replays.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Replays -{ - public class OsuReplayFrame : ReplayFrame, IConvertibleReplayFrame - { - public Vector2 Position; - public List Actions = new List(); - - public OsuReplayFrame() - { - } - - public OsuReplayFrame(double time, Vector2 position, params OsuAction[] actions) - : base(time) - { - Position = position; - Actions.AddRange(actions); - } - - public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) - { - Position = legacyFrame.Position; - if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); - if (legacyFrame.MouseRight) Actions.Add(OsuAction.RightButton); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Rulesets.Replays.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Replays +{ + public class OsuReplayFrame : ReplayFrame, IConvertibleReplayFrame + { + public Vector2 Position; + public List Actions = new List(); + + public OsuReplayFrame() + { + } + + public OsuReplayFrame(double time, Vector2 position, params OsuAction[] actions) + : base(time) + { + Position = position; + Actions.AddRange(actions); + } + + public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) + { + Position = legacyFrame.Position; + if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); + if (legacyFrame.MouseRight) Actions.Add(OsuAction.RightButton); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs index 69154a1d0c..f9e5bfa89b 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs @@ -1,45 +1,45 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Replays; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Replays -{ - public class OsuReplayInputHandler : FramedReplayInputHandler - { - public OsuReplayInputHandler(Replay replay) - : base(replay) - { - } - - protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); - - protected Vector2? Position - { - get - { - if (!HasFrames) - return null; - - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); - } - } - - public override List GetPendingStates() - { - return new List - { - new ReplayState - { - Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)), - PressedActions = CurrentFrame.Actions - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Replays; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Replays +{ + public class OsuReplayInputHandler : FramedReplayInputHandler + { + public OsuReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); + + protected Vector2? Position + { + get + { + if (!HasFrames) + return null; + + return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + } + } + + public override List GetPendingStates() + { + return new List + { + new ReplayState + { + Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)), + PressedActions = CurrentFrame.Actions + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs index 25d88a38f3..8f0feca207 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs @@ -1,199 +1,199 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Beatmaps; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Osu.Scoring -{ - public class OsuPerformanceCalculator : PerformanceCalculator - { - private readonly int countHitCircles; - private readonly int beatmapMaxCombo; - - private Mod[] mods; - private double realApproachRate; - private double accuracy; - private int scoreMaxCombo; - private int count300; - private int count100; - private int count50; - private int countMiss; - - public OsuPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score) - : base(ruleset, beatmap, score) - { - countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); - - beatmapMaxCombo = Beatmap.HitObjects.Count; - beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count) + 1; - } - - public override double Calculate(Dictionary categoryRatings = null) - { - mods = Score.Mods; - accuracy = Score.Accuracy; - scoreMaxCombo = Score.MaxCombo; - count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]); - count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]); - count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); - - // Don't count scores made with supposedly unranked mods - if (mods.Any(m => !m.Ranked)) - return 0; - - // Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done - // locally for now as doing so would modify animations and other things unexpectedly - // DO NOT MODIFY THIS - double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate; - if (mods.Any(m => m is OsuModHardRock)) - ar = Math.Min(10, ar * 1.4); - if (mods.Any(m => m is OsuModEasy)) - ar = Math.Max(0, ar / 2); - double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450); - realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5; - - // Custom multipliers for NoFail and SpunOut. - double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things - - if (mods.Any(m => m is OsuModNoFail)) - multiplier *= 0.90f; - - if (mods.Any(m => m is OsuModSpunOut)) - multiplier *= 0.95f; - - double aimValue = computeAimValue(); - double speedValue = computeSpeedValue(); - double accuracyValue = computeAccuracyValue(); - double totalValue = - Math.Pow( - Math.Pow(aimValue, 1.1f) + - Math.Pow(speedValue, 1.1f) + - Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f - ) * multiplier; - - if (categoryRatings != null) - { - categoryRatings.Add("Aim", aimValue); - categoryRatings.Add("Speed", speedValue); - categoryRatings.Add("Accuracy", accuracyValue); - } - - return totalValue; - } - - private double computeAimValue() - { - double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; - - // Longer maps are worth more - double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); - - aimValue *= lengthBonus; - - // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - aimValue *= Math.Pow(0.97f, countMiss); - - // Combo scaling - if (beatmapMaxCombo > 0) - aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); - - double approachRateFactor = 1.0f; - if (realApproachRate > 10.33f) - approachRateFactor += 0.45f * (realApproachRate - 10.33f); - else if (realApproachRate < 8.0f) - { - // HD is worth more with lower ar! - if (mods.Any(h => h is OsuModHidden)) - approachRateFactor += 0.02f * (8.0f - realApproachRate); - else - approachRateFactor += 0.01f * (8.0f - realApproachRate); - } - - aimValue *= approachRateFactor; - - if (mods.Any(h => h is OsuModHidden)) - aimValue *= 1.18f; - - if (mods.Any(h => h is OsuModFlashlight)) - { - // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. - aimValue *= 1.45f * lengthBonus; - } - - // Scale the aim value with accuracy _slightly_ - aimValue *= 0.5f + accuracy / 2.0f; - // It is important to also consider accuracy difficulty when doing that - aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500; - - return aimValue; - } - - private double computeSpeedValue() - { - double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; - - // Longer maps are worth more - speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); - - // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - speedValue *= Math.Pow(0.97f, countMiss); - - // Combo scaling - if (beatmapMaxCombo > 0) - speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); - - // Scale the speed value with accuracy _slightly_ - speedValue *= 0.5f + accuracy / 2.0f; - // It is important to also consider accuracy difficulty when doing that - speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500; - - return speedValue; - } - - private double computeAccuracyValue() - { - // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window - double betterAccuracyPercentage; - int amountHitObjectsWithAccuracy = countHitCircles; - - if (amountHitObjectsWithAccuracy > 0) - betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6); - else - betterAccuracyPercentage = 0; - - // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points - if (betterAccuracyPercentage < 0) - betterAccuracyPercentage = 0; - - // Lots of arbitrary values from testing. - // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f; - - // Bonus for many hitcircles - it's harder to keep good accuracy up for longer - accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); - - if (mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.02f; - if (mods.Any(m => m is OsuModFlashlight)) - accuracyValue *= 1.02f; - - return accuracyValue; - } - - private double totalHits => count300 + count100 + count50 + countMiss; - private double totalSuccessfulHits => count300 + count100 + count50; - - protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + public class OsuPerformanceCalculator : PerformanceCalculator + { + private readonly int countHitCircles; + private readonly int beatmapMaxCombo; + + private Mod[] mods; + private double realApproachRate; + private double accuracy; + private int scoreMaxCombo; + private int count300; + private int count100; + private int count50; + private int countMiss; + + public OsuPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score) + : base(ruleset, beatmap, score) + { + countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); + + beatmapMaxCombo = Beatmap.HitObjects.Count; + beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count) + 1; + } + + public override double Calculate(Dictionary categoryRatings = null) + { + mods = Score.Mods; + accuracy = Score.Accuracy; + scoreMaxCombo = Score.MaxCombo; + count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]); + count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]); + count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]); + countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + + // Don't count scores made with supposedly unranked mods + if (mods.Any(m => !m.Ranked)) + return 0; + + // Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done + // locally for now as doing so would modify animations and other things unexpectedly + // DO NOT MODIFY THIS + double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate; + if (mods.Any(m => m is OsuModHardRock)) + ar = Math.Min(10, ar * 1.4); + if (mods.Any(m => m is OsuModEasy)) + ar = Math.Max(0, ar / 2); + double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450); + realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5; + + // Custom multipliers for NoFail and SpunOut. + double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things + + if (mods.Any(m => m is OsuModNoFail)) + multiplier *= 0.90f; + + if (mods.Any(m => m is OsuModSpunOut)) + multiplier *= 0.95f; + + double aimValue = computeAimValue(); + double speedValue = computeSpeedValue(); + double accuracyValue = computeAccuracyValue(); + double totalValue = + Math.Pow( + Math.Pow(aimValue, 1.1f) + + Math.Pow(speedValue, 1.1f) + + Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f + ) * multiplier; + + if (categoryRatings != null) + { + categoryRatings.Add("Aim", aimValue); + categoryRatings.Add("Speed", speedValue); + categoryRatings.Add("Accuracy", accuracyValue); + } + + return totalValue; + } + + private double computeAimValue() + { + double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + + // Longer maps are worth more + double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + + aimValue *= lengthBonus; + + // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available + aimValue *= Math.Pow(0.97f, countMiss); + + // Combo scaling + if (beatmapMaxCombo > 0) + aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + + double approachRateFactor = 1.0f; + if (realApproachRate > 10.33f) + approachRateFactor += 0.45f * (realApproachRate - 10.33f); + else if (realApproachRate < 8.0f) + { + // HD is worth more with lower ar! + if (mods.Any(h => h is OsuModHidden)) + approachRateFactor += 0.02f * (8.0f - realApproachRate); + else + approachRateFactor += 0.01f * (8.0f - realApproachRate); + } + + aimValue *= approachRateFactor; + + if (mods.Any(h => h is OsuModHidden)) + aimValue *= 1.18f; + + if (mods.Any(h => h is OsuModFlashlight)) + { + // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. + aimValue *= 1.45f * lengthBonus; + } + + // Scale the aim value with accuracy _slightly_ + aimValue *= 0.5f + accuracy / 2.0f; + // It is important to also consider accuracy difficulty when doing that + aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500; + + return aimValue; + } + + private double computeSpeedValue() + { + double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + + // Longer maps are worth more + speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + + // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available + speedValue *= Math.Pow(0.97f, countMiss); + + // Combo scaling + if (beatmapMaxCombo > 0) + speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + + // Scale the speed value with accuracy _slightly_ + speedValue *= 0.5f + accuracy / 2.0f; + // It is important to also consider accuracy difficulty when doing that + speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500; + + return speedValue; + } + + private double computeAccuracyValue() + { + // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window + double betterAccuracyPercentage; + int amountHitObjectsWithAccuracy = countHitCircles; + + if (amountHitObjectsWithAccuracy > 0) + betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6); + else + betterAccuracyPercentage = 0; + + // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points + if (betterAccuracyPercentage < 0) + betterAccuracyPercentage = 0; + + // Lots of arbitrary values from testing. + // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution + double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f; + + // Bonus for many hitcircles - it's harder to keep good accuracy up for longer + accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); + + if (mods.Any(m => m is OsuModHidden)) + accuracyValue *= 1.02f; + if (mods.Any(m => m is OsuModFlashlight)) + accuracyValue *= 1.02f; + + return accuracyValue; + } + + private double totalHits => count300 + count100 + count50 + countMiss; + private double totalSuccessfulHits => count300 + count100 + count50; + + protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); + } +} diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index d41331e3bd..01b92255ae 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,109 +1,109 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Osu.Scoring -{ - internal class OsuScoreProcessor : ScoreProcessor - { - public OsuScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) - { - } - - private float hpDrainRate; - - private readonly Dictionary scoreResultCounts = new Dictionary(); - private readonly Dictionary comboResultCounts = new Dictionary(); - - protected override void SimulateAutoplay(Beatmap beatmap) - { - hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; - - foreach (var obj in beatmap.HitObjects) - { - if (obj is Slider slider) - { - // Head - AddJudgement(new OsuJudgement { Result = HitResult.Great }); - - // Ticks - foreach (var unused in slider.NestedHitObjects.OfType()) - AddJudgement(new OsuJudgement { Result = HitResult.Great }); - - //Repeats - foreach (var unused in slider.NestedHitObjects.OfType()) - AddJudgement(new OsuJudgement { Result = HitResult.Great }); - } - - AddJudgement(new OsuJudgement { Result = HitResult.Great }); - } - } - - protected override void Reset(bool storeResults) - { - base.Reset(storeResults); - - scoreResultCounts.Clear(); - comboResultCounts.Clear(); - } - - public override void PopulateScore(Score score) - { - base.PopulateScore(score); - - score.Statistics[HitResult.Great] = scoreResultCounts.GetOrDefault(HitResult.Great); - score.Statistics[HitResult.Good] = scoreResultCounts.GetOrDefault(HitResult.Good); - score.Statistics[HitResult.Meh] = scoreResultCounts.GetOrDefault(HitResult.Meh); - score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss); - } - - private const double harshness = 0.01; - - protected override void OnNewJudgement(Judgement judgement) - { - base.OnNewJudgement(judgement); - - var osuJudgement = (OsuJudgement)judgement; - - if (judgement.Result != HitResult.None) - { - scoreResultCounts[judgement.Result] = scoreResultCounts.GetOrDefault(judgement.Result) + 1; - comboResultCounts[osuJudgement.Combo] = comboResultCounts.GetOrDefault(osuJudgement.Combo) + 1; - } - - switch (judgement.Result) - { - case HitResult.Great: - Health.Value += (10.2 - hpDrainRate) * harshness; - break; - - case HitResult.Good: - Health.Value += (8 - hpDrainRate) * harshness; - break; - - case HitResult.Meh: - Health.Value += (4 - hpDrainRate) * harshness; - break; - - /*case HitResult.SliderTick: - Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01; - break;*/ - - case HitResult.Miss: - Health.Value -= hpDrainRate * (harshness * 2); - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + internal class OsuScoreProcessor : ScoreProcessor + { + public OsuScoreProcessor(RulesetContainer rulesetContainer) + : base(rulesetContainer) + { + } + + private float hpDrainRate; + + private readonly Dictionary scoreResultCounts = new Dictionary(); + private readonly Dictionary comboResultCounts = new Dictionary(); + + protected override void SimulateAutoplay(Beatmap beatmap) + { + hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; + + foreach (var obj in beatmap.HitObjects) + { + if (obj is Slider slider) + { + // Head + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + + // Ticks + foreach (var unused in slider.NestedHitObjects.OfType()) + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + + //Repeats + foreach (var unused in slider.NestedHitObjects.OfType()) + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + } + + AddJudgement(new OsuJudgement { Result = HitResult.Great }); + } + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + scoreResultCounts.Clear(); + comboResultCounts.Clear(); + } + + public override void PopulateScore(Score score) + { + base.PopulateScore(score); + + score.Statistics[HitResult.Great] = scoreResultCounts.GetOrDefault(HitResult.Great); + score.Statistics[HitResult.Good] = scoreResultCounts.GetOrDefault(HitResult.Good); + score.Statistics[HitResult.Meh] = scoreResultCounts.GetOrDefault(HitResult.Meh); + score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss); + } + + private const double harshness = 0.01; + + protected override void OnNewJudgement(Judgement judgement) + { + base.OnNewJudgement(judgement); + + var osuJudgement = (OsuJudgement)judgement; + + if (judgement.Result != HitResult.None) + { + scoreResultCounts[judgement.Result] = scoreResultCounts.GetOrDefault(judgement.Result) + 1; + comboResultCounts[osuJudgement.Combo] = comboResultCounts.GetOrDefault(osuJudgement.Combo) + 1; + } + + switch (judgement.Result) + { + case HitResult.Great: + Health.Value += (10.2 - hpDrainRate) * harshness; + break; + + case HitResult.Good: + Health.Value += (8 - hpDrainRate) * harshness; + break; + + case HitResult.Meh: + Health.Value += (4 - hpDrainRate) * harshness; + break; + + /*case HitResult.SliderTick: + Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01; + break;*/ + + case HitResult.Miss: + Health.Value -= hpDrainRate * (harshness * 2); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 3203df16e2..7bd80b5718 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -1,273 +1,273 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.OpenGL.Buffers; -using osu.Framework.Graphics.OpenGL.Vertices; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input; -using osu.Framework.Timing; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Graphics.ES30; - -namespace osu.Game.Rulesets.Osu.UI.Cursor -{ - internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition - { - private int currentIndex; - - private Shader shader; - private Texture texture; - - private Vector2 size => texture.Size * Scale; - - private double timeOffset; - - private float time; - - public override bool IsPresent => true; - - private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); - private const int max_sprites = 2048; - - private readonly TrailPart[] parts = new TrailPart[max_sprites]; - - private Vector2? lastPosition; - - private readonly InputResampler resampler = new InputResampler(); - - protected override DrawNode CreateDrawNode() => new TrailDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - TrailDrawNode tNode = (TrailDrawNode)node; - tNode.Shader = shader; - tNode.Texture = texture; - tNode.Size = size; - tNode.Time = time; - tNode.Shared = trailDrawNodeSharedData; - - for (int i = 0; i < parts.Length; ++i) - if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) - tNode.Parts[i] = parts[i]; - } - - public CursorTrail() - { - // as we are currently very dependent on having a running clock, let's make our own clock for the time being. - Clock = new FramedClock(); - - RelativeSizeAxes = Axes.Both; - - for (int i = 0; i < max_sprites; i++) - { - parts[i].InvalidationID = 0; - parts[i].WasUpdated = true; - } - } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - - [BackgroundDependencyLoader] - private void load(ShaderManager shaders, TextureStore textures) - { - shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); - texture = textures.Get(@"Cursor/cursortrail"); - Scale = new Vector2(1 / texture.ScaleAdjust); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - resetTime(); - } - - protected override void Update() - { - base.Update(); - - Invalidate(Invalidation.DrawNode, shallPropagate: false); - - const int fade_clock_reset_threshold = 1000000; - - time = (float)(Time.Current - timeOffset) / 300f; - if (time > fade_clock_reset_threshold) - resetTime(); - } - - private void resetTime() - { - for (int i = 0; i < parts.Length; ++i) - { - parts[i].Time -= time; - ++parts[i].InvalidationID; - } - - time = 0; - timeOffset = Time.Current; - } - - protected override bool OnMouseMove(InputState state) - { - Vector2 pos = state.Mouse.NativeState.Position; - - if (lastPosition == null) - { - lastPosition = pos; - resampler.AddPosition(lastPosition.Value); - return base.OnMouseMove(state); - } - - foreach (Vector2 pos2 in resampler.AddPosition(pos)) - { - Trace.Assert(lastPosition.HasValue); - - // ReSharper disable once PossibleInvalidOperationException - Vector2 pos1 = lastPosition.Value; - Vector2 diff = pos2 - pos1; - float distance = diff.Length; - Vector2 direction = diff / distance; - - float interval = size.X / 2 * 0.9f; - - for (float d = interval; d < distance; d += interval) - { - lastPosition = pos1 + direction * d; - addPosition(lastPosition.Value); - } - } - - return base.OnMouseMove(state); - } - - private void addPosition(Vector2 pos) - { - parts[currentIndex].Position = pos; - parts[currentIndex].Time = time; - ++parts[currentIndex].InvalidationID; - - currentIndex = (currentIndex + 1) % max_sprites; - } - - private struct TrailPart - { - public Vector2 Position; - public float Time; - public long InvalidationID; - public bool WasUpdated; - } - - private class TrailDrawNodeSharedData - { - public VertexBuffer VertexBuffer; - } - - private class TrailDrawNode : DrawNode - { - public Shader Shader; - public Texture Texture; - - public float Time; - public TrailDrawNodeSharedData Shared; - - public readonly TrailPart[] Parts = new TrailPart[max_sprites]; - public Vector2 Size; - - public TrailDrawNode() - { - for (int i = 0; i < max_sprites; i++) - { - Parts[i].InvalidationID = 0; - Parts[i].WasUpdated = false; - } - } - - public override void Draw(Action vertexAction) - { - if (Shared.VertexBuffer == null) - Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); - - Shader.GetUniform("g_FadeClock").Value = Time; - - int updateStart = -1, updateEnd = 0; - for (int i = 0; i < Parts.Length; ++i) - { - if (Parts[i].WasUpdated) - { - if (updateStart == -1) - updateStart = i; - updateEnd = i + 1; - - int start = i * 4; - int end = start; - - Vector2 pos = Parts[i].Position; - float time = Parts[i].Time; - - Texture.DrawQuad( - new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), - DrawInfo.Colour, - null, - v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex - { - Position = v.Position, - TexturePosition = v.TexturePosition, - Time = time + 1, - Colour = v.Colour, - }); - - Parts[i].WasUpdated = false; - } - else if (updateStart != -1) - { - Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); - updateStart = -1; - } - } - - // Update all remaining vertices that have been changed. - if (updateStart != -1) - Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); - - base.Draw(vertexAction); - - Shader.Bind(); - - Texture.TextureGL.Bind(); - Shared.VertexBuffer.Draw(); - - Shader.Unbind(); - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct TexturedTrailVertex : IEquatable, IVertex - { - [VertexMember(2, VertexAttribPointerType.Float)] - public Vector2 Position; - [VertexMember(4, VertexAttribPointerType.Float)] - public Color4 Colour; - [VertexMember(2, VertexAttribPointerType.Float)] - public Vector2 TexturePosition; - [VertexMember(1, VertexAttribPointerType.Float)] - public float Time; - - public bool Equals(TexturedTrailVertex other) - { - return Position.Equals(other.Position) - && TexturePosition.Equals(other.TexturePosition) - && Colour.Equals(other.Colour) - && Time.Equals(other.Time); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Buffers; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using osu.Framework.Timing; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.ES30; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition + { + private int currentIndex; + + private Shader shader; + private Texture texture; + + private Vector2 size => texture.Size * Scale; + + private double timeOffset; + + private float time; + + public override bool IsPresent => true; + + private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); + private const int max_sprites = 2048; + + private readonly TrailPart[] parts = new TrailPart[max_sprites]; + + private Vector2? lastPosition; + + private readonly InputResampler resampler = new InputResampler(); + + protected override DrawNode CreateDrawNode() => new TrailDrawNode(); + + protected override void ApplyDrawNode(DrawNode node) + { + base.ApplyDrawNode(node); + + TrailDrawNode tNode = (TrailDrawNode)node; + tNode.Shader = shader; + tNode.Texture = texture; + tNode.Size = size; + tNode.Time = time; + tNode.Shared = trailDrawNodeSharedData; + + for (int i = 0; i < parts.Length; ++i) + if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) + tNode.Parts[i] = parts[i]; + } + + public CursorTrail() + { + // as we are currently very dependent on having a running clock, let's make our own clock for the time being. + Clock = new FramedClock(); + + RelativeSizeAxes = Axes.Both; + + for (int i = 0; i < max_sprites; i++) + { + parts[i].InvalidationID = 0; + parts[i].WasUpdated = true; + } + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, TextureStore textures) + { + shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); + texture = textures.Get(@"Cursor/cursortrail"); + Scale = new Vector2(1 / texture.ScaleAdjust); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + resetTime(); + } + + protected override void Update() + { + base.Update(); + + Invalidate(Invalidation.DrawNode, shallPropagate: false); + + const int fade_clock_reset_threshold = 1000000; + + time = (float)(Time.Current - timeOffset) / 300f; + if (time > fade_clock_reset_threshold) + resetTime(); + } + + private void resetTime() + { + for (int i = 0; i < parts.Length; ++i) + { + parts[i].Time -= time; + ++parts[i].InvalidationID; + } + + time = 0; + timeOffset = Time.Current; + } + + protected override bool OnMouseMove(InputState state) + { + Vector2 pos = state.Mouse.NativeState.Position; + + if (lastPosition == null) + { + lastPosition = pos; + resampler.AddPosition(lastPosition.Value); + return base.OnMouseMove(state); + } + + foreach (Vector2 pos2 in resampler.AddPosition(pos)) + { + Trace.Assert(lastPosition.HasValue); + + // ReSharper disable once PossibleInvalidOperationException + Vector2 pos1 = lastPosition.Value; + Vector2 diff = pos2 - pos1; + float distance = diff.Length; + Vector2 direction = diff / distance; + + float interval = size.X / 2 * 0.9f; + + for (float d = interval; d < distance; d += interval) + { + lastPosition = pos1 + direction * d; + addPosition(lastPosition.Value); + } + } + + return base.OnMouseMove(state); + } + + private void addPosition(Vector2 pos) + { + parts[currentIndex].Position = pos; + parts[currentIndex].Time = time; + ++parts[currentIndex].InvalidationID; + + currentIndex = (currentIndex + 1) % max_sprites; + } + + private struct TrailPart + { + public Vector2 Position; + public float Time; + public long InvalidationID; + public bool WasUpdated; + } + + private class TrailDrawNodeSharedData + { + public VertexBuffer VertexBuffer; + } + + private class TrailDrawNode : DrawNode + { + public Shader Shader; + public Texture Texture; + + public float Time; + public TrailDrawNodeSharedData Shared; + + public readonly TrailPart[] Parts = new TrailPart[max_sprites]; + public Vector2 Size; + + public TrailDrawNode() + { + for (int i = 0; i < max_sprites; i++) + { + Parts[i].InvalidationID = 0; + Parts[i].WasUpdated = false; + } + } + + public override void Draw(Action vertexAction) + { + if (Shared.VertexBuffer == null) + Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); + + Shader.GetUniform("g_FadeClock").Value = Time; + + int updateStart = -1, updateEnd = 0; + for (int i = 0; i < Parts.Length; ++i) + { + if (Parts[i].WasUpdated) + { + if (updateStart == -1) + updateStart = i; + updateEnd = i + 1; + + int start = i * 4; + int end = start; + + Vector2 pos = Parts[i].Position; + float time = Parts[i].Time; + + Texture.DrawQuad( + new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), + DrawInfo.Colour, + null, + v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex + { + Position = v.Position, + TexturePosition = v.TexturePosition, + Time = time + 1, + Colour = v.Colour, + }); + + Parts[i].WasUpdated = false; + } + else if (updateStart != -1) + { + Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); + updateStart = -1; + } + } + + // Update all remaining vertices that have been changed. + if (updateStart != -1) + Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); + + base.Draw(vertexAction); + + Shader.Bind(); + + Texture.TextureGL.Bind(); + Shared.VertexBuffer.Draw(); + + Shader.Unbind(); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct TexturedTrailVertex : IEquatable, IVertex + { + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 Position; + [VertexMember(4, VertexAttribPointerType.Float)] + public Color4 Colour; + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 TexturePosition; + [VertexMember(1, VertexAttribPointerType.Float)] + public float Time; + + public bool Equals(TexturedTrailVertex other) + { + return Position.Equals(other.Position) + && TexturePosition.Equals(other.TexturePosition) + && Colour.Equals(other.Colour) + && Time.Equals(other.Time); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index ac81d93309..e7f17dd86b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -1,189 +1,189 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Skinning; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.UI.Cursor -{ - public class GameplayCursor : CursorContainer, IKeyBindingHandler - { - protected override Drawable CreateCursor() => new OsuCursor(); - - protected override Container Content => fadeContainer; - - private readonly Container fadeContainer; - - public GameplayCursor() - { - InternalChild = fadeContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new CursorTrail { Depth = 1 } - } - }; - } - - private int downCount; - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - downCount++; - ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); - break; - } - - return false; - } - - public bool OnReleased(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - if (--downCount == 0) - ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); - break; - } - - return false; - } - - public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. - - protected override void PopIn() - { - fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); - } - - protected override void PopOut() - { - fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); - } - - public class OsuCursor : Container - { - private Drawable cursorContainer; - - private Bindable cursorScale; - private Bindable autoCursorScale; - private Bindable beatmap; - - public OsuCursor() - { - Origin = Anchor.Centre; - Size = new Vector2(42); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuGameBase game) - { - Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 6, - BorderColour = Color4.White, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Pink.Opacity(0.5f), - Radius = 5, - }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 3, - BorderColour = Color4.White.Opacity(0.5f), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - }, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.1f), - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - }, - }, - } - }, restrictSize: false) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }; - - beatmap = game.Beatmap.GetBoundCopy(); - beatmap.ValueChanged += v => calculateScale(); - - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += v => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += v => calculateScale(); - - calculateScale(); - } - - private void calculateScale() - { - float scale = (float)cursorScale.Value; - - if (autoCursorScale && beatmap.Value != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); - } - - cursorContainer.Scale = new Vector2(scale); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Skinning; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class GameplayCursor : CursorContainer, IKeyBindingHandler + { + protected override Drawable CreateCursor() => new OsuCursor(); + + protected override Container Content => fadeContainer; + + private readonly Container fadeContainer; + + public GameplayCursor() + { + InternalChild = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CursorTrail { Depth = 1 } + } + }; + } + + private int downCount; + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + downCount++; + ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (--downCount == 0) + ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); + break; + } + + return false; + } + + public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. + + protected override void PopIn() + { + fadeContainer.FadeTo(1, 300, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + } + + protected override void PopOut() + { + fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + } + + public class OsuCursor : Container + { + private Drawable cursorContainer; + + private Bindable cursorScale; + private Bindable autoCursorScale; + private Bindable beatmap; + + public OsuCursor() + { + Origin = Anchor.Centre; + Size = new Vector2(42); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, OsuGameBase game) + { + Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 6, + BorderColour = Color4.White, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Pink.Opacity(0.5f), + Radius = 5, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 3, + BorderColour = Color4.White.Opacity(0.5f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.1f), + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + }, + }, + } + }, restrictSize: false) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }; + + beatmap = game.Beatmap.GetBoundCopy(); + beatmap.ValueChanged += v => calculateScale(); + + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale.ValueChanged += v => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += v => calculateScale(); + + calculateScale(); + } + + private void calculateScale() + { + float scale = (float)cursorScale.Value; + + if (autoCursorScale && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + } + + cursorContainer.Scale = new Vector2(scale); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9010f66acb..8330aa8e9d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,89 +1,89 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; -using osu.Game.Rulesets.UI; -using System.Linq; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Osu.Judgements; - -namespace osu.Game.Rulesets.Osu.UI -{ - public class OsuPlayfield : Playfield - { - private readonly Container approachCircles; - private readonly JudgementContainer judgementLayer; - private readonly ConnectionRenderer connectionLayer; - - // Todo: This should not be a thing, but is currently required for the editor - // https://github.com/ppy/osu-framework/issues/1283 - protected virtual bool ProxyApproachCircles => true; - protected virtual bool DisplayJudgements => true; - - public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - - public OsuPlayfield() - : base(BASE_SIZE.X) - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - AddRange(new Drawable[] - { - connectionLayer = new FollowPointRenderer - { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, - approachCircles = new Container - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, - }); - } - - public override void Add(DrawableHitObject h) - { - h.OnJudgement += onJudgement; - - var c = h as IDrawableHitObjectWithProxiedApproach; - if (c != null && ProxyApproachCircles) - approachCircles.Add(c.ProxiedLayer.CreateProxy()); - - base.Add(h); - } - - public override void PostProcess() - { - connectionLayer.HitObjects = HitObjects.Objects - .Select(d => d.HitObject) - .OrderBy(h => h.StartTime).OfType(); - } - - private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) - { - if (!judgedObject.DisplayJudgement || !DisplayJudgements) - return; - - DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) - { - Origin = Anchor.Centre, - Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset - }; - - judgementLayer.Add(explosion); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; +using osu.Game.Rulesets.UI; +using System.Linq; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuPlayfield : Playfield + { + private readonly Container approachCircles; + private readonly JudgementContainer judgementLayer; + private readonly ConnectionRenderer connectionLayer; + + // Todo: This should not be a thing, but is currently required for the editor + // https://github.com/ppy/osu-framework/issues/1283 + protected virtual bool ProxyApproachCircles => true; + protected virtual bool DisplayJudgements => true; + + public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); + + public OsuPlayfield() + : base(BASE_SIZE.X) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + AddRange(new Drawable[] + { + connectionLayer = new FollowPointRenderer + { + RelativeSizeAxes = Axes.Both, + Depth = 2, + }, + judgementLayer = new JudgementContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1, + }, + approachCircles = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = -1, + }, + }); + } + + public override void Add(DrawableHitObject h) + { + h.OnJudgement += onJudgement; + + var c = h as IDrawableHitObjectWithProxiedApproach; + if (c != null && ProxyApproachCircles) + approachCircles.Add(c.ProxiedLayer.CreateProxy()); + + base.Add(h); + } + + public override void PostProcess() + { + connectionLayer.HitObjects = HitObjects.Objects + .Select(d => d.HitObject) + .OrderBy(h => h.StartTime).OfType(); + } + + private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + if (!judgedObject.DisplayJudgement || !DisplayJudgements) + return; + + DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) + { + Origin = Anchor.Centre, + Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset + }; + + judgementLayer.Add(explosion); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index b825ba73b7..22c7b719cd 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -1,62 +1,62 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Cursor; -using osu.Framework.Input; -using OpenTK; -using osu.Game.Beatmaps; -using osu.Game.Input.Handlers; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Osu.UI.Cursor; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Osu.UI -{ - public class OsuRulesetContainer : RulesetContainer - { - public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(ruleset, beatmap, isForCurrentRuleset) - { - } - - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); - - protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); - - protected override BeatmapProcessor CreateBeatmapProcessor() => new OsuBeatmapProcessor(); - - protected override Playfield CreatePlayfield() => new OsuPlayfield(); - - public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - - protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) - { - if (h is HitCircle circle) - return new DrawableHitCircle(circle); - - if (h is Slider slider) - return new DrawableSlider(slider); - - if (h is Spinner spinner) - return new DrawableSpinner(spinner); - return null; - } - - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); - - protected override Vector2 GetAspectAdjustedSize() - { - var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); - return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y); - } - - protected override CursorContainer CreateCursor() => new GameplayCursor(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; +using OpenTK; +using osu.Game.Beatmaps; +using osu.Game.Input.Handlers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuRulesetContainer : RulesetContainer + { + public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); + + protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); + + protected override BeatmapProcessor CreateBeatmapProcessor() => new OsuBeatmapProcessor(); + + protected override Playfield CreatePlayfield() => new OsuPlayfield(); + + public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); + + protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) + { + if (h is HitCircle circle) + return new DrawableHitCircle(circle); + + if (h is Slider slider) + return new DrawableSlider(slider); + + if (h is Spinner spinner) + return new DrawableSpinner(spinner); + return null; + } + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); + + protected override Vector2 GetAspectAdjustedSize() + { + var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); + return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y); + } + + protected override CursorContainer CreateCursor() => new GameplayCursor(); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuSettings.cs index d8f0aa9ebf..31ad6701fd 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettings.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Configuration; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Rulesets.Osu.UI -{ - public class OsuSettings : SettingsSubsection - { - protected override string Header => "osu!"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Snaking in sliders", - Bindable = config.GetBindable(OsuSetting.SnakingInSliders) - }, - new SettingsCheckbox - { - LabelText = "Snaking out sliders", - Bindable = config.GetBindable(OsuSetting.SnakingOutSliders) - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuSettings : SettingsSubsection + { + protected override string Header => "osu!"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Snaking in sliders", + Bindable = config.GetBindable(OsuSetting.SnakingInSliders) + }, + new SettingsCheckbox + { + LabelText = "Snaking out sliders", + Bindable = config.GetBindable(OsuSetting.SnakingOutSliders) + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index 385e041ace..aa61f2d60b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -1,73 +1,73 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Taiko.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Rulesets.Taiko.Tests -{ - public class TaikoBeatmapConversionTest : BeatmapConversionTest - { - protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - - private bool isForCurrentRuleset; - - [NonParallelizable] - [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")] - [TestCase("slider-generating-drumroll", false)] - public void Test(string name, bool isForCurrentRuleset) - { - this.isForCurrentRuleset = isForCurrentRuleset; - base.Test(name); - } - - protected override IEnumerable CreateConvertValue(HitObject hitObject) - { - yield return new ConvertValue - { - StartTime = hitObject.StartTime, - EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, - IsRim = hitObject is RimHit, - IsCentre = hitObject is CentreHit, - IsDrumRoll = hitObject is DrumRoll, - IsSwell = hitObject is Swell, - IsStrong = ((TaikoHitObject)hitObject).IsStrong - }; - } - - protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset); - } - - public struct ConvertValue : IEquatable - { - /// - /// A sane value to account for osu!stable using ints everwhere. - /// - private const float conversion_lenience = 2; - - public double StartTime; - public double EndTime; - public bool IsRim; - public bool IsCentre; - public bool IsDrumRoll; - public bool IsSwell; - public bool IsStrong; - - public bool Equals(ConvertValue other) - => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) - && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) - && IsRim == other.IsRim - && IsCentre == other.IsCentre - && IsDrumRoll == other.IsDrumRoll - && IsSwell == other.IsSwell - && IsStrong == other.IsStrong; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TaikoBeatmapConversionTest : BeatmapConversionTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; + + private bool isForCurrentRuleset; + + [NonParallelizable] + [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")] + [TestCase("slider-generating-drumroll", false)] + public void Test(string name, bool isForCurrentRuleset) + { + this.isForCurrentRuleset = isForCurrentRuleset; + base.Test(name); + } + + protected override IEnumerable CreateConvertValue(HitObject hitObject) + { + yield return new ConvertValue + { + StartTime = hitObject.StartTime, + EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, + IsRim = hitObject is RimHit, + IsCentre = hitObject is CentreHit, + IsDrumRoll = hitObject is DrumRoll, + IsSwell = hitObject is Swell, + IsStrong = ((TaikoHitObject)hitObject).IsStrong + }; + } + + protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset); + } + + public struct ConvertValue : IEquatable + { + /// + /// A sane value to account for osu!stable using ints everwhere. + /// + private const float conversion_lenience = 2; + + public double StartTime; + public double EndTime; + public bool IsRim; + public bool IsCentre; + public bool IsDrumRoll; + public bool IsSwell; + public bool IsStrong; + + public bool Equals(ConvertValue other) + => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) + && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) + && IsRim == other.IsRim + && IsCentre == other.IsCentre + && IsDrumRoll == other.IsDrumRoll + && IsSwell == other.IsSwell + && IsStrong == other.IsStrong; + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs index 80721271d6..322e9b6bd4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs @@ -1,44 +1,44 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Taiko.Audio; -using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Taiko.Tests -{ - [TestFixture] - public class TestCaseInputDrum : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(InputDrum), - typeof(DrumSampleMapping), - typeof(SampleInfo), - typeof(SampleControlPoint) - }; - - public TestCaseInputDrum() - { - Add(new TaikoInputManager(new RulesetInfo { ID = 1 }) - { - RelativeSizeAxes = Axes.Both, - Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200), - Child = new InputDrum(new ControlPointInfo()) - } - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestCaseInputDrum : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(InputDrum), + typeof(DrumSampleMapping), + typeof(SampleInfo), + typeof(SampleControlPoint) + }; + + public TestCaseInputDrum() + { + Add(new TaikoInputManager(new RulesetInfo { ID = 1 }) + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200), + Child = new InputDrum(new ControlPointInfo()) + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Taiko.Tests/TestCasePerformancePoints.cs index f6b0ceb7bd..2fd9161d13 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCasePerformancePoints.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCasePerformancePoints.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Rulesets.Taiko.Tests -{ - [TestFixture] - public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints - { - public TestCasePerformancePoints() - : base(new TaikoRuleset()) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints + { + public TestCasePerformancePoints() + : base(new TaikoRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index 3fd16ed1b5..aa7318b863 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -1,240 +1,240 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.MathUtils; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Judgements; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.Objects.Drawables; -using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Tests.Beatmaps; -using osu.Game.Tests.Visual; -using OpenTK; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Tests -{ - [TestFixture] - public class TestCaseTaikoPlayfield : OsuTestCase - { - private const double default_duration = 1000; - private const float scroll_time = 1000; - - protected override double TimePerAction => default_duration * 2; - - private readonly Random rng = new Random(1337); - private TaikoRulesetContainer rulesetContainer; - private Container playfieldContainer; - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - AddStep("Hit!", () => addHitJudgement(false)); - AddStep("Kiai hit", () => addHitJudgement(true)); - AddStep("Miss :(", addMissJudgement); - AddStep("DrumRoll", () => addDrumRoll(false)); - AddStep("Strong DrumRoll", () => addDrumRoll(true)); - AddStep("Swell", () => addSwell()); - AddStep("Centre", () => addCentreHit(false)); - AddStep("Strong Centre", () => addCentreHit(true)); - AddStep("Rim", () => addRimHit(false)); - AddStep("Strong Rim", () => addRimHit(true)); - AddStep("Add bar line", () => addBarLine(false)); - AddStep("Add major bar line", () => addBarLine(true)); - AddStep("Height test 1", () => changePlayfieldSize(1)); - AddStep("Height test 2", () => changePlayfieldSize(2)); - AddStep("Height test 3", () => changePlayfieldSize(3)); - AddStep("Height test 4", () => changePlayfieldSize(4)); - AddStep("Height test 5", () => changePlayfieldSize(5)); - AddStep("Reset height", () => changePlayfieldSize(6)); - - var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); - - WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap - { - HitObjects = new List { new CentreHit() }, - BeatmapInfo = new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - Metadata = new BeatmapMetadata - { - Artist = @"Unknown", - Title = @"Sample Beatmap", - AuthorString = @"peppy", - }, - }, - ControlPointInfo = controlPointInfo - }); - - var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; - - Add(playfieldContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 768, - Clock = new FramedClock(rateAdjustClock), - Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap, true) } - }); - } - - private void changePlayfieldSize(int step) - { - double delay = 0; - - // Add new hits - switch (step) - { - case 1: - addCentreHit(false); - break; - case 2: - addCentreHit(true); - break; - case 3: - addDrumRoll(false); - break; - case 4: - addDrumRoll(true); - break; - case 5: - addSwell(); - delay = scroll_time - 100; - break; - } - - // Tween playfield height - switch (step) - { - default: - playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); - break; - case 6: - playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); - break; - } - } - - private void addHitJudgement(bool kiai) - { - HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; - - var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint - { - KiaiMode = kiai - }); - - Hit hit = new Hit(); - hit.ApplyDefaults(cpi, new BeatmapDifficulty()); - - var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - - ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoJudgement { Result = hitResult }); - - if (RNG.Next(10) == 0) - { - ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoJudgement { Result = hitResult }); - ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoStrongHitJudgement()); - } - } - - private void addMissJudgement() - { - ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(new DrawableTestHit(new Hit()), new TaikoJudgement { Result = HitResult.Miss }); - } - - private void addBarLine(bool major, double delay = scroll_time) - { - BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay }; - - rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); - } - - private void addSwell(double duration = default_duration) - { - var swell = new Swell - { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, - Duration = duration, - }; - - swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - rulesetContainer.Playfield.Add(new DrawableSwell(swell)); - } - - private void addDrumRoll(bool strong, double duration = default_duration) - { - addBarLine(true); - addBarLine(true, scroll_time + duration); - - var d = new DrumRoll - { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, - IsStrong = strong, - Duration = duration, - }; - - d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); - } - - private void addCentreHit(bool strong) - { - Hit h = new Hit - { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, - IsStrong = strong - }; - - h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - if (strong) - rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h)); - else - rulesetContainer.Playfield.Add(new DrawableCentreHit(h)); - } - - private void addRimHit(bool strong) - { - Hit h = new Hit - { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, - IsStrong = strong - }; - - h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - if (strong) - rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h)); - else - rulesetContainer.Playfield.Add(new DrawableRimHit(h)); - } - - private class DrawableTestHit : DrawableHitObject - { - public DrawableTestHit(TaikoHitObject hitObject) - : base(hitObject) - { - } - - protected override void UpdateState(ArmedState state) - { - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; +using OpenTK; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestCaseTaikoPlayfield : OsuTestCase + { + private const double default_duration = 1000; + private const float scroll_time = 1000; + + protected override double TimePerAction => default_duration * 2; + + private readonly Random rng = new Random(1337); + private TaikoRulesetContainer rulesetContainer; + private Container playfieldContainer; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + AddStep("Hit!", () => addHitJudgement(false)); + AddStep("Kiai hit", () => addHitJudgement(true)); + AddStep("Miss :(", addMissJudgement); + AddStep("DrumRoll", () => addDrumRoll(false)); + AddStep("Strong DrumRoll", () => addDrumRoll(true)); + AddStep("Swell", () => addSwell()); + AddStep("Centre", () => addCentreHit(false)); + AddStep("Strong Centre", () => addCentreHit(true)); + AddStep("Rim", () => addRimHit(false)); + AddStep("Strong Rim", () => addRimHit(true)); + AddStep("Add bar line", () => addBarLine(false)); + AddStep("Add major bar line", () => addBarLine(true)); + AddStep("Height test 1", () => changePlayfieldSize(1)); + AddStep("Height test 2", () => changePlayfieldSize(2)); + AddStep("Height test 3", () => changePlayfieldSize(3)); + AddStep("Height test 4", () => changePlayfieldSize(4)); + AddStep("Height test 5", () => changePlayfieldSize(5)); + AddStep("Reset height", () => changePlayfieldSize(6)); + + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + + WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap + { + HitObjects = new List { new CentreHit() }, + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + Metadata = new BeatmapMetadata + { + Artist = @"Unknown", + Title = @"Sample Beatmap", + AuthorString = @"peppy", + }, + }, + ControlPointInfo = controlPointInfo + }); + + var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; + + Add(playfieldContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 768, + Clock = new FramedClock(rateAdjustClock), + Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap, true) } + }); + } + + private void changePlayfieldSize(int step) + { + double delay = 0; + + // Add new hits + switch (step) + { + case 1: + addCentreHit(false); + break; + case 2: + addCentreHit(true); + break; + case 3: + addDrumRoll(false); + break; + case 4: + addDrumRoll(true); + break; + case 5: + addSwell(); + delay = scroll_time - 100; + break; + } + + // Tween playfield height + switch (step) + { + default: + playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); + break; + case 6: + playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); + break; + } + } + + private void addHitJudgement(bool kiai) + { + HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; + + var cpi = new ControlPointInfo(); + cpi.EffectPoints.Add(new EffectControlPoint + { + KiaiMode = kiai + }); + + Hit hit = new Hit(); + hit.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; + + ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoJudgement { Result = hitResult }); + + if (RNG.Next(10) == 0) + { + ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoJudgement { Result = hitResult }); + ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoStrongHitJudgement()); + } + } + + private void addMissJudgement() + { + ((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(new DrawableTestHit(new Hit()), new TaikoJudgement { Result = HitResult.Miss }); + } + + private void addBarLine(bool major, double delay = scroll_time) + { + BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay }; + + rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + } + + private void addSwell(double duration = default_duration) + { + var swell = new Swell + { + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + Duration = duration, + }; + + swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + rulesetContainer.Playfield.Add(new DrawableSwell(swell)); + } + + private void addDrumRoll(bool strong, double duration = default_duration) + { + addBarLine(true); + addBarLine(true, scroll_time + duration); + + var d = new DrumRoll + { + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + IsStrong = strong, + Duration = duration, + }; + + d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); + } + + private void addCentreHit(bool strong) + { + Hit h = new Hit + { + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + IsStrong = strong + }; + + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + if (strong) + rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h)); + else + rulesetContainer.Playfield.Add(new DrawableCentreHit(h)); + } + + private void addRimHit(bool strong) + { + Hit h = new Hit + { + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + IsStrong = strong + }; + + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + if (strong) + rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h)); + else + rulesetContainer.Playfield.Add(new DrawableRimHit(h)); + } + + private class DrawableTestHit : DrawableHitObject + { + public DrawableTestHit(TaikoHitObject hitObject) + : base(hitObject) + { + } + + protected override void UpdateState(ArmedState state) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index df73fa61cb..ccd69c574d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -1,10 +1,10 @@ - - - - WinExe - netcoreapp2.0;net461 - - - - + + + + WinExe + netcoreapp2.0;net461 + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs index afa3d162f4..91aafdae76 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -1,61 +1,61 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Taiko.Audio -{ - public class DrumSampleMapping - { - private readonly ControlPointInfo controlPoints; - private readonly Dictionary mappings = new Dictionary(); - - public readonly List Sounds = new List(); - - public DrumSampleMapping(ControlPointInfo controlPoints) - { - this.controlPoints = controlPoints; - - IEnumerable samplePoints; - if (controlPoints.SamplePoints.Count == 0) - // Get the default sample point - samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; - else - samplePoints = controlPoints.SamplePoints; - - foreach (var s in samplePoints) - { - var centre = s.GetSampleInfo(); - var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP); - - // todo: this is ugly - centre.Namespace = "taiko"; - rim.Namespace = "taiko"; - - mappings[s.Time] = new DrumSample - { - Centre = addSound(centre), - Rim = addSound(rim) - }; - } - } - - private SkinnableSound addSound(SampleInfo sampleInfo) - { - var drawable = new SkinnableSound(sampleInfo); - Sounds.Add(drawable); - return drawable; - } - - public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; - - public class DrumSample - { - public SkinnableSound Centre; - public SkinnableSound Rim; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Taiko.Audio +{ + public class DrumSampleMapping + { + private readonly ControlPointInfo controlPoints; + private readonly Dictionary mappings = new Dictionary(); + + public readonly List Sounds = new List(); + + public DrumSampleMapping(ControlPointInfo controlPoints) + { + this.controlPoints = controlPoints; + + IEnumerable samplePoints; + if (controlPoints.SamplePoints.Count == 0) + // Get the default sample point + samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; + else + samplePoints = controlPoints.SamplePoints; + + foreach (var s in samplePoints) + { + var centre = s.GetSampleInfo(); + var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP); + + // todo: this is ugly + centre.Namespace = "taiko"; + rim.Namespace = "taiko"; + + mappings[s.Time] = new DrumSample + { + Centre = addSound(centre), + Rim = addSound(rim) + }; + } + } + + private SkinnableSound addSound(SampleInfo sampleInfo) + { + var drawable = new SkinnableSound(sampleInfo); + Sounds.Add(drawable); + return drawable; + } + + public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; + + public class DrumSample + { + public SkinnableSound Centre; + public SkinnableSound Rim; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9d6b5b5ce4..2f175a9922 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -1,200 +1,200 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Taiko.Objects; -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.IO.Serialization; -using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Taiko.Beatmaps -{ - internal class TaikoBeatmapConverter : BeatmapConverter - { - /// - /// osu! is generally slower than taiko, so a factor is added to increase - /// speed. This must be used everywhere slider length or beat length is used. - /// - private const float legacy_velocity_multiplier = 1.4f; - - /// - /// Because swells are easier in taiko than spinners are in osu!, - /// legacy taiko multiplies a factor when converting the number of required hits. - /// - private const float swell_hit_multiplier = 1.65f; - - /// - /// Base osu! slider scoring distance. - /// - private const float osu_base_scoring_distance = 100; - - /// - /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. - /// - private const float taiko_base_distance = 100; - - private readonly bool isForCurrentRuleset; - - protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) }; - - public TaikoBeatmapConverter(bool isForCurrentRuleset) - { - this.isForCurrentRuleset = isForCurrentRuleset; - } - - protected override Beatmap ConvertBeatmap(Beatmap original) - { - // Rewrite the beatmap info to add the slider velocity multiplier - BeatmapInfo info = original.BeatmapInfo.DeepClone(); - info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier; - - Beatmap converted = base.ConvertBeatmap(original); - - if (original.BeatmapInfo.RulesetID == 3) - { - // Post processing step to transform mania hit objects with the same start time into strong hits - converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => - { - TaikoHitObject first = x.First(); - if (x.Skip(1).Any()) - first.IsStrong = true; - return first; - }).ToList(); - } - - return converted; - } - - protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap) - { - var distanceData = obj as IHasDistance; - var repeatsData = obj as IHasRepeats; - var endTimeData = obj as IHasEndTime; - var curveData = obj as IHasCurve; - - // Old osu! used hit sounding to determine various hit type information - List samples = obj.Samples; - - bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); - - if (distanceData != null) - { - // Number of spans of the object - one for the initial length and for each repeat - int spans = repeatsData?.SpanCount() ?? 1; - - TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); - - double speedAdjustment = difficultyPoint.SpeedMultiplier; - double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; - - // The true distance, accounting for any repeats. This ends up being the drum roll distance later - double distance = distanceData.Distance * spans * legacy_velocity_multiplier; - - // The velocity of the taiko hit object - calculated as the velocity of a drum roll - double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; - // The duration of the taiko hit object - double taikoDuration = distance / taikoVelocity; - - // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; - // The duration of the osu! hit object - double osuDuration = distance / osuVelocity; - - // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but - // only uses it for tick rate if beatmap version < 8 - if (beatmap.BeatmapInfo.BeatmapVersion >= 8) - speedAdjustedBeatLength *= speedAdjustment; - - // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat - double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans); - - if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) - { - List> allSamples = curveData != null ? curveData.RepeatSamples : new List>(new[] { samples }); - - int i = 0; - for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) - { - List currentSamples = allSamples[i]; - bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); - strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); - - if (isRim) - { - yield return new RimHit - { - StartTime = j, - Samples = currentSamples, - IsStrong = strong - }; - } - else - { - yield return new CentreHit - { - StartTime = j, - Samples = currentSamples, - IsStrong = strong, - }; - } - - i = (i + 1) % allSamples.Count; - } - } - else - { - yield return new DrumRoll - { - StartTime = obj.StartTime, - Samples = obj.Samples, - IsStrong = strong, - Duration = taikoDuration, - TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4, - }; - } - } - else if (endTimeData != null) - { - double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; - - yield return new Swell - { - StartTime = obj.StartTime, - Samples = obj.Samples, - IsStrong = strong, - Duration = endTimeData.Duration, - RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier), - }; - } - else - { - bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); - - if (isRim) - { - yield return new RimHit - { - StartTime = obj.StartTime, - Samples = obj.Samples, - IsStrong = strong, - }; - } - else - { - yield return new CentreHit - { - StartTime = obj.StartTime, - Samples = obj.Samples, - IsStrong = strong, - }; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Taiko.Objects; +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.IO.Serialization; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Taiko.Beatmaps +{ + internal class TaikoBeatmapConverter : BeatmapConverter + { + /// + /// osu! is generally slower than taiko, so a factor is added to increase + /// speed. This must be used everywhere slider length or beat length is used. + /// + private const float legacy_velocity_multiplier = 1.4f; + + /// + /// Because swells are easier in taiko than spinners are in osu!, + /// legacy taiko multiplies a factor when converting the number of required hits. + /// + private const float swell_hit_multiplier = 1.65f; + + /// + /// Base osu! slider scoring distance. + /// + private const float osu_base_scoring_distance = 100; + + /// + /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. + /// + private const float taiko_base_distance = 100; + + private readonly bool isForCurrentRuleset; + + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) }; + + public TaikoBeatmapConverter(bool isForCurrentRuleset) + { + this.isForCurrentRuleset = isForCurrentRuleset; + } + + protected override Beatmap ConvertBeatmap(Beatmap original) + { + // Rewrite the beatmap info to add the slider velocity multiplier + BeatmapInfo info = original.BeatmapInfo.DeepClone(); + info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier; + + Beatmap converted = base.ConvertBeatmap(original); + + if (original.BeatmapInfo.RulesetID == 3) + { + // Post processing step to transform mania hit objects with the same start time into strong hits + converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => + { + TaikoHitObject first = x.First(); + if (x.Skip(1).Any()) + first.IsStrong = true; + return first; + }).ToList(); + } + + return converted; + } + + protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap) + { + var distanceData = obj as IHasDistance; + var repeatsData = obj as IHasRepeats; + var endTimeData = obj as IHasEndTime; + var curveData = obj as IHasCurve; + + // Old osu! used hit sounding to determine various hit type information + List samples = obj.Samples; + + bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); + + if (distanceData != null) + { + // Number of spans of the object - one for the initial length and for each repeat + int spans = repeatsData?.SpanCount() ?? 1; + + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); + + double speedAdjustment = difficultyPoint.SpeedMultiplier; + double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; + + // The true distance, accounting for any repeats. This ends up being the drum roll distance later + double distance = distanceData.Distance * spans * legacy_velocity_multiplier; + + // The velocity of the taiko hit object - calculated as the velocity of a drum roll + double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + // The duration of the taiko hit object + double taikoDuration = distance / taikoVelocity; + + // The velocity of the osu! hit object - calculated as the velocity of a slider + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + // The duration of the osu! hit object + double osuDuration = distance / osuVelocity; + + // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but + // only uses it for tick rate if beatmap version < 8 + if (beatmap.BeatmapInfo.BeatmapVersion >= 8) + speedAdjustedBeatLength *= speedAdjustment; + + // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans); + + if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) + { + List> allSamples = curveData != null ? curveData.RepeatSamples : new List>(new[] { samples }); + + int i = 0; + for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) + { + List currentSamples = allSamples[i]; + bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); + + if (isRim) + { + yield return new RimHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong + }; + } + else + { + yield return new CentreHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong, + }; + } + + i = (i + 1) % allSamples.Count; + } + } + else + { + yield return new DrumRoll + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + Duration = taikoDuration, + TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4, + }; + } + } + else if (endTimeData != null) + { + double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + + yield return new Swell + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + Duration = endTimeData.Duration, + RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier), + }; + } + else + { + bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + + if (isRim) + { + yield return new RimHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + }; + } + else + { + yield return new CentreHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + }; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index a713b8ba8c..446dd0d11b 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Judgements -{ - public class TaikoDrumRollTickJudgement : TaikoJudgement - { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - case HitResult.Great: - return 200; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Judgements +{ + public class TaikoDrumRollTickJudgement : TaikoJudgement + { + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Great: + return 200; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index f3dc45d78e..9b1f7a08b5 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Judgements -{ - public class TaikoJudgement : Judgement - { - public override HitResult MaxResult => HitResult.Great; - - /// - /// Computes the numeric result value for the combo portion of the score. - /// - /// The result to compute the value for. - /// The numeric result value. - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - case HitResult.Good: - return 100; - case HitResult.Great: - return 300; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Judgements +{ + public class TaikoJudgement : Judgement + { + public override HitResult MaxResult => HitResult.Great; + + /// + /// Computes the numeric result value for the combo portion of the score. + /// + /// The result to compute the value for. + /// The numeric result value. + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Good: + return 100; + case HitResult.Great: + return 300; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs index c477851335..288ad236aa 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Taiko.Judgements -{ - public class TaikoStrongHitJudgement : TaikoJudgement - { - public override bool AffectsCombo => false; - - public TaikoStrongHitJudgement() - { - Final = true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Taiko.Judgements +{ + public class TaikoStrongHitJudgement : TaikoJudgement + { + public override bool AffectsCombo => false; + + public TaikoStrongHitJudgement() + { + Final = true; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 239f0d5a6b..c63bf10542 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.Replays; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModAutoplay : ModAutoplay - { - protected override Score CreateReplayScore(Beatmap beatmap) - { - return new Score - { - User = new User { Username = "mekkadosu!" }, - Replay = new TaikoAutoGenerator(beatmap).Generate(), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModAutoplay : ModAutoplay + { + protected override Score CreateReplayScore(Beatmap beatmap) + { + return new Score + { + User = new User { Username = "mekkadosu!" }, + Replay = new TaikoAutoGenerator(beatmap).Generate(), + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs index 703e6b4f1c..80d8da6cf6 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModDaycore : ModDaycore - { - public override double ScoreMultiplier => 0.3; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModDaycore : ModDaycore + { + public override double ScoreMultiplier => 0.3; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs index 2ae52b4549..21e48f2c8c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModDoubleTime : ModDoubleTime - { - public override double ScoreMultiplier => 1.12; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1.12; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index be6510459e..16364964f8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModEasy : ModEasy - { - public override string Description => @"Beats move slower, less accuracy required, and three lives!"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModEasy : ModEasy + { + public override string Description => @"Beats move slower, less accuracy required, and three lives!"; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index a2c6d7f9e0..49f7786f59 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModFlashlight : ModFlashlight - { - public override double ScoreMultiplier => 1.12; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModFlashlight : ModFlashlight + { + public override double ScoreMultiplier => 1.12; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs index 6542b5a844..ef609b893d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModHalfTime : ModHalfTime - { - public override double ScoreMultiplier => 0.3; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModHalfTime : ModHalfTime + { + public override double ScoreMultiplier => 0.3; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index ba304c41d8..908778d4d6 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModHardRock : ModHardRock - { - public override double ScoreMultiplier => 1.06; - public override bool Ranked => true; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1.06; + public override bool Ranked => true; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index be987a1773..f4e12ede21 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModHidden : ModHidden - { - public override string Description => @"Beats fade out before you hit them!"; - public override double ScoreMultiplier => 1.06; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModHidden : ModHidden + { + public override string Description => @"Beats fade out before you hit them!"; + public override double ScoreMultiplier => 1.06; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs index 0504b7c5b6..abe4b2e25e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModNightcore : ModNightcore - { - public override double ScoreMultiplier => 1.12; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModNightcore : ModNightcore + { + public override double ScoreMultiplier => 1.12; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs index 3e10f58bbd..5a8e903fc6 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModNoFail : ModNoFail - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModNoFail : ModNoFail + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs index 7388283d41..027feed112 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModPerfect : ModPerfect - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModPerfect : ModPerfect + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs index d5ad04f595..fd72291f1f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModRelax : ModRelax - { - public override string Description => @"No ninja-like spinners, demanding drumrolls or unexpected katu's."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModRelax : ModRelax + { + public override string Description => @"No ninja-like spinners, demanding drumrolls or unexpected katu's."; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs index 129d181616..6ed1ad3bda 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Taiko.Mods -{ - public class TaikoModSuddenDeath : ModSuddenDeath - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModSuddenDeath : ModSuddenDeath + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index efd7d57ed7..f7496642dd 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class BarLine : TaikoHitObject - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class BarLine : TaikoHitObject + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs index ff7c36a8ad..898c562ea9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class CentreHit : Hit - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class CentreHit : Hit + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index d3a38289a8..db5f418e18 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using OpenTK; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - /// - /// A line that scrolls alongside hit objects in the playfield and visualises control points. - /// - public class DrawableBarLine : DrawableHitObject - { - /// - /// The width of the line tracker. - /// - private const float tracker_width = 2f; - - /// - /// Fade out time calibrated to a pre-empt of 1000ms. - /// - private const float base_fadeout_time = 100f; - - /// - /// The visual line tracker. - /// - protected Box Tracker; - - /// - /// The bar line. - /// - protected readonly BarLine BarLine; - - public DrawableBarLine(BarLine barLine) - : base(barLine) - { - BarLine = barLine; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Y; - Width = tracker_width; - - InternalChildren = new[] - { - Tracker = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(0.5f, 0), - Alpha = 0.75f - } - }; - } - - protected override void UpdateState(ArmedState state) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using OpenTK; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + /// + /// A line that scrolls alongside hit objects in the playfield and visualises control points. + /// + public class DrawableBarLine : DrawableHitObject + { + /// + /// The width of the line tracker. + /// + private const float tracker_width = 2f; + + /// + /// Fade out time calibrated to a pre-empt of 1000ms. + /// + private const float base_fadeout_time = 100f; + + /// + /// The visual line tracker. + /// + protected Box Tracker; + + /// + /// The bar line. + /// + protected readonly BarLine BarLine; + + public DrawableBarLine(BarLine barLine) + : base(barLine) + { + BarLine = barLine; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Y; + Width = tracker_width; + + InternalChildren = new[] + { + Tracker = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(0.5f, 0), + Alpha = 0.75f + } + }; + } + + protected override void UpdateState(ArmedState state) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index 19a6e4eac2..300edd2e76 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -1,67 +1,67 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableBarLineMajor : DrawableBarLine - { - /// - /// The vertical offset of the triangles from the line tracker. - /// - private const float triangle_offfset = 10f; - - /// - /// The size of the triangles. - /// - private const float triangle_size = 20f; - - private readonly Container triangleContainer; - - public DrawableBarLineMajor(BarLine barLine) - : base(barLine) - { - AddInternal(triangleContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Children = new[] - { - new EquilateralTriangle - { - Name = "Top", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Position = new Vector2(0, -triangle_offfset), - Size = new Vector2(-triangle_size), - EdgeSmoothness = new Vector2(1), - }, - new EquilateralTriangle - { - Name = "Bottom", - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre, - Position = new Vector2(0, triangle_offfset), - Size = new Vector2(triangle_size), - EdgeSmoothness = new Vector2(1), - } - } - }); - - Tracker.Alpha = 1f; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime)) - triangleContainer.FadeOut(150); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableBarLineMajor : DrawableBarLine + { + /// + /// The vertical offset of the triangles from the line tracker. + /// + private const float triangle_offfset = 10f; + + /// + /// The size of the triangles. + /// + private const float triangle_size = 20f; + + private readonly Container triangleContainer; + + public DrawableBarLineMajor(BarLine barLine) + : base(barLine) + { + AddInternal(triangleContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new EquilateralTriangle + { + Name = "Top", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, -triangle_offfset), + Size = new Vector2(-triangle_size), + EdgeSmoothness = new Vector2(1), + }, + new EquilateralTriangle + { + Name = "Bottom", + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, triangle_offfset), + Size = new Vector2(triangle_size), + EdgeSmoothness = new Vector2(1), + } + } + }); + + Tracker.Alpha = 1f; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime)) + triangleContainer.FadeOut(150); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 56a0976ddb..dda96c2caf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableCentreHit : DrawableHit - { - protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; - - public DrawableCentreHit(Hit hit) - : base(hit) - { - MainPiece.Add(new CentreHitSymbolPiece()); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colours.PinkDarker; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableCentreHit : DrawableHit + { + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; + + public DrawableCentreHit(Hit hit) + : base(hit) + { + MainPiece.Add(new CentreHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.PinkDarker; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs index c0bfaba5f5..a2dabf2b18 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableCentreHitStrong : DrawableHitStrong - { - protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; - - public DrawableCentreHitStrong(Hit hit) - : base(hit) - { - MainPiece.Add(new CentreHitSymbolPiece()); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colours.PinkDarker; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableCentreHitStrong : DrawableHitStrong + { + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; + + public DrawableCentreHitStrong(Hit hit) + : base(hit) + { + MainPiece.Add(new CentreHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.PinkDarker; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2bb2d478c3..00eac4adca 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -1,107 +1,107 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.MathUtils; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Judgements; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableDrumRoll : DrawableTaikoHitObject - { - /// - /// Number of rolling hits required to reach the dark/final colour. - /// - private const int rolling_hits_for_engaged_colour = 5; - - /// - /// Rolling number of tick hits. This increases for hits and decreases for misses. - /// - private int rollingHits; - - public DrawableDrumRoll(DrumRoll drumRoll) - : base(drumRoll) - { - RelativeSizeAxes = Axes.Y; - - Container tickContainer; - MainPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); - - foreach (var tick in drumRoll.NestedHitObjects.OfType()) - { - var newTick = new DrawableDrumRollTick(tick); - newTick.OnJudgement += onTickJudgement; - - AddNested(newTick); - tickContainer.Add(newTick); - } - } - - protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); - - public override bool OnPressed(TaikoAction action) => false; - - private Color4 colourIdle; - private Color4 colourEngaged; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colourIdle = colours.YellowDark; - colourEngaged = colours.YellowDarker; - } - - private void onTickJudgement(DrawableHitObject obj, Judgement judgement) - { - if (judgement.Result > HitResult.Miss) - rollingHits++; - else - rollingHits--; - - rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); - - Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); - MainPiece.FadeAccent(newColour, 100); - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (userTriggered) - return; - - if (timeOffset < 0) - return; - - int countHit = NestedHitObjects.Count(o => o.IsHit); - if (countHit >= HitObject.RequiredGoodHits) - { - AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good }); - if (HitObject.IsStrong) - AddJudgement(new TaikoStrongHitJudgement()); - } - else - AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); - } - - protected override void UpdateState(ArmedState state) - { - switch (state) - { - case ArmedState.Hit: - case ArmedState.Miss: - this.FadeOut(100).Expire(); - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableDrumRoll : DrawableTaikoHitObject + { + /// + /// Number of rolling hits required to reach the dark/final colour. + /// + private const int rolling_hits_for_engaged_colour = 5; + + /// + /// Rolling number of tick hits. This increases for hits and decreases for misses. + /// + private int rollingHits; + + public DrawableDrumRoll(DrumRoll drumRoll) + : base(drumRoll) + { + RelativeSizeAxes = Axes.Y; + + Container tickContainer; + MainPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); + + foreach (var tick in drumRoll.NestedHitObjects.OfType()) + { + var newTick = new DrawableDrumRollTick(tick); + newTick.OnJudgement += onTickJudgement; + + AddNested(newTick); + tickContainer.Add(newTick); + } + } + + protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); + + public override bool OnPressed(TaikoAction action) => false; + + private Color4 colourIdle; + private Color4 colourEngaged; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colourIdle = colours.YellowDark; + colourEngaged = colours.YellowDarker; + } + + private void onTickJudgement(DrawableHitObject obj, Judgement judgement) + { + if (judgement.Result > HitResult.Miss) + rollingHits++; + else + rollingHits--; + + rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour); + + Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1); + MainPiece.FadeAccent(newColour, 100); + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (userTriggered) + return; + + if (timeOffset < 0) + return; + + int countHit = NestedHitObjects.Count(o => o.IsHit); + if (countHit >= HitObject.RequiredGoodHits) + { + AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good }); + if (HitObject.IsStrong) + AddJudgement(new TaikoStrongHitJudgement()); + } + else + AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); + } + + protected override void UpdateState(ArmedState state) + { + switch (state) + { + case ArmedState.Hit: + case ArmedState.Miss: + this.FadeOut(100).Expire(); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 65a4e7bd95..7a57cf77b4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -1,53 +1,53 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableDrumRollTick : DrawableTaikoHitObject - { - public DrawableDrumRollTick(DrumRollTick tick) - : base(tick) - { - FillMode = FillMode.Fit; - } - - public override bool DisplayJudgement => false; - - protected override TaikoPiece CreateMainPiece() => new TickPiece - { - Filled = HitObject.FirstTick - }; - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered) - return; - - if (!(Math.Abs(timeOffset) < HitObject.HitWindow)) - return; - - AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); - if (HitObject.IsStrong) - AddJudgement(new TaikoStrongHitJudgement()); - } - - protected override void UpdateState(ArmedState state) - { - switch (state) - { - case ArmedState.Hit: - this.ScaleTo(0, 100, Easing.OutQuint).Expire(); - break; - } - } - - public override bool OnPressed(TaikoAction action) => UpdateJudgement(true); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableDrumRollTick : DrawableTaikoHitObject + { + public DrawableDrumRollTick(DrumRollTick tick) + : base(tick) + { + FillMode = FillMode.Fit; + } + + public override bool DisplayJudgement => false; + + protected override TaikoPiece CreateMainPiece() => new TickPiece + { + Filled = HitObject.FirstTick + }; + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered) + return; + + if (!(Math.Abs(timeOffset) < HitObject.HitWindow)) + return; + + AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); + if (HitObject.IsStrong) + AddJudgement(new TaikoStrongHitJudgement()); + } + + protected override void UpdateState(ArmedState state) + { + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(0, 100, Easing.OutQuint).Expire(); + break; + } + } + + public override bool OnPressed(TaikoAction action) => UpdateJudgement(true); + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 75e1e2a247..d923b2dcdf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -1,120 +1,120 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public abstract class DrawableHit : DrawableTaikoHitObject - { - /// - /// A list of keys which can result in hits for this HitObject. - /// - protected abstract TaikoAction[] HitActions { get; } - - /// - /// Whether a second hit is allowed to be processed. This occurs once this hit object has been hit successfully. - /// - protected bool SecondHitAllowed { get; private set; } - - /// - /// Whether the last key pressed is a valid hit key. - /// - private bool validKeyPressed; - - protected DrawableHit(Hit hit) - : base(hit) - { - FillMode = FillMode.Fit; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!userTriggered) - { - if (!HitObject.HitWindows.CanBeHit(timeOffset)) - AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); - return; - } - - var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) - return; - - if (!validKeyPressed || result == HitResult.Miss) - AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); - else - { - AddJudgement(new TaikoJudgement - { - Result = result, - Final = !HitObject.IsStrong - }); - - SecondHitAllowed = true; - } - } - - public override bool OnPressed(TaikoAction action) - { - validKeyPressed = HitActions.Contains(action); - - // Only count this as handled if the new judgement is a hit - return UpdateJudgement(true); - } - - protected override void Update() - { - base.Update(); - - Size = BaseSize * Parent.RelativeChildSize; - } - - protected override void UpdateState(ArmedState state) - { - var circlePiece = MainPiece as CirclePiece; - circlePiece?.FlashBox.FinishTransforms(); - - var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; - using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) - { - switch (State.Value) - { - case ArmedState.Idle: - this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); - break; - case ArmedState.Miss: - this.FadeOut(100) - .Expire(); - break; - case ArmedState.Hit: - var flash = circlePiece?.FlashBox; - if (flash != null) - { - flash.FadeTo(0.9f); - flash.FadeOut(300); - } - - const float gravity_time = 300; - const float gravity_travel_height = 200; - - this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad); - - this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out) - .Then() - .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In); - - this.FadeOut(800) - .Expire(); - - break; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public abstract class DrawableHit : DrawableTaikoHitObject + { + /// + /// A list of keys which can result in hits for this HitObject. + /// + protected abstract TaikoAction[] HitActions { get; } + + /// + /// Whether a second hit is allowed to be processed. This occurs once this hit object has been hit successfully. + /// + protected bool SecondHitAllowed { get; private set; } + + /// + /// Whether the last key pressed is a valid hit key. + /// + private bool validKeyPressed; + + protected DrawableHit(Hit hit) + : base(hit) + { + FillMode = FillMode.Fit; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered) + { + if (!HitObject.HitWindows.CanBeHit(timeOffset)) + AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); + return; + } + + var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) + return; + + if (!validKeyPressed || result == HitResult.Miss) + AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); + else + { + AddJudgement(new TaikoJudgement + { + Result = result, + Final = !HitObject.IsStrong + }); + + SecondHitAllowed = true; + } + } + + public override bool OnPressed(TaikoAction action) + { + validKeyPressed = HitActions.Contains(action); + + // Only count this as handled if the new judgement is a hit + return UpdateJudgement(true); + } + + protected override void Update() + { + base.Update(); + + Size = BaseSize * Parent.RelativeChildSize; + } + + protected override void UpdateState(ArmedState state) + { + var circlePiece = MainPiece as CirclePiece; + circlePiece?.FlashBox.FinishTransforms(); + + var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; + using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) + { + switch (State.Value) + { + case ArmedState.Idle: + this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); + break; + case ArmedState.Miss: + this.FadeOut(100) + .Expire(); + break; + case ArmedState.Hit: + var flash = circlePiece?.FlashBox; + if (flash != null) + { + flash.FadeTo(0.9f); + flash.FadeOut(300); + } + + const float gravity_time = 300; + const float gravity_travel_height = 200; + + this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad); + + this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out) + .Then() + .MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In); + + this.FadeOut(800) + .Expire(); + + break; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs index cf6236e872..c416d50062 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -1,89 +1,89 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public abstract class DrawableHitStrong : DrawableHit - { - /// - /// The lenience for the second key press. - /// This does not adjust by map difficulty in ScoreV2 yet. - /// - private const double second_hit_window = 30; - - private double firstHitTime; - private bool firstKeyHeld; - private TaikoAction firstHitAction; - - protected DrawableHitStrong(Hit hit) - : base(hit) - { - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (!SecondHitAllowed) - { - base.CheckForJudgements(userTriggered, timeOffset); - return; - } - - if (!userTriggered) - { - if (timeOffset > second_hit_window) - AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.None }); - return; - } - - // If we get here, we're assured that the key pressed is the correct secondary key - - if (Math.Abs(firstHitTime - Time.Current) < second_hit_window) - AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great }); - } - - public override bool OnReleased(TaikoAction action) - { - if (action == firstHitAction) - firstKeyHeld = false; - return base.OnReleased(action); - } - - public override bool OnPressed(TaikoAction action) - { - if (AllJudged) - return false; - - // Check if we've handled the first key - if (!SecondHitAllowed) - { - // First key hasn't been handled yet, attempt to handle it - bool handled = base.OnPressed(action); - - if (handled) - { - firstHitTime = Time.Current; - firstHitAction = action; - firstKeyHeld = true; - } - - return handled; - } - - // Don't handle represses of the first key - if (firstHitAction == action) - return false; - - // Don't handle invalid hit action presses - if (!HitActions.Contains(action)) - return false; - - // Assume the intention was to hit the strong hit with both keys only if the first key is still being held down - return firstKeyHeld && UpdateJudgement(true); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public abstract class DrawableHitStrong : DrawableHit + { + /// + /// The lenience for the second key press. + /// This does not adjust by map difficulty in ScoreV2 yet. + /// + private const double second_hit_window = 30; + + private double firstHitTime; + private bool firstKeyHeld; + private TaikoAction firstHitAction; + + protected DrawableHitStrong(Hit hit) + : base(hit) + { + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!SecondHitAllowed) + { + base.CheckForJudgements(userTriggered, timeOffset); + return; + } + + if (!userTriggered) + { + if (timeOffset > second_hit_window) + AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.None }); + return; + } + + // If we get here, we're assured that the key pressed is the correct secondary key + + if (Math.Abs(firstHitTime - Time.Current) < second_hit_window) + AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great }); + } + + public override bool OnReleased(TaikoAction action) + { + if (action == firstHitAction) + firstKeyHeld = false; + return base.OnReleased(action); + } + + public override bool OnPressed(TaikoAction action) + { + if (AllJudged) + return false; + + // Check if we've handled the first key + if (!SecondHitAllowed) + { + // First key hasn't been handled yet, attempt to handle it + bool handled = base.OnPressed(action); + + if (handled) + { + firstHitTime = Time.Current; + firstHitAction = action; + firstKeyHeld = true; + } + + return handled; + } + + // Don't handle represses of the first key + if (firstHitAction == action) + return false; + + // Don't handle invalid hit action presses + if (!HitActions.Contains(action)) + return false; + + // Assume the intention was to hit the strong hit with both keys only if the first key is still being held down + return firstKeyHeld && UpdateJudgement(true); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index e8492ac168..f2194c6d56 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableRimHit : DrawableHit - { - protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; - - public DrawableRimHit(Hit hit) - : base(hit) - { - MainPiece.Add(new RimHitSymbolPiece()); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colours.BlueDarker; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableRimHit : DrawableHit + { + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; + + public DrawableRimHit(Hit hit) + : base(hit) + { + MainPiece.Add(new RimHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.BlueDarker; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs index b5e232a65d..728fe416f7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableRimHitStrong : DrawableHitStrong - { - protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; - - public DrawableRimHitStrong(Hit hit) - : base(hit) - { - MainPiece.Add(new RimHitSymbolPiece()); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colours.BlueDarker; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableRimHitStrong : DrawableHitStrong + { + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; + + public DrawableRimHitStrong(Hit hit) + : base(hit) + { + MainPiece.Add(new RimHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.BlueDarker; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 37f1300d47..33cc29bccf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -1,225 +1,225 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Taiko.Judgements; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableSwell : DrawableTaikoHitObject - { - /// - /// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime. - /// This is only ever invoked once. - /// - public event Action OnStart; - - private const float target_ring_thick_border = 1.4f; - private const float target_ring_thin_border = 1f; - private const float target_ring_scale = 5f; - private const float inner_ring_alpha = 0.65f; - - private readonly Container bodyContainer; - private readonly CircularContainer targetRing; - private readonly CircularContainer expandingRing; - - /// - /// The amount of times the user has hit this swell. - /// - private int userHits; - - private bool hasStarted; - private readonly SwellSymbolPiece symbol; - - public DrawableSwell(Swell swell) - : base(swell) - { - FillMode = FillMode.Fit; - - AddInternal(bodyContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - Children = new Drawable[] - { - expandingRing = new CircularContainer - { - Name = "Expanding ring", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0, - RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = inner_ring_alpha, - } - } - }, - targetRing = new CircularContainer - { - Name = "Target ring (thick border)", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = target_ring_thick_border, - Blending = BlendingMode.Additive, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - new CircularContainer - { - Name = "Target ring (thin border)", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = target_ring_thin_border, - BorderColour = Color4.White, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - } - } - } - } - }); - - MainPiece.Add(symbol = new SwellSymbolPiece()); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - MainPiece.AccentColour = colours.YellowDark; - expandingRing.Colour = colours.YellowLight; - targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize - Width *= Parent.RelativeChildSize.X; - } - - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (userTriggered) - { - userHits++; - - var completion = (float)userHits / HitObject.RequiredHits; - - expandingRing - .FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50) - .Then() - .FadeTo(completion / 8, 2000, Easing.OutQuint); - - symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); - - expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); - - if (userHits == HitObject.RequiredHits) - AddJudgement(new TaikoJudgement { Result = HitResult.Great }); - } - else - { - if (timeOffset < 0) - return; - - //TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP - AddJudgement(userHits > HitObject.RequiredHits / 2 - ? new TaikoJudgement { Result = HitResult.Good } - : new TaikoJudgement { Result = HitResult.Miss }); - } - } - - protected override void UpdateState(ArmedState state) - { - const float preempt = 100; - const float out_transition_time = 300; - - double untilStartTime = HitObject.StartTime - Time.Current; - double untilJudgement = untilStartTime + (Judgements.FirstOrDefault()?.TimeOffset ?? 0) + HitObject.Duration; - - targetRing.Delay(untilStartTime - preempt).ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint); - this.Delay(untilJudgement).FadeOut(out_transition_time, Easing.Out); - - switch (state) - { - case ArmedState.Hit: - bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time); - break; - } - - Expire(); - } - - protected override void Update() - { - base.Update(); - - Size = BaseSize * Parent.RelativeChildSize; - - // Make the swell stop at the hit target - X = Math.Max(0, X); - - double t = Math.Min(HitObject.StartTime, Time.Current); - if (t == HitObject.StartTime && !hasStarted) - { - OnStart?.Invoke(); - hasStarted = true; - } - } - - private bool? lastWasCentre; - - public override bool OnPressed(TaikoAction action) - { - // Don't handle keys before the swell starts - if (Time.Current < HitObject.StartTime) - return false; - - var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre; - - // Ensure alternating centre and rim hits - if (lastWasCentre == isCentre) - return false; - lastWasCentre = isCentre; - - UpdateJudgement(true); - - return true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public class DrawableSwell : DrawableTaikoHitObject + { + /// + /// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime. + /// This is only ever invoked once. + /// + public event Action OnStart; + + private const float target_ring_thick_border = 1.4f; + private const float target_ring_thin_border = 1f; + private const float target_ring_scale = 5f; + private const float inner_ring_alpha = 0.65f; + + private readonly Container bodyContainer; + private readonly CircularContainer targetRing; + private readonly CircularContainer expandingRing; + + /// + /// The amount of times the user has hit this swell. + /// + private int userHits; + + private bool hasStarted; + private readonly SwellSymbolPiece symbol; + + public DrawableSwell(Swell swell) + : base(swell) + { + FillMode = FillMode.Fit; + + AddInternal(bodyContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = 1, + Children = new Drawable[] + { + expandingRing = new CircularContainer + { + Name = "Expanding ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Blending = BlendingMode.Additive, + Masking = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = inner_ring_alpha, + } + } + }, + targetRing = new CircularContainer + { + Name = "Target ring (thick border)", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = target_ring_thick_border, + Blending = BlendingMode.Additive, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new CircularContainer + { + Name = "Target ring (thin border)", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = target_ring_thin_border, + BorderColour = Color4.White, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + } + } + } + }); + + MainPiece.Add(symbol = new SwellSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.YellowDark; + expandingRing.Colour = colours.YellowLight; + targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize + Width *= Parent.RelativeChildSize.X; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (userTriggered) + { + userHits++; + + var completion = (float)userHits / HitObject.RequiredHits; + + expandingRing + .FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50) + .Then() + .FadeTo(completion / 8, 2000, Easing.OutQuint); + + symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint); + + expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); + + if (userHits == HitObject.RequiredHits) + AddJudgement(new TaikoJudgement { Result = HitResult.Great }); + } + else + { + if (timeOffset < 0) + return; + + //TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP + AddJudgement(userHits > HitObject.RequiredHits / 2 + ? new TaikoJudgement { Result = HitResult.Good } + : new TaikoJudgement { Result = HitResult.Miss }); + } + } + + protected override void UpdateState(ArmedState state) + { + const float preempt = 100; + const float out_transition_time = 300; + + double untilStartTime = HitObject.StartTime - Time.Current; + double untilJudgement = untilStartTime + (Judgements.FirstOrDefault()?.TimeOffset ?? 0) + HitObject.Duration; + + targetRing.Delay(untilStartTime - preempt).ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint); + this.Delay(untilJudgement).FadeOut(out_transition_time, Easing.Out); + + switch (state) + { + case ArmedState.Hit: + bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time); + break; + } + + Expire(); + } + + protected override void Update() + { + base.Update(); + + Size = BaseSize * Parent.RelativeChildSize; + + // Make the swell stop at the hit target + X = Math.Max(0, X); + + double t = Math.Min(HitObject.StartTime, Time.Current); + if (t == HitObject.StartTime && !hasStarted) + { + OnStart?.Invoke(); + hasStarted = true; + } + } + + private bool? lastWasCentre; + + public override bool OnPressed(TaikoAction action) + { + // Don't handle keys before the swell starts + if (Time.Current < HitObject.StartTime) + return false; + + var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre; + + // Ensure alternating centre and rim hits + if (lastWasCentre == isCentre) + return false; + lastWasCentre = isCentre; + + UpdateJudgement(true); + + return true; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index f20ad5b4aa..971fd8854d 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -1,52 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK; -using System.Linq; -using osu.Game.Audio; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public abstract class DrawableTaikoHitObject : DrawableHitObject, IKeyBindingHandler - where TaikoHitType : TaikoHitObject - { - public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); - - protected readonly Vector2 BaseSize; - - protected readonly TaikoPiece MainPiece; - - public new TaikoHitType HitObject; - - protected DrawableTaikoHitObject(TaikoHitType hitObject) - : base(hitObject) - { - HitObject = hitObject; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.Custom; - - RelativeSizeAxes = Axes.Both; - Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); - - InternalChild = MainPiece = CreateMainPiece(); - MainPiece.KiaiMode = HitObject.Kiai; - } - - // Normal and clap samples are handled by the drum - protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP); - - protected override string SampleNamespace => "Taiko"; - - protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); - - public abstract bool OnPressed(TaikoAction action); - - public virtual bool OnReleased(TaikoAction action) => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; +using OpenTK; +using System.Linq; +using osu.Game.Audio; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables +{ + public abstract class DrawableTaikoHitObject : DrawableHitObject, IKeyBindingHandler + where TaikoHitType : TaikoHitObject + { + public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); + + protected readonly Vector2 BaseSize; + + protected readonly TaikoPiece MainPiece; + + public new TaikoHitType HitObject; + + protected DrawableTaikoHitObject(TaikoHitType hitObject) + : base(hitObject) + { + HitObject = hitObject; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Custom; + + RelativeSizeAxes = Axes.Both; + Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); + + InternalChild = MainPiece = CreateMainPiece(); + MainPiece.KiaiMode = HitObject.Kiai; + } + + // Normal and clap samples are handled by the drum + protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP); + + protected override string SampleNamespace => "Taiko"; + + protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); + + public abstract bool OnPressed(TaikoAction action); + + public virtual bool OnReleased(TaikoAction action) => false; + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs index 05c3f45d80..27e2b3c762 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - /// - /// The symbol used for centre hit pieces. - /// - public class CentreHitSymbolPiece : Container - { - public CentreHitSymbolPiece() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - Size = new Vector2(CirclePiece.SYMBOL_SIZE); - Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER); - - Children = new[] - { - new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new[] { new Box { RelativeSizeAxes = Axes.Both } } - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + /// + /// The symbol used for centre hit pieces. + /// + public class CentreHitSymbolPiece : Container + { + public CentreHitSymbolPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(CirclePiece.SYMBOL_SIZE); + Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER); + + Children = new[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new[] { new Box { RelativeSizeAxes = Axes.Both } } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index b68dac9e85..7a4931dc71 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -1,158 +1,158 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Backgrounds; -using OpenTK.Graphics; -using osu.Game.Beatmaps.ControlPoints; -using osu.Framework.Audio.Track; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - /// - /// A circle piece which is used uniformly through osu!taiko to visualise hitobjects. - /// - /// Note that this can actually be non-circle if the width is changed. See - /// for a usage example. - /// - /// - public class CirclePiece : TaikoPiece - { - public const float SYMBOL_SIZE = 0.45f; - public const float SYMBOL_BORDER = 8; - private const double pre_beat_transition_time = 80; - - /// - /// The colour of the inner circle and outer glows. - /// - public override Color4 AccentColour - { - get { return base.AccentColour; } - set - { - base.AccentColour = value; - - background.Colour = AccentColour; - - resetEdgeEffects(); - } - } - - /// - /// Whether Kiai mode effects are enabled for this circle piece. - /// - public override bool KiaiMode - { - get { return base.KiaiMode; } - set - { - base.KiaiMode = value; - - resetEdgeEffects(); - } - } - - protected override Container Content => content; - - private readonly Container content; - - private readonly Container background; - - public Box FlashBox; - - public CirclePiece() - { - EarlyActivationMilliseconds = pre_beat_transition_time; - - AddRangeInternal(new Drawable[] - { - background = new CircularContainer - { - Name = "Background", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }, - new Triangles - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - ColourLight = Color4.White, - ColourDark = Color4.White.Darken(0.1f) - } - } - }, - new CircularContainer - { - Name = "Ring", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - BorderThickness = 8, - BorderColour = Color4.White, - Masking = true, - Children = new[] - { - FlashBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - Blending = BlendingMode.Additive, - Alpha = 0, - AlwaysPresent = true - } - } - }, - content = new Container - { - Name = "Content", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - } - }); - } - - private const float edge_alpha_kiai = 0.5f; - - private void resetEdgeEffects() - { - background.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = AccentColour.Opacity(KiaiMode ? edge_alpha_kiai : 1f), - Radius = KiaiMode ? 32 : 8 - }; - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) - { - if (!effectPoint.KiaiMode) - return; - - if (beatIndex % (int)timingPoint.TimeSignature != 0) - return; - - double duration = timingPoint.BeatLength * 2; - - background - .FadeEdgeEffectTo(1, pre_beat_transition_time, Easing.OutQuint) - .Then() - .FadeEdgeEffectTo(edge_alpha_kiai, duration, Easing.OutQuint); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Backgrounds; +using OpenTK.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Framework.Audio.Track; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + /// + /// A circle piece which is used uniformly through osu!taiko to visualise hitobjects. + /// + /// Note that this can actually be non-circle if the width is changed. See + /// for a usage example. + /// + /// + public class CirclePiece : TaikoPiece + { + public const float SYMBOL_SIZE = 0.45f; + public const float SYMBOL_BORDER = 8; + private const double pre_beat_transition_time = 80; + + /// + /// The colour of the inner circle and outer glows. + /// + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + + background.Colour = AccentColour; + + resetEdgeEffects(); + } + } + + /// + /// Whether Kiai mode effects are enabled for this circle piece. + /// + public override bool KiaiMode + { + get { return base.KiaiMode; } + set + { + base.KiaiMode = value; + + resetEdgeEffects(); + } + } + + protected override Container Content => content; + + private readonly Container content; + + private readonly Container background; + + public Box FlashBox; + + public CirclePiece() + { + EarlyActivationMilliseconds = pre_beat_transition_time; + + AddRangeInternal(new Drawable[] + { + background = new CircularContainer + { + Name = "Background", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + new Triangles + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + ColourLight = Color4.White, + ColourDark = Color4.White.Darken(0.1f) + } + } + }, + new CircularContainer + { + Name = "Ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + BorderThickness = 8, + BorderColour = Color4.White, + Masking = true, + Children = new[] + { + FlashBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Blending = BlendingMode.Additive, + Alpha = 0, + AlwaysPresent = true + } + } + }, + content = new Container + { + Name = "Content", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } + }); + } + + private const float edge_alpha_kiai = 0.5f; + + private void resetEdgeEffects() + { + background.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Opacity(KiaiMode ? edge_alpha_kiai : 1f), + Radius = KiaiMode ? 32 : 8 + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + if (!effectPoint.KiaiMode) + return; + + if (beatIndex % (int)timingPoint.TimeSignature != 0) + return; + + double duration = timingPoint.BeatLength * 2; + + background + .FadeEdgeEffectTo(1, pre_beat_transition_time, Easing.OutQuint) + .Then() + .FadeEdgeEffectTo(edge_alpha_kiai, duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs index 26d099c101..c5b8228439 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs @@ -1,30 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - public class ElongatedCirclePiece : CirclePiece - { - public ElongatedCirclePiece() - { - RelativeSizeAxes = Axes.Y; - } - - protected override void Update() - { - base.Update(); - - var padding = Content.DrawHeight * Content.Width / 2; - - Content.Padding = new MarginPadding - { - Left = padding, - Right = padding, - }; - - Width = Parent.DrawSize.X + DrawHeight; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + public class ElongatedCirclePiece : CirclePiece + { + public ElongatedCirclePiece() + { + RelativeSizeAxes = Axes.Y; + } + + protected override void Update() + { + base.Update(); + + var padding = Content.DrawHeight * Content.Width / 2; + + Content.Padding = new MarginPadding + { + Left = padding, + Right = padding, + }; + + Width = Parent.DrawSize.X + DrawHeight; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs index 7614f1d6d8..d3487fb65c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - /// - /// The symbol used for rim hit pieces. - /// - public class RimHitSymbolPiece : CircularContainer - { - public RimHitSymbolPiece() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - Size = new Vector2(CirclePiece.SYMBOL_SIZE); - - BorderThickness = CirclePiece.SYMBOL_BORDER; - BorderColour = Color4.White; - Masking = true; - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + /// + /// The symbol used for rim hit pieces. + /// + public class RimHitSymbolPiece : CircularContainer + { + public RimHitSymbolPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(CirclePiece.SYMBOL_SIZE); + + BorderThickness = CirclePiece.SYMBOL_BORDER; + BorderColour = Color4.White; + Masking = true; + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index bccfaceb56..d70ac64a27 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - /// - /// The symbol used for swell pieces. - /// - public class SwellSymbolPiece : Container - { - public SwellSymbolPiece() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - Size = new Vector2(CirclePiece.SYMBOL_SIZE); - Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER); - - Children = new[] - { - new SpriteIcon - { - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_asterisk, - Shadow = false - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + /// + /// The symbol used for swell pieces. + /// + public class SwellSymbolPiece : Container + { + public SwellSymbolPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(CirclePiece.SYMBOL_SIZE); + Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER); + + Children = new[] + { + new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_asterisk, + Shadow = false + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs index fa2d1c78e3..e630847aec 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; -using OpenTK.Graphics; -using osu.Game.Graphics.Containers; -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - public class TaikoPiece : BeatSyncedContainer, IHasAccentColour - { - private Color4 accentColour; - /// - /// The colour of the inner circle and outer glows. - /// - public virtual Color4 AccentColour - { - get { return accentColour; } - set { accentColour = value; } - } - - private bool kiaiMode; - /// - /// Whether Kiai mode effects are enabled for this circle piece. - /// - public virtual bool KiaiMode - { - get { return kiaiMode; } - set - { - kiaiMode = value; - } - } - - public TaikoPiece() - { - RelativeSizeAxes = Axes.Both; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using OpenTK.Graphics; +using osu.Game.Graphics.Containers; +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + public class TaikoPiece : BeatSyncedContainer, IHasAccentColour + { + private Color4 accentColour; + /// + /// The colour of the inner circle and outer glows. + /// + public virtual Color4 AccentColour + { + get { return accentColour; } + set { accentColour = value; } + } + + private bool kiaiMode; + /// + /// Whether Kiai mode effects are enabled for this circle piece. + /// + public virtual bool KiaiMode + { + get { return kiaiMode; } + set + { + kiaiMode = value; + } + } + + public TaikoPiece() + { + RelativeSizeAxes = Axes.Both; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs index 0ff2742be6..976836a5ed 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -1,65 +1,65 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces -{ - public class TickPiece : TaikoPiece - { - /// - /// Any tick that is not the first for a drumroll is not filled, but is instead displayed - /// as a hollow circle. This is what controls the border width of that circle. - /// - private const float tick_border_width = 5; - - /// - /// The size of a tick. - /// - private const float tick_size = 0.35f; - - private bool filled; - public bool Filled - { - get { return filled; } - set - { - filled = value; - fillBox.Alpha = filled ? 1 : 0; - } - } - - private readonly Box fillBox; - - public TickPiece() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; - Size = new Vector2(tick_size); - - Add(new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = tick_border_width, - BorderColour = Color4.White, - Children = new[] - { - fillBox = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces +{ + public class TickPiece : TaikoPiece + { + /// + /// Any tick that is not the first for a drumroll is not filled, but is instead displayed + /// as a hollow circle. This is what controls the border width of that circle. + /// + private const float tick_border_width = 5; + + /// + /// The size of a tick. + /// + private const float tick_size = 0.35f; + + private bool filled; + public bool Filled + { + get { return filled; } + set + { + filled = value; + fillBox.Alpha = filled ? 1 : 0; + } + } + + private readonly Box fillBox; + + public TickPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + Size = new Vector2(tick_size); + + Add(new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = tick_border_width, + BorderColour = Color4.White, + Children = new[] + { + fillBox = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 181fee8393..64219c7b52 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -1,82 +1,82 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using System; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class DrumRoll : TaikoHitObject, IHasEndTime - { - /// - /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. - /// - private const float base_distance = 100; - - public double EndTime => StartTime + Duration; - - public double Duration { get; set; } - - /// - /// Numer of ticks per beat length. - /// - public int TickRate = 1; - - /// - /// Number of drum roll ticks required for a "Good" hit. - /// - public double RequiredGoodHits { get; protected set; } - - /// - /// Number of drum roll ticks required for a "Great" hit. - /// - public double RequiredGreatHits { get; protected set; } - - /// - /// The length (in milliseconds) between ticks of this drumroll. - /// Half of this value is the hit window of the ticks. - /// - private double tickSpacing = 100; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - - tickSpacing = timingPoint.BeatLength / TickRate; - - RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); - RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); - } - - protected override void CreateNestedHitObjects() - { - base.CreateNestedHitObjects(); - - createTicks(); - } - - private void createTicks() - { - if (tickSpacing == 0) - return; - - bool first = true; - for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) - { - AddNested(new DrumRollTick - { - FirstTick = first, - TickSpacing = tickSpacing, - StartTime = t, - IsStrong = IsStrong - }); - - first = false; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using System; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class DrumRoll : TaikoHitObject, IHasEndTime + { + /// + /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. + /// + private const float base_distance = 100; + + public double EndTime => StartTime + Duration; + + public double Duration { get; set; } + + /// + /// Numer of ticks per beat length. + /// + public int TickRate = 1; + + /// + /// Number of drum roll ticks required for a "Good" hit. + /// + public double RequiredGoodHits { get; protected set; } + + /// + /// Number of drum roll ticks required for a "Great" hit. + /// + public double RequiredGreatHits { get; protected set; } + + /// + /// The length (in milliseconds) between ticks of this drumroll. + /// Half of this value is the hit window of the ticks. + /// + private double tickSpacing = 100; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + + tickSpacing = timingPoint.BeatLength / TickRate; + + RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); + RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); + } + + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + + createTicks(); + } + + private void createTicks() + { + if (tickSpacing == 0) + return; + + bool first = true; + for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) + { + AddNested(new DrumRollTick + { + FirstTick = first, + TickSpacing = tickSpacing, + StartTime = t, + IsStrong = IsStrong + }); + + first = false; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index f72db4db6d..e546d6427f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class DrumRollTick : TaikoHitObject - { - /// - /// Whether this is the first (initial) tick of the slider. - /// - public bool FirstTick; - - /// - /// The length (in milliseconds) between this tick and the next. - /// Half of this value is the hit window of the tick. - /// - public double TickSpacing; - - /// - /// The time allowed to hit this tick. - /// - public double HitWindow => TickSpacing / 2; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class DrumRollTick : TaikoHitObject + { + /// + /// Whether this is the first (initial) tick of the slider. + /// + public bool FirstTick; + + /// + /// The length (in milliseconds) between this tick and the next. + /// Half of this value is the hit window of the tick. + /// + public double TickSpacing; + + /// + /// The time allowed to hit this tick. + /// + public double HitWindow => TickSpacing / 2; + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index c91a1f1714..0b47aa490b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class Hit : TaikoHitObject - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class Hit : TaikoHitObject + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs index d161970c38..2e52e57f1f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class RimHit : Hit - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class RimHit : Hit + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index e17d11e3e7..eb6f931af4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public class Swell : TaikoHitObject, IHasEndTime - { - public double EndTime => StartTime + Duration; - - public double Duration { get; set; } - - /// - /// The number of hits required to complete the swell successfully. - /// - public int RequiredHits = 10; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class Swell : TaikoHitObject, IHasEndTime + { + public double EndTime => StartTime + Duration; + + public double Duration { get; set; } + + /// + /// The number of hits required to complete the swell successfully. + /// + public int RequiredHits = 10; + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 6e0d0e380a..63de096238 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Taiko.Objects -{ - public abstract class TaikoHitObject : HitObject - { - /// - /// Default size of a drawable taiko hit object. - /// - public const float DEFAULT_SIZE = 0.45f; - - /// - /// Scale multiplier for a strong drawable taiko hit object. - /// - public const float STRONG_SCALE = 1.4f; - - /// - /// Default size of a strong drawable taiko hit object. - /// - public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE; - - /// - /// Whether this HitObject is a "strong" type. - /// Strong hit objects give more points for hitting the hit object with both keys. - /// - public bool IsStrong; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public abstract class TaikoHitObject : HitObject + { + /// + /// Default size of a drawable taiko hit object. + /// + public const float DEFAULT_SIZE = 0.45f; + + /// + /// Scale multiplier for a strong drawable taiko hit object. + /// + public const float STRONG_SCALE = 1.4f; + + /// + /// Default size of a strong drawable taiko hit object. + /// + public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE; + + /// + /// Whether this HitObject is a "strong" type. + /// Strong hit objects give more points for hitting the hit object with both keys. + /// + public bool IsStrong; + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs index 54f08e9015..70a516af79 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs @@ -1,127 +1,127 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Rulesets.Taiko.Objects -{ - internal class TaikoHitObjectDifficulty - { - /// - /// Factor by how much individual / overall strain decays per second. - /// - /// - /// These values are results of tweaking a lot and taking into account general feedback. - /// - internal const double DECAY_BASE = 0.30; - - private const double type_change_bonus = 0.75; - private const double rhythm_change_bonus = 1.0; - private const double rhythm_change_base_threshold = 0.2; - private const double rhythm_change_base = 2.0; - - internal TaikoHitObject BaseHitObject; - - /// - /// Measures note density in a way - /// - internal double Strain = 1; - - private double timeElapsed; - private int sameTypeSince = 1; - - private bool isRim => BaseHitObject is RimHit; - - public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject) - { - BaseHitObject = baseHitObject; - } - - internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate) - { - // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. - // See Taiko feedback thread. - timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); - - double addition = 1; - - // Only if we are no slider or spinner we get an extra addition - if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit - && BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past - { - addition += typeChangeAddition(previousHitObject); - addition += rhythmChangeAddition(previousHitObject); - } - - double additionFactor = 1.0; - // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50 - if (timeElapsed < 50.0) - additionFactor = 0.4 + 0.6 * timeElapsed / 50.0; - - Strain = previousHitObject.Strain * decay + addition * additionFactor; - } - - private TypeSwitch lastTypeSwitchEven = TypeSwitch.None; - private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject) - { - // If we don't have the same hit type, trigger a type change! - if (previousHitObject.isRim != isRim) - { - lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd; - - // We only want a bonus if the parity of the type switch changes! - switch (previousHitObject.lastTypeSwitchEven) - { - case TypeSwitch.Even: - if (lastTypeSwitchEven == TypeSwitch.Odd) - return type_change_bonus; - break; - case TypeSwitch.Odd: - if (lastTypeSwitchEven == TypeSwitch.Even) - return type_change_bonus; - break; - } - } - // No type change? Increment counter and keep track of last type switch - else - { - lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven; - sameTypeSince = previousHitObject.sameTypeSince + 1; - } - - return 0; - } - - private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject) - { - // We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time. - if (timeElapsed == 0 || previousHitObject.timeElapsed == 0) - return 0; - - double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed); - - if (timeElapsedRatio >= 8) - return 0; - - double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; - - if (isWithinChangeThreshold(difference)) - return rhythm_change_bonus; - - return 0; - } - - private bool isWithinChangeThreshold(double value) - { - return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold; - } - - private enum TypeSwitch - { - None, - Even, - Odd - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + internal class TaikoHitObjectDifficulty + { + /// + /// Factor by how much individual / overall strain decays per second. + /// + /// + /// These values are results of tweaking a lot and taking into account general feedback. + /// + internal const double DECAY_BASE = 0.30; + + private const double type_change_bonus = 0.75; + private const double rhythm_change_bonus = 1.0; + private const double rhythm_change_base_threshold = 0.2; + private const double rhythm_change_base = 2.0; + + internal TaikoHitObject BaseHitObject; + + /// + /// Measures note density in a way + /// + internal double Strain = 1; + + private double timeElapsed; + private int sameTypeSince = 1; + + private bool isRim => BaseHitObject is RimHit; + + public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject) + { + BaseHitObject = baseHitObject; + } + + internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate) + { + // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. + // See Taiko feedback thread. + timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; + double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); + + double addition = 1; + + // Only if we are no slider or spinner we get an extra addition + if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit + && BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past + { + addition += typeChangeAddition(previousHitObject); + addition += rhythmChangeAddition(previousHitObject); + } + + double additionFactor = 1.0; + // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50 + if (timeElapsed < 50.0) + additionFactor = 0.4 + 0.6 * timeElapsed / 50.0; + + Strain = previousHitObject.Strain * decay + addition * additionFactor; + } + + private TypeSwitch lastTypeSwitchEven = TypeSwitch.None; + private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject) + { + // If we don't have the same hit type, trigger a type change! + if (previousHitObject.isRim != isRim) + { + lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd; + + // We only want a bonus if the parity of the type switch changes! + switch (previousHitObject.lastTypeSwitchEven) + { + case TypeSwitch.Even: + if (lastTypeSwitchEven == TypeSwitch.Odd) + return type_change_bonus; + break; + case TypeSwitch.Odd: + if (lastTypeSwitchEven == TypeSwitch.Even) + return type_change_bonus; + break; + } + } + // No type change? Increment counter and keep track of last type switch + else + { + lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven; + sameTypeSince = previousHitObject.sameTypeSince + 1; + } + + return 0; + } + + private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject) + { + // We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time. + if (timeElapsed == 0 || previousHitObject.timeElapsed == 0) + return 0; + + double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed); + + if (timeElapsedRatio >= 8) + return 0; + + double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; + + if (isWithinChangeThreshold(difference)) + return rhythm_change_bonus; + + return 0; + } + + private bool isWithinChangeThreshold(double value) + { + return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold; + } + + private enum TypeSwitch + { + None, + Even, + Odd + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs index 77218af5e1..26f70bc927 100644 --- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Runtime.CompilerServices; - -// We publish our internal attributes to other sub-projects of the framework. -// Note, that we omit visual tests as they are meant to test the framework -// behavior "in the wild". - -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")] -[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")] +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Runtime.CompilerServices; + +// We publish our internal attributes to other sub-projects of the framework. +// Note, that we omit visual tests as they are meant to test the framework +// behavior "in the wild". + +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")] diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 1a556fe01d..e7b2789010 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -1,130 +1,130 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Replays; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Taiko.Replays -{ - public class TaikoAutoGenerator : AutoGenerator - { - private const double swell_hit_speed = 50; - - public TaikoAutoGenerator(Beatmap beatmap) - : base(beatmap) - { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; - } - - protected Replay Replay; - protected List Frames => Replay.Frames; - - public override Replay Generate() - { - bool hitButton = true; - - Frames.Add(new TaikoReplayFrame(-100000)); - Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000)); - - for (int i = 0; i < Beatmap.HitObjects.Count; i++) - { - TaikoHitObject h = Beatmap.HitObjects[i]; - - IHasEndTime endTimeData = h as IHasEndTime; - double endTime = endTimeData?.EndTime ?? h.StartTime; - - Swell swell = h as Swell; - DrumRoll drumRoll = h as DrumRoll; - Hit hit = h as Hit; - - if (swell != null) - { - int d = 0; - int count = 0; - int req = swell.RequiredHits; - double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); - for (double j = h.StartTime; j < endTime; j += hitRate) - { - TaikoAction action; - - switch (d) - { - default: - case 0: - action = TaikoAction.LeftCentre; - break; - case 1: - action = TaikoAction.LeftRim; - break; - case 2: - action = TaikoAction.RightCentre; - break; - case 3: - action = TaikoAction.RightRim; - break; - } - - Frames.Add(new TaikoReplayFrame(j, action)); - d = (d + 1) % 4; - if (++count == req) - break; - } - } - else if (drumRoll != null) - { - foreach (var tick in drumRoll.NestedHitObjects.OfType()) - { - Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre)); - hitButton = !hitButton; - } - } - else if (hit != null) - { - TaikoAction[] actions; - - if (hit is CentreHit) - { - actions = h.IsStrong - ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } - : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre }; - } - else - { - actions = h.IsStrong - ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim } - : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim }; - } - - Frames.Add(new TaikoReplayFrame(h.StartTime, actions)); - } - else - throw new InvalidOperationException("Unknown hit object type."); - - Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); - - if (i < Beatmap.HitObjects.Count - 1) - { - double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; - if (waitTime > endTime) - Frames.Add(new TaikoReplayFrame(waitTime)); - } - - hitButton = !hitButton; - } - - return Replay; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Replays; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Taiko.Replays +{ + public class TaikoAutoGenerator : AutoGenerator + { + private const double swell_hit_speed = 50; + + public TaikoAutoGenerator(Beatmap beatmap) + : base(beatmap) + { + Replay = new Replay + { + User = new User + { + Username = @"Autoplay", + } + }; + } + + protected Replay Replay; + protected List Frames => Replay.Frames; + + public override Replay Generate() + { + bool hitButton = true; + + Frames.Add(new TaikoReplayFrame(-100000)); + Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000)); + + for (int i = 0; i < Beatmap.HitObjects.Count; i++) + { + TaikoHitObject h = Beatmap.HitObjects[i]; + + IHasEndTime endTimeData = h as IHasEndTime; + double endTime = endTimeData?.EndTime ?? h.StartTime; + + Swell swell = h as Swell; + DrumRoll drumRoll = h as DrumRoll; + Hit hit = h as Hit; + + if (swell != null) + { + int d = 0; + int count = 0; + int req = swell.RequiredHits; + double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); + for (double j = h.StartTime; j < endTime; j += hitRate) + { + TaikoAction action; + + switch (d) + { + default: + case 0: + action = TaikoAction.LeftCentre; + break; + case 1: + action = TaikoAction.LeftRim; + break; + case 2: + action = TaikoAction.RightCentre; + break; + case 3: + action = TaikoAction.RightRim; + break; + } + + Frames.Add(new TaikoReplayFrame(j, action)); + d = (d + 1) % 4; + if (++count == req) + break; + } + } + else if (drumRoll != null) + { + foreach (var tick in drumRoll.NestedHitObjects.OfType()) + { + Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre)); + hitButton = !hitButton; + } + } + else if (hit != null) + { + TaikoAction[] actions; + + if (hit is CentreHit) + { + actions = h.IsStrong + ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } + : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre }; + } + else + { + actions = h.IsStrong + ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim } + : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim }; + } + + Frames.Add(new TaikoReplayFrame(h.StartTime, actions)); + } + else + throw new InvalidOperationException("Unknown hit object type."); + + Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY)); + + if (i < Beatmap.HitObjects.Count - 1) + { + double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; + if (waitTime > endTime) + Frames.Add(new TaikoReplayFrame(waitTime)); + } + + hitButton = !hitButton; + } + + return Replay; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index c80bddc304..6ccbd575e5 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Replays; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Input; - -namespace osu.Game.Rulesets.Taiko.Replays -{ - internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler - { - public TaikoFramedReplayInputHandler(Replay replay) - : base(replay) - { - } - - protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - - public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Replays; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input; + +namespace osu.Game.Rulesets.Taiko.Replays +{ + internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler + { + public TaikoFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); + + public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + } +} diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index 6cd63f6c70..e510b34ad7 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Rulesets.Replays.Types; - -namespace osu.Game.Rulesets.Taiko.Replays -{ - public class TaikoReplayFrame : ReplayFrame, IConvertibleReplayFrame - { - public List Actions = new List(); - - public TaikoReplayFrame() - { - } - - public TaikoReplayFrame(double time, params TaikoAction[] actions) - : base(time) - { - Actions.AddRange(actions); - } - - public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) - { - if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); - if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); - if (legacyFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre); - if (legacyFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Rulesets.Replays.Types; + +namespace osu.Game.Rulesets.Taiko.Replays +{ + public class TaikoReplayFrame : ReplayFrame, IConvertibleReplayFrame + { + public List Actions = new List(); + + public TaikoReplayFrame() + { + } + + public TaikoReplayFrame(double time, params TaikoAction[] actions) + : base(time) + { + Actions.AddRange(actions); + } + + public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) + { + if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); + if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); + if (legacyFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre); + if (legacyFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index b91f37d84d..c7f75e44fa 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -1,144 +1,144 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Taiko.Scoring -{ - internal class TaikoScoreProcessor : ScoreProcessor - { - /// - /// The HP awarded by a hit. - /// - private const double hp_hit_great = 0.03; - - /// - /// The HP awarded for a hit. - /// - private const double hp_hit_good = 0.011; - - /// - /// The minimum HP deducted for a . - /// This occurs when HP Drain = 0. - /// - private const double hp_miss_min = -0.0018; - - /// - /// The median HP deducted for a . - /// This occurs when HP Drain = 5. - /// - private const double hp_miss_mid = -0.0075; - - /// - /// The maximum HP deducted for a . - /// This occurs when HP Drain = 10. - /// - private const double hp_miss_max = -0.12; - - /// - /// The HP awarded for a hit. - /// - /// hits award less HP as they're more spammable, although in hindsight - /// this probably awards too little HP and is kept at this value for now for compatibility. - /// - /// - private const double hp_hit_tick = 0.00000003; - - /// - /// Taiko fails at the end of the map if the player has not half-filled their HP bar. - /// - protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5; - - private double hpIncreaseTick; - private double hpIncreaseGreat; - private double hpIncreaseGood; - private double hpIncreaseMiss; - - public TaikoScoreProcessor() - { - } - - public TaikoScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) - { - } - - protected override void SimulateAutoplay(Beatmap beatmap) - { - double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); - - hpIncreaseTick = hp_hit_tick; - hpIncreaseGreat = hpMultiplierNormal * hp_hit_great; - hpIncreaseGood = hpMultiplierNormal * hp_hit_good; - hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); - - foreach (var obj in beatmap.HitObjects) - { - if (obj is Hit) - { - AddJudgement(new TaikoJudgement { Result = HitResult.Great }); - if (obj.IsStrong) - AddJudgement(new TaikoStrongHitJudgement()); - } - else if (obj is DrumRoll) - { - for (int i = 0; i < ((DrumRoll)obj).NestedHitObjects.OfType().Count(); i++) - { - AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); - - if (obj.IsStrong) - AddJudgement(new TaikoStrongHitJudgement()); - } - - AddJudgement(new TaikoJudgement { Result = HitResult.Great }); - - if (obj.IsStrong) - AddJudgement(new TaikoStrongHitJudgement()); - } - else if (obj is Swell) - { - AddJudgement(new TaikoJudgement { Result = HitResult.Great }); - } - } - } - - protected override void OnNewJudgement(Judgement judgement) - { - base.OnNewJudgement(judgement); - - bool isTick = judgement is TaikoDrumRollTickJudgement; - - // Apply HP changes - switch (judgement.Result) - { - case HitResult.Miss: - // Missing ticks shouldn't drop HP - if (!isTick) - Health.Value += hpIncreaseMiss; - break; - case HitResult.Good: - Health.Value += hpIncreaseGood; - break; - case HitResult.Great: - if (isTick) - Health.Value += hpIncreaseTick; - else - Health.Value += hpIncreaseGreat; - break; - } - } - - protected override void Reset(bool storeResults) - { - base.Reset(storeResults); - - Health.Value = 0; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.Scoring +{ + internal class TaikoScoreProcessor : ScoreProcessor + { + /// + /// The HP awarded by a hit. + /// + private const double hp_hit_great = 0.03; + + /// + /// The HP awarded for a hit. + /// + private const double hp_hit_good = 0.011; + + /// + /// The minimum HP deducted for a . + /// This occurs when HP Drain = 0. + /// + private const double hp_miss_min = -0.0018; + + /// + /// The median HP deducted for a . + /// This occurs when HP Drain = 5. + /// + private const double hp_miss_mid = -0.0075; + + /// + /// The maximum HP deducted for a . + /// This occurs when HP Drain = 10. + /// + private const double hp_miss_max = -0.12; + + /// + /// The HP awarded for a hit. + /// + /// hits award less HP as they're more spammable, although in hindsight + /// this probably awards too little HP and is kept at this value for now for compatibility. + /// + /// + private const double hp_hit_tick = 0.00000003; + + /// + /// Taiko fails at the end of the map if the player has not half-filled their HP bar. + /// + protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5; + + private double hpIncreaseTick; + private double hpIncreaseGreat; + private double hpIncreaseGood; + private double hpIncreaseMiss; + + public TaikoScoreProcessor() + { + } + + public TaikoScoreProcessor(RulesetContainer rulesetContainer) + : base(rulesetContainer) + { + } + + protected override void SimulateAutoplay(Beatmap beatmap) + { + double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); + + hpIncreaseTick = hp_hit_tick; + hpIncreaseGreat = hpMultiplierNormal * hp_hit_great; + hpIncreaseGood = hpMultiplierNormal * hp_hit_good; + hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); + + foreach (var obj in beatmap.HitObjects) + { + if (obj is Hit) + { + AddJudgement(new TaikoJudgement { Result = HitResult.Great }); + if (obj.IsStrong) + AddJudgement(new TaikoStrongHitJudgement()); + } + else if (obj is DrumRoll) + { + for (int i = 0; i < ((DrumRoll)obj).NestedHitObjects.OfType().Count(); i++) + { + AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); + + if (obj.IsStrong) + AddJudgement(new TaikoStrongHitJudgement()); + } + + AddJudgement(new TaikoJudgement { Result = HitResult.Great }); + + if (obj.IsStrong) + AddJudgement(new TaikoStrongHitJudgement()); + } + else if (obj is Swell) + { + AddJudgement(new TaikoJudgement { Result = HitResult.Great }); + } + } + } + + protected override void OnNewJudgement(Judgement judgement) + { + base.OnNewJudgement(judgement); + + bool isTick = judgement is TaikoDrumRollTickJudgement; + + // Apply HP changes + switch (judgement.Result) + { + case HitResult.Miss: + // Missing ticks shouldn't drop HP + if (!isTick) + Health.Value += hpIncreaseMiss; + break; + case HitResult.Good: + Health.Value += hpIncreaseGood; + break; + case HitResult.Great: + if (isTick) + Health.Value += hpIncreaseTick; + else + Health.Value += hpIncreaseGreat; + break; + } + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + Health.Value = 0; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs index 8b4e77573f..58661d7881 100644 --- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs @@ -1,138 +1,138 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; -using System.Collections.Generic; -using System; - -namespace osu.Game.Rulesets.Taiko -{ - internal class TaikoDifficultyCalculator : DifficultyCalculator - { - private const double star_scaling_factor = 0.04125; - - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 400; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.9; - - /// - /// HitObjects are stored as a member variable. - /// - private readonly List difficultyHitObjects = new List(); - - public TaikoDifficultyCalculator(Beatmap beatmap) - : base(beatmap) - { - } - - public override double Calculate(Dictionary categoryDifficulty = null) - { - // Fill our custom DifficultyHitObject class, that carries additional information - difficultyHitObjects.Clear(); - - foreach (var hitObject in Beatmap.HitObjects) - difficultyHitObjects.Add(new TaikoHitObjectDifficulty(hitObject)); - - // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. - difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - - if (!calculateStrainValues()) return 0; - - double starRating = calculateDifficulty() * star_scaling_factor; - - if (categoryDifficulty != null) - { - categoryDifficulty.Add("Strain", starRating); - categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate); - } - - return starRating; - } - - private bool calculateStrainValues() - { - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) - { - if (!hitObjectsEnumerator.MoveNext()) return false; - - TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current; - - // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. - while (hitObjectsEnumerator.MoveNext()) - { - var next = hitObjectsEnumerator.Current; - next?.CalculateStrains(current, TimeRate); - current = next; - } - - return true; - } - } - - private double calculateDifficulty() - { - double actualStrainStep = strain_step * TimeRate; - - // Find the highest strain value within each strain step - List highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - TaikoHitObjectDifficulty previousHitObject = null; - foreach (var hitObject in difficultyHitObjects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.Strain * decay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - maximumStrain = Math.Max(hitObject.Strain, maximumStrain); - - previousHitObject = hitObject; - } - - // Build the weighted sum over the highest strains for each interval - double difficulty = 0; - double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - foreach (double strain in highestStrains) - { - difficulty += weight * strain; - weight *= decay_weight; - } - - return difficulty; - } - - protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new TaikoBeatmapConverter(true); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using System.Collections.Generic; +using System; + +namespace osu.Game.Rulesets.Taiko +{ + internal class TaikoDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.04125; + + /// + /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. + /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. + /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. + /// + private const double strain_step = 400; + + /// + /// The weighting of each strain value decays to this number * it's previous value + /// + private const double decay_weight = 0.9; + + /// + /// HitObjects are stored as a member variable. + /// + private readonly List difficultyHitObjects = new List(); + + public TaikoDifficultyCalculator(Beatmap beatmap) + : base(beatmap) + { + } + + public override double Calculate(Dictionary categoryDifficulty = null) + { + // Fill our custom DifficultyHitObject class, that carries additional information + difficultyHitObjects.Clear(); + + foreach (var hitObject in Beatmap.HitObjects) + difficultyHitObjects.Add(new TaikoHitObjectDifficulty(hitObject)); + + // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. + difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); + + if (!calculateStrainValues()) return 0; + + double starRating = calculateDifficulty() * star_scaling_factor; + + if (categoryDifficulty != null) + { + categoryDifficulty.Add("Strain", starRating); + categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate); + } + + return starRating; + } + + private bool calculateStrainValues() + { + // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. + using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) + { + if (!hitObjectsEnumerator.MoveNext()) return false; + + TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current; + + // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. + while (hitObjectsEnumerator.MoveNext()) + { + var next = hitObjectsEnumerator.Current; + next?.CalculateStrains(current, TimeRate); + current = next; + } + + return true; + } + } + + private double calculateDifficulty() + { + double actualStrainStep = strain_step * TimeRate; + + // Find the highest strain value within each strain step + List highestStrains = new List(); + double intervalEndTime = actualStrainStep; + double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval + + TaikoHitObjectDifficulty previousHitObject = null; + foreach (var hitObject in difficultyHitObjects) + { + // While we are beyond the current interval push the currently available maximum to our strain list + while (hitObject.BaseHitObject.StartTime > intervalEndTime) + { + highestStrains.Add(maximumStrain); + + // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay + // until the beginning of the next interval. + if (previousHitObject == null) + { + maximumStrain = 0; + } + else + { + double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + maximumStrain = previousHitObject.Strain * decay; + } + + // Go to the next time interval + intervalEndTime += actualStrainStep; + } + + // Obtain maximum strain + maximumStrain = Math.Max(hitObject.Strain, maximumStrain); + + previousHitObject = hitObject; + } + + // Build the weighted sum over the highest strains for each interval + double difficulty = 0; + double weight = 1; + highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + foreach (double strain in highestStrains) + { + difficulty += weight * strain; + weight *= decay_weight; + } + + return difficulty; + } + + protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new TaikoBeatmapConverter(true); + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index f668a09f21..dc683ae2f5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Taiko -{ - public class TaikoInputManager : RulesetInputManager - { - public TaikoInputManager(RulesetInfo ruleset) - : base(ruleset, 0, SimultaneousBindingMode.Unique) - { - } - } - - public enum TaikoAction - { - [Description("Left (Rim)")] - LeftRim, - [Description("Left (Centre)")] - LeftCentre, - [Description("Right (Centre)")] - RightCentre, - [Description("Right (Rim)")] - RightRim - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko +{ + public class TaikoInputManager : RulesetInputManager + { + public TaikoInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum TaikoAction + { + [Description("Left (Rim)")] + LeftRim, + [Description("Left (Centre)")] + LeftCentre, + [Description("Right (Centre)")] + RightCentre, + [Description("Right (Rim)")] + RightRim + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 0a9719f27b..cb5e020601 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -1,115 +1,115 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Rulesets.UI; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Taiko.Replays; - -namespace osu.Game.Rulesets.Taiko -{ - public class TaikoRuleset : Ruleset - { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoRulesetContainer(this, beatmap, isForCurrentRuleset); - - public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] - { - new KeyBinding(InputKey.D, TaikoAction.LeftRim), - new KeyBinding(InputKey.F, TaikoAction.LeftCentre), - new KeyBinding(InputKey.J, TaikoAction.RightCentre), - new KeyBinding(InputKey.K, TaikoAction.RightRim), - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre), - new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), - new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim), - }; - - public override IEnumerable GetModsFor(ModType type) - { - switch (type) - { - case ModType.DifficultyReduction: - return new Mod[] - { - new TaikoModEasy(), - new TaikoModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new TaikoModHalfTime(), - new TaikoModDaycore(), - }, - }, - }; - - case ModType.DifficultyIncrease: - return new Mod[] - { - new TaikoModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new TaikoModSuddenDeath(), - new TaikoModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new TaikoModDoubleTime(), - new TaikoModNightcore(), - }, - }, - new TaikoModHidden(), - new TaikoModFlashlight(), - }; - - case ModType.Special: - return new Mod[] - { - new TaikoModRelax(), - null, - null, - new MultiMod - { - Mods = new Mod[] - { - new TaikoModAutoplay(), - new ModCinema(), - }, - }, - }; - - default: - return new Mod[] { }; - } - } - - public override string Description => "osu!taiko"; - - public override string ShortName => "taiko"; - - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; - - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap); - - public override int? LegacyID => 1; - - public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - - public TaikoRuleset(RulesetInfo rulesetInfo = null) - : base(rulesetInfo) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Taiko.Replays; + +namespace osu.Game.Rulesets.Taiko +{ + public class TaikoRuleset : Ruleset + { + public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoRulesetContainer(this, beatmap, isForCurrentRuleset); + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.D, TaikoAction.LeftRim), + new KeyBinding(InputKey.F, TaikoAction.LeftCentre), + new KeyBinding(InputKey.J, TaikoAction.RightCentre), + new KeyBinding(InputKey.K, TaikoAction.RightRim), + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), + new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre), + new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), + new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim), + }; + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.DifficultyReduction: + return new Mod[] + { + new TaikoModEasy(), + new TaikoModNoFail(), + new MultiMod + { + Mods = new Mod[] + { + new TaikoModHalfTime(), + new TaikoModDaycore(), + }, + }, + }; + + case ModType.DifficultyIncrease: + return new Mod[] + { + new TaikoModHardRock(), + new MultiMod + { + Mods = new Mod[] + { + new TaikoModSuddenDeath(), + new TaikoModPerfect(), + }, + }, + new MultiMod + { + Mods = new Mod[] + { + new TaikoModDoubleTime(), + new TaikoModNightcore(), + }, + }, + new TaikoModHidden(), + new TaikoModFlashlight(), + }; + + case ModType.Special: + return new Mod[] + { + new TaikoModRelax(), + null, + null, + new MultiMod + { + Mods = new Mod[] + { + new TaikoModAutoplay(), + new ModCinema(), + }, + }, + }; + + default: + return new Mod[] { }; + } + } + + public override string Description => "osu!taiko"; + + public override string ShortName => "taiko"; + + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap); + + public override int? LegacyID => 1; + + public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + + public TaikoRuleset(RulesetInfo rulesetInfo = null) + : base(rulesetInfo) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 6274232ffd..b07a3ce8df 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -1,50 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Drawables; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// Text that is shown as judgement when a hit object is hit or missed. - /// - public class DrawableTaikoJudgement : DrawableJudgement - { - /// - /// Creates a new judgement text. - /// - /// The object which is being judged. - /// The judgement to visualise. - public DrawableTaikoJudgement(Judgement judgement, DrawableHitObject judgedObject) - : base(judgement, judgedObject) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - switch (Judgement.Result) - { - case HitResult.Good: - Colour = colours.GreenLight; - break; - case HitResult.Great: - Colour = colours.BlueLight; - break; - } - } - - protected override void LoadComplete() - { - if (Judgement.IsHit) - this.MoveToY(-100, 500); - - base.LoadComplete(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// Text that is shown as judgement when a hit object is hit or missed. + /// + public class DrawableTaikoJudgement : DrawableJudgement + { + /// + /// Creates a new judgement text. + /// + /// The object which is being judged. + /// The judgement to visualise. + public DrawableTaikoJudgement(Judgement judgement, DrawableHitObject judgedObject) + : base(judgement, judgedObject) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + switch (Judgement.Result) + { + case HitResult.Good: + Colour = colours.GreenLight; + break; + case HitResult.Great: + Colour = colours.BlueLight; + break; + } + } + + protected override void LoadComplete() + { + if (Judgement.IsHit) + this.MoveToY(-100, 500); + + base.LoadComplete(); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 1a9915f78e..ee2c1d5ad5 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -1,80 +1,80 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// A circle explodes from the hit target to indicate a hitobject has been hit. - /// - internal class HitExplosion : CircularContainer - { - public readonly DrawableHitObject JudgedObject; - - private readonly Box innerFill; - - private readonly bool isRim; - - public HitExplosion(DrawableHitObject judgedObject, bool isRim) - { - this.isRim = isRim; - - JudgedObject = judgedObject; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE); - - RelativePositionAxes = Axes.Both; - - BorderColour = Color4.White; - BorderThickness = 1; - - Alpha = 0.15f; - Masking = true; - - Children = new[] - { - innerFill = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - innerFill.Colour = isRim ? colours.BlueDarker : colours.PinkDarker; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - this.ScaleTo(3f, 1000, Easing.OutQuint); - this.FadeOut(500); - - Expire(); - } - - /// - /// Transforms this hit explosion to visualise a secondary hit. - /// - public void VisualiseSecondHit() - { - this.ResizeTo(new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), 50); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// A circle explodes from the hit target to indicate a hitobject has been hit. + /// + internal class HitExplosion : CircularContainer + { + public readonly DrawableHitObject JudgedObject; + + private readonly Box innerFill; + + private readonly bool isRim; + + public HitExplosion(DrawableHitObject judgedObject, bool isRim) + { + this.isRim = isRim; + + JudgedObject = judgedObject; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE); + + RelativePositionAxes = Axes.Both; + + BorderColour = Color4.White; + BorderThickness = 1; + + Alpha = 0.15f; + Masking = true; + + Children = new[] + { + innerFill = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + innerFill.Colour = isRim ? colours.BlueDarker : colours.PinkDarker; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.ScaleTo(3f, 1000, Easing.OutQuint); + this.FadeOut(500); + + Expire(); + } + + /// + /// Transforms this hit explosion to visualise a secondary hit. + /// + public void VisualiseSecondHit() + { + this.ResizeTo(new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), 50); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs index fa2b6e737f..9d824ca5b7 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs @@ -1,92 +1,92 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// A component that is displayed at the hit position in the taiko playfield. - /// - internal class HitTarget : Container - { - /// - /// Thickness of all drawn line pieces. - /// - private const float border_thickness = 2.5f; - - public HitTarget() - { - Children = new Drawable[] - { - new Box - { - Name = "Bar Upper", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f), - Alpha = 0.1f - }, - new CircularContainer - { - Name = "Strong Hit Ring", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), - Masking = true, - BorderColour = Color4.White, - BorderThickness = border_thickness, - Alpha = 0.1f, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - new CircularContainer - { - Name = "Normal Hit Ring", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE), - Masking = true, - BorderColour = Color4.White, - BorderThickness = border_thickness, - Alpha = 0.5f, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - new Box - { - Name = "Bar Lower", - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f), - Alpha = 0.1f - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// A component that is displayed at the hit position in the taiko playfield. + /// + internal class HitTarget : Container + { + /// + /// Thickness of all drawn line pieces. + /// + private const float border_thickness = 2.5f; + + public HitTarget() + { + Children = new Drawable[] + { + new Box + { + Name = "Bar Upper", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f), + Alpha = 0.1f + }, + new CircularContainer + { + Name = "Strong Hit Ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), + Masking = true, + BorderColour = Color4.White, + BorderThickness = border_thickness, + Alpha = 0.1f, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + new CircularContainer + { + Name = "Normal Hit Ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE), + Masking = true, + BorderColour = Color4.White, + BorderThickness = border_thickness, + Alpha = 0.5f, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + new Box + { + Name = "Bar Lower", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f), + Alpha = 0.1f + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index b918f495fc..524535bfde 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -1,193 +1,193 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Audio; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// A component of the playfield that captures input and displays input as a drum. - /// - internal class InputDrum : Container - { - private const float middle_split = 0.025f; - - private readonly ControlPointInfo controlPoints; - - public InputDrum(ControlPointInfo controlPoints) - { - this.controlPoints = controlPoints; - - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; - } - - [BackgroundDependencyLoader] - private void load() - { - var sampleMappings = new DrumSampleMapping(controlPoints); - - Children = new Drawable[] - { - new TaikoHalfDrum(false, sampleMappings) - { - Name = "Left Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = -middle_split / 2, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - new TaikoHalfDrum(true, sampleMappings) - { - Name = "Right Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = middle_split / 2, - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre - } - }; - - AddRangeInternal(sampleMappings.Sounds); - } - - /// - /// A half-drum. Contains one centre and one rim hit. - /// - private class TaikoHalfDrum : Container, IKeyBindingHandler - { - /// - /// The key to be used for the rim of the half-drum. - /// - public TaikoAction RimAction; - - /// - /// The key to be used for the centre of the half-drum. - /// - public TaikoAction CentreAction; - - private readonly Sprite rim; - private readonly Sprite rimHit; - private readonly Sprite centre; - private readonly Sprite centreHit; - - private readonly DrumSampleMapping sampleMappings; - - public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings) - { - this.sampleMappings = sampleMappings; - - Masking = true; - - Children = new Drawable[] - { - rim = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both - }, - rimHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingMode.Additive, - }, - centre = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) - }, - centreHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), - Alpha = 0, - Blending = BlendingMode.Additive - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) - { - rim.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner-hit"); - - rimHit.Colour = colours.Blue; - centreHit.Colour = colours.Pink; - } - - public bool OnPressed(TaikoAction action) - { - Drawable target = null; - Drawable back = null; - - var drumSample = sampleMappings.SampleAt(Time.Current); - - if (action == CentreAction) - { - target = centreHit; - back = centre; - - drumSample.Centre?.Play(); - } - else if (action == RimAction) - { - target = rimHit; - back = rim; - - drumSample.Rim?.Play(); - } - - if (target != null) - { - const float scale_amount = 0.05f; - const float alpha_amount = 0.5f; - - const float down_time = 40; - const float up_time = 1000; - - back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) - .Then() - .ScaleTo(1, up_time, Easing.OutQuint); - - target.Animate( - t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) - ).Then( - t => t.ScaleTo(1, up_time, Easing.OutQuint), - t => t.FadeOut(up_time, Easing.OutQuint) - ); - } - - return false; - } - - public bool OnReleased(TaikoAction action) => false; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Rulesets.Taiko.Audio; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// A component of the playfield that captures input and displays input as a drum. + /// + internal class InputDrum : Container + { + private const float middle_split = 0.025f; + + private readonly ControlPointInfo controlPoints; + + public InputDrum(ControlPointInfo controlPoints) + { + this.controlPoints = controlPoints; + + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + } + + [BackgroundDependencyLoader] + private void load() + { + var sampleMappings = new DrumSampleMapping(controlPoints); + + Children = new Drawable[] + { + new TaikoHalfDrum(false, sampleMappings) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = -middle_split / 2, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new TaikoHalfDrum(true, sampleMappings) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = middle_split / 2, + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } + }; + + AddRangeInternal(sampleMappings.Sounds); + } + + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container, IKeyBindingHandler + { + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; + + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; + + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; + + private readonly DrumSampleMapping sampleMappings; + + public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings) + { + this.sampleMappings = sampleMappings; + + Masking = true; + + Children = new Drawable[] + { + rim = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingMode.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f), + Alpha = 0, + Blending = BlendingMode.Additive + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + rim.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner-hit"); + + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; + } + + public bool OnPressed(TaikoAction action) + { + Drawable target = null; + Drawable back = null; + + var drumSample = sampleMappings.SampleAt(Time.Current); + + if (action == CentreAction) + { + target = centreHit; + back = centre; + + drumSample.Centre?.Play(); + } + else if (action == RimAction) + { + target = rimHit; + back = rim; + + drumSample.Rim?.Play(); + } + + if (target != null) + { + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) + .Then() + .ScaleTo(1, up_time, Easing.OutQuint); + + target.Animate( + t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) + ).Then( + t => t.ScaleTo(1, up_time, Easing.OutQuint), + t => t.FadeOut(up_time, Easing.OutQuint) + ); + } + + return false; + } + + public bool OnReleased(TaikoAction action) => false; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index 0f5065f3ec..ac3cf8305a 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -1,68 +1,68 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.UI -{ - public class KiaiHitExplosion : CircularContainer - { - public readonly DrawableHitObject JudgedObject; - - private readonly bool isRim; - - public KiaiHitExplosion(DrawableHitObject judgedObject, bool isRim) - { - this.isRim = isRim; - - JudgedObject = judgedObject; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.Both; - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1); - - Masking = true; - Alpha = 0.25f; - - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = isRim ? colours.BlueDarker : colours.PinkDarker, - Radius = 60, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint); - this.FadeOut(250); - - Expire(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class KiaiHitExplosion : CircularContainer + { + public readonly DrawableHitObject JudgedObject; + + private readonly bool isRim; + + public KiaiHitExplosion(DrawableHitObject judgedObject, bool isRim) + { + this.isRim = isRim; + + JudgedObject = judgedObject; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1); + + Masking = true; + Alpha = 0.25f; + + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = isRim ? colours.BlueDarker : colours.PinkDarker, + Radius = 60, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint); + this.FadeOut(250); + + Expire(); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index effb9eb54f..417a7c2581 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -1,267 +1,267 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Taiko.Objects; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Rulesets.Taiko.Judgements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Extensions.Color4Extensions; -using System.Linq; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Taiko.Objects.Drawables; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Taiko.UI -{ - public class TaikoPlayfield : ScrollingPlayfield - { - /// - /// Default height of a when inside a . - /// - public const float DEFAULT_HEIGHT = 178; - - /// - /// The offset from which the center of the hit target lies at. - /// - public const float HIT_TARGET_OFFSET = 100; - - /// - /// The size of the left area of the playfield. This area contains the input drum. - /// - private const float left_area_size = 240; - - protected override bool UserScrollSpeedAdjustment => false; - - private readonly Container hitExplosionContainer; - private readonly Container kiaiExplosionContainer; - private readonly JudgementContainer judgementContainer; - - protected override Container Content => content; - private readonly Container content; - - private readonly Container topLevelHitContainer; - - private readonly Container barlineContainer; - - private readonly Container overlayBackgroundContainer; - private readonly Container backgroundContainer; - - private readonly Box overlayBackground; - private readonly Box background; - - public TaikoPlayfield(ControlPointInfo controlPoints) - : base(ScrollingDirection.Left) - { - AddRangeInternal(new Drawable[] - { - backgroundContainer = new Container - { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, - }, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, - new Container - { - Name = "Right area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] - { - new Container - { - Name = "Masked elements before hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Children = new Drawable[] - { - hitExplosionContainer = new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Blending = BlendingMode.Additive, - }, - new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - } - } - }, - barlineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } - }, - content = new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true - }, - kiaiExplosionContainer = new Container - { - Name = "Kiai hit explosions", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - judgementContainer = new JudgementContainer - { - Name = "Judgements", - RelativeSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - } - }, - overlayBackgroundContainer = new Container - { - Name = "Left overlay", - RelativeSizeAxes = Axes.Y, - Size = new Vector2(left_area_size, 1), - Children = new Drawable[] - { - overlayBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum(controlPoints) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Scale = new Vector2(0.9f), - Margin = new MarginPadding { Right = 20 } - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), - }, - } - }, - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 0, - BorderThickness = 2, - AlwaysPresent = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - topLevelHitContainer = new Container - { - Name = "Top level hit objects", - RelativeSizeAxes = Axes.Both, - } - }); - - VisibleTimeRange.Value = 6000; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - overlayBackgroundContainer.BorderColour = colours.Gray0; - overlayBackground.Colour = colours.Gray1; - - backgroundContainer.BorderColour = colours.Gray1; - background.Colour = colours.Gray0; - } - - public override void Add(DrawableHitObject h) - { - h.OnJudgement += OnJudgement; - - base.Add(h); - - var barline = h as DrawableBarLine; - if (barline != null) - barlineContainer.Add(barline.CreateProxy()); - - // Swells should be moved at the very top of the playfield when they reach the hit target - var swell = h as DrawableSwell; - if (swell != null) - swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy()); - } - - internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) - { - if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null) - { - judgementContainer.Add(new DrawableTaikoJudgement(judgement, judgedObject) - { - Anchor = judgement.IsHit ? Anchor.TopLeft : Anchor.CentreLeft, - Origin = judgement.IsHit ? Anchor.BottomCentre : Anchor.Centre, - RelativePositionAxes = Axes.X, - X = judgement.IsHit ? judgedObject.Position.X : 0, - }); - } - - if (!judgement.IsHit) - return; - - bool isRim = judgedObject.HitObject is RimHit; - - if (judgement is TaikoStrongHitJudgement) - hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit(); - else - { - if (judgedObject.X >= -0.05f && judgedObject is DrawableHit) - { - // If we're far enough away from the left stage, we should bring outselves in front of it - // Todo: The following try-catch is temporary for replay rewinding support - try - { - topLevelHitContainer.Add(judgedObject.CreateProxy()); - } - catch - { - } - } - - hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim)); - - if (judgedObject.HitObject.Kiai) - kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject, isRim)); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Taiko.Objects; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Extensions.Color4Extensions; +using System.Linq; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class TaikoPlayfield : ScrollingPlayfield + { + /// + /// Default height of a when inside a . + /// + public const float DEFAULT_HEIGHT = 178; + + /// + /// The offset from which the center of the hit target lies at. + /// + public const float HIT_TARGET_OFFSET = 100; + + /// + /// The size of the left area of the playfield. This area contains the input drum. + /// + private const float left_area_size = 240; + + protected override bool UserScrollSpeedAdjustment => false; + + private readonly Container hitExplosionContainer; + private readonly Container kiaiExplosionContainer; + private readonly JudgementContainer judgementContainer; + + protected override Container Content => content; + private readonly Container content; + + private readonly Container topLevelHitContainer; + + private readonly Container barlineContainer; + + private readonly Container overlayBackgroundContainer; + private readonly Container backgroundContainer; + + private readonly Box overlayBackground; + private readonly Box background; + + public TaikoPlayfield(ControlPointInfo controlPoints) + : base(ScrollingDirection.Left) + { + AddRangeInternal(new Drawable[] + { + backgroundContainer = new Container + { + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f + }, + } + }, + new Container + { + Name = "Right area", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, + Children = new Drawable[] + { + new Container + { + Name = "Masked elements before hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Blending = BlendingMode.Additive, + }, + new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + } + } + }, + barlineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } + }, + content = new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true + }, + kiaiExplosionContainer = new Container + { + Name = "Kiai hit explosions", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + judgementContainer = new JudgementContainer + { + Name = "Judgements", + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + } + }, + overlayBackgroundContainer = new Container + { + Name = "Left overlay", + RelativeSizeAxes = Axes.Y, + Size = new Vector2(left_area_size, 1), + Children = new Drawable[] + { + overlayBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum(controlPoints) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Scale = new Vector2(0.9f), + Margin = new MarginPadding { Right = 20 } + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } + }, + new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 0, + BorderThickness = 2, + AlwaysPresent = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + topLevelHitContainer = new Container + { + Name = "Top level hit objects", + RelativeSizeAxes = Axes.Both, + } + }); + + VisibleTimeRange.Value = 6000; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + overlayBackgroundContainer.BorderColour = colours.Gray0; + overlayBackground.Colour = colours.Gray1; + + backgroundContainer.BorderColour = colours.Gray1; + background.Colour = colours.Gray0; + } + + public override void Add(DrawableHitObject h) + { + h.OnJudgement += OnJudgement; + + base.Add(h); + + var barline = h as DrawableBarLine; + if (barline != null) + barlineContainer.Add(barline.CreateProxy()); + + // Swells should be moved at the very top of the playfield when they reach the hit target + var swell = h as DrawableSwell; + if (swell != null) + swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy()); + } + + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null) + { + judgementContainer.Add(new DrawableTaikoJudgement(judgement, judgedObject) + { + Anchor = judgement.IsHit ? Anchor.TopLeft : Anchor.CentreLeft, + Origin = judgement.IsHit ? Anchor.BottomCentre : Anchor.Centre, + RelativePositionAxes = Axes.X, + X = judgement.IsHit ? judgedObject.Position.X : 0, + }); + } + + if (!judgement.IsHit) + return; + + bool isRim = judgedObject.HitObject is RimHit; + + if (judgement is TaikoStrongHitJudgement) + hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit(); + else + { + if (judgedObject.X >= -0.05f && judgedObject is DrawableHit) + { + // If we're far enough away from the left stage, we should bring outselves in front of it + // Todo: The following try-catch is temporary for replay rewinding support + try + { + topLevelHitContainer.Add(judgedObject.CreateProxy()); + } + catch + { + } + } + + hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim)); + + if (judgedObject.HitObject.Kiai) + kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject, isRim)); + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index eb282c53ca..3d3c6ab2f3 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -1,139 +1,139 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Taiko.Replays; -using OpenTK; -using System.Linq; -using osu.Framework.Input; -using osu.Game.Input.Handlers; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Taiko.UI -{ - public class TaikoRulesetContainer : ScrollingRulesetContainer - { - public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(ruleset, beatmap, isForCurrentRuleset) - { - } - - [BackgroundDependencyLoader] - private void load() - { - loadBarLines(); - } - - private void loadBarLines() - { - TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; - double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; - - var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList(); - - if (timingPoints.Count == 0) - return; - - int currentIndex = 0; - int currentBeat = 0; - double time = timingPoints[currentIndex].Time; - while (time <= lastHitTime) - { - int nextIndex = currentIndex + 1; - if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) - { - currentIndex = nextIndex; - time = timingPoints[currentIndex].Time; - currentBeat = 0; - } - - var currentPoint = timingPoints[currentIndex]; - - var barLine = new BarLine - { - StartTime = time, - }; - - barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); - - bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; - Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); - - double bl = currentPoint.BeatLength; - if (bl < 800) - bl *= (int)currentPoint.TimeSignature; - - time += bl; - currentBeat++; - } - } - - protected override Vector2 GetAspectAdjustedSize() - { - const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; - const float default_aspect = 16f / 9f; - - float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect; - - return new Vector2(1, default_relative_height * aspectAdjust); - } - - protected override Vector2 PlayfieldArea => Vector2.One; - - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); - - protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset); - - public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); - - protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }; - - protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) - { - var centreHit = h as CentreHit; - if (centreHit != null) - { - if (h.IsStrong) - return new DrawableCentreHitStrong(centreHit); - return new DrawableCentreHit(centreHit); - } - - var rimHit = h as RimHit; - if (rimHit != null) - { - if (h.IsStrong) - return new DrawableRimHitStrong(rimHit); - return new DrawableRimHit(rimHit); - } - - var drumRoll = h as DrumRoll; - if (drumRoll != null) - { - return new DrawableDrumRoll(drumRoll); - } - - var swell = h as Swell; - if (swell != null) - return new DrawableSwell(swell); - - return null; - } - - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Taiko.Replays; +using OpenTK; +using System.Linq; +using osu.Framework.Input; +using osu.Game.Input.Handlers; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class TaikoRulesetContainer : ScrollingRulesetContainer + { + public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + [BackgroundDependencyLoader] + private void load() + { + loadBarLines(); + } + + private void loadBarLines() + { + TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; + double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; + + var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList(); + + if (timingPoints.Count == 0) + return; + + int currentIndex = 0; + int currentBeat = 0; + double time = timingPoints[currentIndex].Time; + while (time <= lastHitTime) + { + int nextIndex = currentIndex + 1; + if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) + { + currentIndex = nextIndex; + time = timingPoints[currentIndex].Time; + currentBeat = 0; + } + + var currentPoint = timingPoints[currentIndex]; + + var barLine = new BarLine + { + StartTime = time, + }; + + barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); + + bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; + Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); + + double bl = currentPoint.BeatLength; + if (bl < 800) + bl *= (int)currentPoint.TimeSignature; + + time += bl; + currentBeat++; + } + } + + protected override Vector2 GetAspectAdjustedSize() + { + const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; + const float default_aspect = 16f / 9f; + + float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect; + + return new Vector2(1, default_relative_height * aspectAdjust); + } + + protected override Vector2 PlayfieldArea => Vector2.One; + + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + + protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset); + + public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); + + protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }; + + protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + { + var centreHit = h as CentreHit; + if (centreHit != null) + { + if (h.IsStrong) + return new DrawableCentreHitStrong(centreHit); + return new DrawableCentreHit(centreHit); + } + + var rimHit = h as RimHit; + if (rimHit != null) + { + if (h.IsStrong) + return new DrawableRimHitStrong(rimHit); + return new DrawableRimHit(rimHit); + } + + var drumRoll = h as DrumRoll; + if (drumRoll != null) + { + return new DrawableDrumRoll(drumRoll); + } + + var swell = h as Swell; + if (swell != null) + return new DrawableSwell(swell); + + return null; + } + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index bc878b599b..4985aa9365 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1,215 +1,215 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.IO; -using NUnit.Framework; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Tests.Resources; -using System.Linq; -using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.Timing; -using osu.Game.Skinning; - -namespace osu.Game.Tests.Beatmaps.Formats -{ - [TestFixture] - public class LegacyBeatmapDecoderTest - { - [Test] - public void TestDecodeBeatmapGeneral() - { - var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var beatmap = decoder.Decode(stream); - var beatmapInfo = beatmap.BeatmapInfo; - var metadata = beatmap.Metadata; - - Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); - Assert.AreEqual(0, beatmapInfo.AudioLeadIn); - Assert.AreEqual(164471, metadata.PreviewTime); - Assert.IsFalse(beatmapInfo.Countdown); - Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); - Assert.IsTrue(beatmapInfo.RulesetID == 0); - Assert.IsFalse(beatmapInfo.LetterboxInBreaks); - Assert.IsFalse(beatmapInfo.SpecialStyle); - Assert.IsFalse(beatmapInfo.WidescreenStoryboard); - } - } - - [Test] - public void TestDecodeBeatmapEditor() - { - var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var beatmapInfo = decoder.Decode(stream).BeatmapInfo; - - int[] expectedBookmarks = - { - 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, - 95901, 106450, 116999, 119637, 130186, 140735, 151285, - 161834, 164471, 175020, 185570, 196119, 206669, 209306 - }; - Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); - for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); - Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); - Assert.AreEqual(4, beatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmapInfo.GridSize); - Assert.AreEqual(2, beatmapInfo.TimelineZoom); - } - } - - [Test] - public void TestDecodeBeatmapMetadata() - { - var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var beatmap = decoder.Decode(stream); - var beatmapInfo = beatmap.BeatmapInfo; - var metadata = beatmap.Metadata; - - Assert.AreEqual("Renatus", metadata.Title); - Assert.AreEqual("Renatus", metadata.TitleUnicode); - Assert.AreEqual("Soleily", metadata.Artist); - Assert.AreEqual("Soleily", metadata.ArtistUnicode); - Assert.AreEqual("Gamu", metadata.AuthorString); - Assert.AreEqual("Insane", beatmapInfo.Version); - Assert.AreEqual(string.Empty, metadata.Source); - Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); - Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); - Assert.AreEqual(241526, metadata.OnlineBeatmapSetID); - } - } - - [Test] - public void TestDecodeBeatmapDifficulty() - { - var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty; - - Assert.AreEqual(6.5f, difficulty.DrainRate); - Assert.AreEqual(4, difficulty.CircleSize); - Assert.AreEqual(8, difficulty.OverallDifficulty); - Assert.AreEqual(9, difficulty.ApproachRate); - Assert.AreEqual(1.8, difficulty.SliderMultiplier); - Assert.AreEqual(2, difficulty.SliderTickRate); - } - } - - [Test] - public void TestDecodeBeatmapEvents() - { - var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var beatmap = decoder.Decode(stream); - var metadata = beatmap.Metadata; - var breakPoint = beatmap.Breaks[0]; - - Assert.AreEqual("machinetop_background.jpg", metadata.BackgroundFile); - Assert.AreEqual(122474, breakPoint.StartTime); - Assert.AreEqual(140135, breakPoint.EndTime); - Assert.IsTrue(breakPoint.HasEffect); - } - } - - [Test] - public void TestDecodeBeatmapTimingPoints() - { - var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var beatmap = decoder.Decode(stream); - var controlPoints = beatmap.ControlPointInfo; - - Assert.AreEqual(4, controlPoints.TimingPoints.Count); - var timingPoint = controlPoints.TimingPoints[0]; - Assert.AreEqual(956, timingPoint.Time); - Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); - - Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); - var difficultyPoint = controlPoints.DifficultyPoints[0]; - Assert.AreEqual(116999, difficultyPoint.Time); - Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier); - - Assert.AreEqual(34, controlPoints.SamplePoints.Count); - var soundPoint = controlPoints.SamplePoints[0]; - Assert.AreEqual(956, soundPoint.Time); - Assert.AreEqual("soft", soundPoint.SampleBank); - Assert.AreEqual(60, soundPoint.SampleVolume); - - Assert.AreEqual(8, controlPoints.EffectPoints.Count); - var effectPoint = controlPoints.EffectPoints[0]; - Assert.AreEqual(53703, effectPoint.Time); - Assert.IsTrue(effectPoint.KiaiMode); - Assert.IsFalse(effectPoint.OmitFirstBarLine); - } - } - - [Test] - public void TestDecodeBeatmapColors() - { - var decoder = new LegacySkinDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var comboColors = decoder.Decode(stream).ComboColours; - - Color4[] expectedColors = - { - new Color4(142, 199, 255, 255), - new Color4(255, 128, 128, 255), - new Color4(128, 255, 255, 255), - new Color4(128, 255, 128, 255), - new Color4(255, 187, 255, 255), - new Color4(255, 177, 140, 255), - }; - Assert.AreEqual(expectedColors.Length, comboColors.Count); - for (int i = 0; i < expectedColors.Length; i++) - Assert.AreEqual(expectedColors[i], comboColors[i]); - } - } - - [Test] - public void TestDecodeBeatmapHitObjects() - { - var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) - using (var stream = new StreamReader(resStream)) - { - var hitObjects = decoder.Decode(stream).HitObjects; - - var curveData = hitObjects[0] as IHasCurve; - var positionData = hitObjects[0] as IHasPosition; - - Assert.IsNotNull(positionData); - Assert.IsNotNull(curveData); - Assert.AreEqual(new Vector2(192, 168), positionData.Position); - Assert.AreEqual(956, hitObjects[0].StartTime); - Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); - - positionData = hitObjects[1] as IHasPosition; - - Assert.IsNotNull(positionData); - Assert.AreEqual(new Vector2(304, 56), positionData.Position); - Assert.AreEqual(1285, hitObjects[1].StartTime); - Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using NUnit.Framework; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Tests.Resources; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Timing; +using osu.Game.Skinning; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyBeatmapDecoderTest + { + [Test] + public void TestDecodeBeatmapGeneral() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.Decode(stream); + var beatmapInfo = beatmap.BeatmapInfo; + var metadata = beatmap.Metadata; + + Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); + Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(164471, metadata.PreviewTime); + Assert.IsFalse(beatmapInfo.Countdown); + Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsFalse(beatmapInfo.LetterboxInBreaks); + Assert.IsFalse(beatmapInfo.SpecialStyle); + Assert.IsFalse(beatmapInfo.WidescreenStoryboard); + } + } + + [Test] + public void TestDecodeBeatmapEditor() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmapInfo = decoder.Decode(stream).BeatmapInfo; + + int[] expectedBookmarks = + { + 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, + 95901, 106450, 116999, 119637, 130186, 140735, 151285, + 161834, 164471, 175020, 185570, 196119, 206669, 209306 + }; + Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + for (int i = 0; i < expectedBookmarks.Length; i++) + Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); + Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); + Assert.AreEqual(4, beatmapInfo.BeatDivisor); + Assert.AreEqual(4, beatmapInfo.GridSize); + Assert.AreEqual(2, beatmapInfo.TimelineZoom); + } + } + + [Test] + public void TestDecodeBeatmapMetadata() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.Decode(stream); + var beatmapInfo = beatmap.BeatmapInfo; + var metadata = beatmap.Metadata; + + Assert.AreEqual("Renatus", metadata.Title); + Assert.AreEqual("Renatus", metadata.TitleUnicode); + Assert.AreEqual("Soleily", metadata.Artist); + Assert.AreEqual("Soleily", metadata.ArtistUnicode); + Assert.AreEqual("Gamu", metadata.AuthorString); + Assert.AreEqual("Insane", beatmapInfo.Version); + Assert.AreEqual(string.Empty, metadata.Source); + Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); + Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); + Assert.AreEqual(241526, metadata.OnlineBeatmapSetID); + } + } + + [Test] + public void TestDecodeBeatmapDifficulty() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty; + + Assert.AreEqual(6.5f, difficulty.DrainRate); + Assert.AreEqual(4, difficulty.CircleSize); + Assert.AreEqual(8, difficulty.OverallDifficulty); + Assert.AreEqual(9, difficulty.ApproachRate); + Assert.AreEqual(1.8, difficulty.SliderMultiplier); + Assert.AreEqual(2, difficulty.SliderTickRate); + } + } + + [Test] + public void TestDecodeBeatmapEvents() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.Decode(stream); + var metadata = beatmap.Metadata; + var breakPoint = beatmap.Breaks[0]; + + Assert.AreEqual("machinetop_background.jpg", metadata.BackgroundFile); + Assert.AreEqual(122474, breakPoint.StartTime); + Assert.AreEqual(140135, breakPoint.EndTime); + Assert.IsTrue(breakPoint.HasEffect); + } + } + + [Test] + public void TestDecodeBeatmapTimingPoints() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.Decode(stream); + var controlPoints = beatmap.ControlPointInfo; + + Assert.AreEqual(4, controlPoints.TimingPoints.Count); + var timingPoint = controlPoints.TimingPoints[0]; + Assert.AreEqual(956, timingPoint.Time); + Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); + Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + var difficultyPoint = controlPoints.DifficultyPoints[0]; + Assert.AreEqual(116999, difficultyPoint.Time); + Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier); + + Assert.AreEqual(34, controlPoints.SamplePoints.Count); + var soundPoint = controlPoints.SamplePoints[0]; + Assert.AreEqual(956, soundPoint.Time); + Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(60, soundPoint.SampleVolume); + + Assert.AreEqual(8, controlPoints.EffectPoints.Count); + var effectPoint = controlPoints.EffectPoints[0]; + Assert.AreEqual(53703, effectPoint.Time); + Assert.IsTrue(effectPoint.KiaiMode); + Assert.IsFalse(effectPoint.OmitFirstBarLine); + } + } + + [Test] + public void TestDecodeBeatmapColors() + { + var decoder = new LegacySkinDecoder(); + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var comboColors = decoder.Decode(stream).ComboColours; + + Color4[] expectedColors = + { + new Color4(142, 199, 255, 255), + new Color4(255, 128, 128, 255), + new Color4(128, 255, 255, 255), + new Color4(128, 255, 128, 255), + new Color4(255, 187, 255, 255), + new Color4(255, 177, 140, 255), + }; + Assert.AreEqual(expectedColors.Length, comboColors.Count); + for (int i = 0; i < expectedColors.Length; i++) + Assert.AreEqual(expectedColors[i], comboColors[i]); + } + } + + [Test] + public void TestDecodeBeatmapHitObjects() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + var curveData = hitObjects[0] as IHasCurve; + var positionData = hitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.IsNotNull(curveData); + Assert.AreEqual(new Vector2(192, 168), positionData.Position); + Assert.AreEqual(956, hitObjects[0].StartTime); + Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + + positionData = hitObjects[1] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(304, 56), positionData.Position); + Assert.AreEqual(1285, hitObjects[1].StartTime); + Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + } + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 1c0801c634..3431be91f9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -1,90 +1,90 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.IO; -using System.Linq; -using NUnit.Framework; -using OpenTK; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.Formats; -using osu.Game.Storyboards; -using osu.Game.Tests.Resources; - -namespace osu.Game.Tests.Beatmaps.Formats -{ - [TestFixture] - public class LegacyStoryboardDecoderTest - { - [Test] - public void TestDecodeStoryboardEvents() - { - var decoder = new LegacyStoryboardDecoder(); - using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) - using (var stream = new StreamReader(resStream)) - { - var storyboard = decoder.Decode(stream); - - Assert.IsTrue(storyboard.HasDrawable); - Assert.AreEqual(4, storyboard.Layers.Count()); - - StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); - Assert.IsNotNull(background); - Assert.AreEqual(16, background.Elements.Count()); - Assert.IsTrue(background.EnabledWhenFailing); - Assert.IsTrue(background.EnabledWhenPassing); - Assert.AreEqual("Background", background.Name); - - StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); - Assert.IsNotNull(fail); - Assert.AreEqual(0, fail.Elements.Count()); - Assert.IsTrue(fail.EnabledWhenFailing); - Assert.IsFalse(fail.EnabledWhenPassing); - Assert.AreEqual("Fail", fail.Name); - - StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); - Assert.IsNotNull(pass); - Assert.AreEqual(0, pass.Elements.Count()); - Assert.IsFalse(pass.EnabledWhenFailing); - Assert.IsTrue(pass.EnabledWhenPassing); - Assert.AreEqual("Pass", pass.Name); - - StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); - Assert.IsNotNull(foreground); - Assert.AreEqual(151, foreground.Elements.Count()); - Assert.IsTrue(foreground.EnabledWhenFailing); - Assert.IsTrue(foreground.EnabledWhenPassing); - Assert.AreEqual("Foreground", foreground.Name); - - int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); - int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation)); - int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample)); - - Assert.AreEqual(15, spriteCount); - Assert.AreEqual(1, animationCount); - Assert.AreEqual(0, sampleCount); - Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount); - - var sprite = background.Elements.ElementAt(0) as StoryboardSprite; - Assert.NotNull(sprite); - Assert.IsTrue(sprite.HasCommands); - Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); - Assert.IsTrue(sprite.IsDrawable); - Assert.AreEqual(Anchor.Centre, sprite.Origin); - Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); - - var animation = background.Elements.ElementAt(12) as StoryboardAnimation; - Assert.NotNull(animation); - Assert.AreEqual(141175, animation.EndTime); - Assert.AreEqual(10, animation.FrameCount); - Assert.AreEqual(30, animation.FrameDelay); - Assert.IsTrue(animation.HasCommands); - Assert.AreEqual(new Vector2(320, 240), animation.InitialPosition); - Assert.IsTrue(animation.IsDrawable); - Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType); - Assert.AreEqual(Anchor.Centre, animation.Origin); - Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path); - Assert.AreEqual(78993, animation.StartTime); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using System.Linq; +using NUnit.Framework; +using OpenTK; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.Formats; +using osu.Game.Storyboards; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyStoryboardDecoderTest + { + [Test] + public void TestDecodeStoryboardEvents() + { + var decoder = new LegacyStoryboardDecoder(); + using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) + using (var stream = new StreamReader(resStream)) + { + var storyboard = decoder.Decode(stream); + + Assert.IsTrue(storyboard.HasDrawable); + Assert.AreEqual(4, storyboard.Layers.Count()); + + StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); + Assert.IsNotNull(background); + Assert.AreEqual(16, background.Elements.Count()); + Assert.IsTrue(background.EnabledWhenFailing); + Assert.IsTrue(background.EnabledWhenPassing); + Assert.AreEqual("Background", background.Name); + + StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); + Assert.IsNotNull(fail); + Assert.AreEqual(0, fail.Elements.Count()); + Assert.IsTrue(fail.EnabledWhenFailing); + Assert.IsFalse(fail.EnabledWhenPassing); + Assert.AreEqual("Fail", fail.Name); + + StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); + Assert.IsNotNull(pass); + Assert.AreEqual(0, pass.Elements.Count()); + Assert.IsFalse(pass.EnabledWhenFailing); + Assert.IsTrue(pass.EnabledWhenPassing); + Assert.AreEqual("Pass", pass.Name); + + StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); + Assert.IsNotNull(foreground); + Assert.AreEqual(151, foreground.Elements.Count()); + Assert.IsTrue(foreground.EnabledWhenFailing); + Assert.IsTrue(foreground.EnabledWhenPassing); + Assert.AreEqual("Foreground", foreground.Name); + + int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); + int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation)); + int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample)); + + Assert.AreEqual(15, spriteCount); + Assert.AreEqual(1, animationCount); + Assert.AreEqual(0, sampleCount); + Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount); + + var sprite = background.Elements.ElementAt(0) as StoryboardSprite; + Assert.NotNull(sprite); + Assert.IsTrue(sprite.HasCommands); + Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); + Assert.IsTrue(sprite.IsDrawable); + Assert.AreEqual(Anchor.Centre, sprite.Origin); + Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); + + var animation = background.Elements.ElementAt(12) as StoryboardAnimation; + Assert.NotNull(animation); + Assert.AreEqual(141175, animation.EndTime); + Assert.AreEqual(10, animation.FrameCount); + Assert.AreEqual(30, animation.FrameDelay); + Assert.IsTrue(animation.HasCommands); + Assert.AreEqual(new Vector2(320, 240), animation.InitialPosition); + Assert.IsTrue(animation.IsDrawable); + Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType); + Assert.AreEqual(Anchor.Centre, animation.Origin); + Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path); + Assert.AreEqual(78993, animation.StartTime); + } + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index f37672b5cc..9c9589f398 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -1,157 +1,157 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.IO; -using System.Linq; -using DeepEqual.Syntax; -using NUnit.Framework; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Formats; -using osu.Game.IO.Serialization; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Tests.Resources; -using OpenTK; - -namespace osu.Game.Tests.Beatmaps.Formats -{ - [TestFixture] - public class OsuJsonDecoderTest - { - private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; - private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; - private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; - - [Test] - public void TestDecodeMetadata() - { - var beatmap = decodeAsJson(normal); - var meta = beatmap.BeatmapInfo.Metadata; - Assert.AreEqual(241526, meta.OnlineBeatmapSetID); - Assert.AreEqual("Soleily", meta.Artist); - Assert.AreEqual("Soleily", meta.ArtistUnicode); - Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); - Assert.AreEqual("Gamu", meta.AuthorString); - Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); - Assert.AreEqual(164471, meta.PreviewTime); - Assert.AreEqual(string.Empty, meta.Source); - Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); - Assert.AreEqual("Renatus", meta.Title); - Assert.AreEqual("Renatus", meta.TitleUnicode); - } - - [Test] - public void TestDecodeGeneral() - { - var beatmap = decodeAsJson(normal); - var beatmapInfo = beatmap.BeatmapInfo; - Assert.AreEqual(0, beatmapInfo.AudioLeadIn); - Assert.AreEqual(false, beatmapInfo.Countdown); - Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); - Assert.AreEqual(false, beatmapInfo.SpecialStyle); - Assert.IsTrue(beatmapInfo.RulesetID == 0); - Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); - Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); - } - - [Test] - public void TestDecodeEditor() - { - var beatmap = decodeAsJson(normal); - var beatmapInfo = beatmap.BeatmapInfo; - - int[] expectedBookmarks = - { - 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, - 95901, 106450, 116999, 119637, 130186, 140735, 151285, - 161834, 164471, 175020, 185570, 196119, 206669, 209306 - }; - Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); - for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); - Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); - Assert.AreEqual(4, beatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmapInfo.GridSize); - Assert.AreEqual(2, beatmapInfo.TimelineZoom); - } - - [Test] - public void TestDecodeDifficulty() - { - var beatmap = decodeAsJson(normal); - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; - Assert.AreEqual(6.5f, difficulty.DrainRate); - Assert.AreEqual(4, difficulty.CircleSize); - Assert.AreEqual(8, difficulty.OverallDifficulty); - Assert.AreEqual(9, difficulty.ApproachRate); - Assert.AreEqual(1.8, difficulty.SliderMultiplier); - Assert.AreEqual(2, difficulty.SliderTickRate); - } - - [Test] - public void TestDecodeHitObjects() - { - var beatmap = decodeAsJson(normal); - - var curveData = beatmap.HitObjects[0] as IHasCurve; - var positionData = beatmap.HitObjects[0] as IHasPosition; - - Assert.IsNotNull(positionData); - Assert.IsNotNull(curveData); - Assert.AreEqual(new Vector2(192, 168), positionData.Position); - Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); - Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); - - positionData = beatmap.HitObjects[1] as IHasPosition; - - Assert.IsNotNull(positionData); - Assert.AreEqual(new Vector2(304, 56), positionData.Position); - Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); - Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); - } - - [TestCase(normal)] - [TestCase(marathon)] - // Currently fails: - // [TestCase(with_sb)] - public void TestParity(string beatmap) - { - var beatmaps = decode(beatmap); - beatmaps.jsonDecoded.ShouldDeepEqual(beatmaps.legacyDecoded); - } - - /// - /// Reads a .osu file first with a , serializes the resulting to JSON - /// and then deserializes the result back into a through an . - /// - /// The .osu file to decode. - /// The after being decoded by an . - private Beatmap decodeAsJson(string filename) => decode(filename).jsonDecoded; - - /// - /// Reads a .osu file first with a , serializes the resulting to JSON - /// and then deserializes the result back into a through an . - /// - /// The .osu file to decode. - /// The after being decoded by an . - private (Beatmap legacyDecoded, Beatmap jsonDecoded) decode(string filename) - { - using (var stream = Resource.OpenResource(filename)) - using (var sr = new StreamReader(stream)) - { - - var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); - using (var ms = new MemoryStream()) - using (var sw = new StreamWriter(ms)) - using (var sr2 = new StreamReader(ms)) - { - sw.Write(legacyDecoded.Serialize()); - sw.Flush(); - - ms.Position = 0; - return (legacyDecoded, new JsonBeatmapDecoder().Decode(sr2)); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using System.Linq; +using DeepEqual.Syntax; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO.Serialization; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Resources; +using OpenTK; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class OsuJsonDecoderTest + { + private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; + private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; + private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; + + [Test] + public void TestDecodeMetadata() + { + var beatmap = decodeAsJson(normal); + var meta = beatmap.BeatmapInfo.Metadata; + Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + Assert.AreEqual("Soleily", meta.Artist); + Assert.AreEqual("Soleily", meta.ArtistUnicode); + Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); + Assert.AreEqual("Gamu", meta.AuthorString); + Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); + Assert.AreEqual(164471, meta.PreviewTime); + Assert.AreEqual(string.Empty, meta.Source); + Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); + Assert.AreEqual("Renatus", meta.Title); + Assert.AreEqual("Renatus", meta.TitleUnicode); + } + + [Test] + public void TestDecodeGeneral() + { + var beatmap = decodeAsJson(normal); + var beatmapInfo = beatmap.BeatmapInfo; + Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(false, beatmapInfo.Countdown); + Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.AreEqual(false, beatmapInfo.SpecialStyle); + Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); + Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); + } + + [Test] + public void TestDecodeEditor() + { + var beatmap = decodeAsJson(normal); + var beatmapInfo = beatmap.BeatmapInfo; + + int[] expectedBookmarks = + { + 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, + 95901, 106450, 116999, 119637, 130186, 140735, 151285, + 161834, 164471, 175020, 185570, 196119, 206669, 209306 + }; + Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + for (int i = 0; i < expectedBookmarks.Length; i++) + Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); + Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); + Assert.AreEqual(4, beatmapInfo.BeatDivisor); + Assert.AreEqual(4, beatmapInfo.GridSize); + Assert.AreEqual(2, beatmapInfo.TimelineZoom); + } + + [Test] + public void TestDecodeDifficulty() + { + var beatmap = decodeAsJson(normal); + var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + Assert.AreEqual(6.5f, difficulty.DrainRate); + Assert.AreEqual(4, difficulty.CircleSize); + Assert.AreEqual(8, difficulty.OverallDifficulty); + Assert.AreEqual(9, difficulty.ApproachRate); + Assert.AreEqual(1.8, difficulty.SliderMultiplier); + Assert.AreEqual(2, difficulty.SliderTickRate); + } + + [Test] + public void TestDecodeHitObjects() + { + var beatmap = decodeAsJson(normal); + + var curveData = beatmap.HitObjects[0] as IHasCurve; + var positionData = beatmap.HitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.IsNotNull(curveData); + Assert.AreEqual(new Vector2(192, 168), positionData.Position); + Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); + Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + + positionData = beatmap.HitObjects[1] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(304, 56), positionData.Position); + Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); + Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + } + + [TestCase(normal)] + [TestCase(marathon)] + // Currently fails: + // [TestCase(with_sb)] + public void TestParity(string beatmap) + { + var beatmaps = decode(beatmap); + beatmaps.jsonDecoded.ShouldDeepEqual(beatmaps.legacyDecoded); + } + + /// + /// Reads a .osu file first with a , serializes the resulting to JSON + /// and then deserializes the result back into a through an . + /// + /// The .osu file to decode. + /// The after being decoded by an . + private Beatmap decodeAsJson(string filename) => decode(filename).jsonDecoded; + + /// + /// Reads a .osu file first with a , serializes the resulting to JSON + /// and then deserializes the result back into a through an . + /// + /// The .osu file to decode. + /// The after being decoded by an . + private (Beatmap legacyDecoded, Beatmap jsonDecoded) decode(string filename) + { + using (var stream = Resource.OpenResource(filename)) + using (var sr = new StreamReader(stream)) + { + + var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms)) + using (var sr2 = new StreamReader(ms)) + { + sw.Write(legacyDecoded.Serialize()); + sw.Flush(); + + ms.Position = 0; + return (legacyDecoded, new JsonBeatmapDecoder().Decode(sr2)); + } + } + } + } +} diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 4f5eae85c8..6453cdbd3e 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1,297 +1,297 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; -using osu.Framework.Platform; -using osu.Game.IPC; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; - -namespace osu.Game.Tests.Beatmaps.IO -{ - [TestFixture] - public class ImportBeatmapTest - { - private const string osz_path = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; - - [Test] - public void TestImportWhenClosed() - { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed")) - { - try - { - loadOszIntoOsu(loadOsu(host)); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestImportThenDelete() - { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete")) - { - try - { - var osu = loadOsu(host); - - var imported = loadOszIntoOsu(osu); - - deleteBeatmapSet(imported, osu); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestImportThenImport() - { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) - { - try - { - var osu = loadOsu(host); - - var imported = loadOszIntoOsu(osu); - var importedSecondTime = loadOszIntoOsu(osu); - - // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. - Assert.IsTrue(imported.ID == importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - - var manager = osu.Dependencies.Get(); - - Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestImportThenImportDifferentHash() - { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash")) - { - try - { - var osu = loadOsu(host); - var manager = osu.Dependencies.Get(); - - var imported = loadOszIntoOsu(osu); - - //var change = manager.QueryBeatmapSets(_ => true).First(); - imported.Hash += "-changed"; - manager.Update(imported); - - var importedSecondTime = loadOszIntoOsu(osu); - - // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); - - Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestImportThenDeleteThenImport() - { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport")) - { - try - { - var osu = loadOsu(host); - - var imported = loadOszIntoOsu(osu); - - deleteBeatmapSet(imported, osu); - - var importedSecondTime = loadOszIntoOsu(osu); - - // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. - Assert.IsTrue(imported.ID == importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - } - finally - { - host.Exit(); - } - } - } - - [Test] - [NonParallelizable] - [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] - public void TestImportOverIPC() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("host", true)) - using (HeadlessGameHost client = new CleanRunHeadlessGameHost("client", true)) - { - try - { - Assert.IsTrue(host.IsPrimaryInstance); - Assert.IsFalse(client.IsPrimaryInstance); - - var osu = loadOsu(host); - - var temp = prepareTempCopy(osz_path); - Assert.IsTrue(File.Exists(temp)); - - var importer = new ArchiveImportIPCChannel(client); - if (!importer.ImportAsync(temp).Wait(10000)) - Assert.Fail(@"IPC took too long to send"); - - ensureLoaded(osu); - - waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); - } - finally - { - host.Exit(); - } - } - } - - [Test] - public void TestImportWhenFileOpen() - { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen")) - { - try - { - var osu = loadOsu(host); - var temp = prepareTempCopy(osz_path); - Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated"); - using (File.OpenRead(temp)) - osu.Dependencies.Get().Import(temp); - ensureLoaded(osu); - File.Delete(temp); - Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); - } - finally - { - host.Exit(); - } - } - } - - private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu) - { - var temp = prepareTempCopy(osz_path); - - Assert.IsTrue(File.Exists(temp)); - - var manager = osu.Dependencies.Get(); - - manager.Import(temp); - - var imported = manager.GetAllUsableBeatmapSets(); - - ensureLoaded(osu); - - waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - - return imported.FirstOrDefault(); - } - - private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) - { - var manager = osu.Dependencies.Get(); - manager.Delete(imported); - - Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0); - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); - } - - private string prepareTempCopy(string path) - { - var temp = Path.GetTempFileName(); - return new FileInfo(path).CopyTo(temp, true).FullName; - } - - private OsuGameBase loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - Task.Run(() => host.Run(osu)); - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - return osu; - } - - private void ensureLoaded(OsuGameBase osu, int timeout = 60000) - { - IEnumerable resultSets = null; - var store = osu.Dependencies.Get(); - waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), - @"BeatmapSet did not import to the database in allocated time.", timeout); - - //ensure we were stored to beatmap database backing... - Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); - IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); - IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526); - - //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. - waitForOrAssert(() => queryBeatmaps().Count() == 12, - @"Beatmaps did not import to the database in allocated time", timeout); - waitForOrAssert(() => queryBeatmapSets().Count() == 1, - @"BeatmapSet did not import to the database in allocated time", timeout); - int countBeatmapSetBeatmaps = 0; - int countBeatmaps = 0; - waitForOrAssert(() => - (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) == - (countBeatmaps = queryBeatmaps().Count()), - $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout); - - var set = queryBeatmapSets().First(); - foreach (BeatmapInfo b in set.Beatmaps) - Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); - Assert.IsTrue(set.Beatmaps.Count > 0); - var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Count > 0); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Count > 0); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Count > 0); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; - Assert.IsTrue(beatmap?.HitObjects.Count > 0); - } - - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Action waitAction = () => - { - while (!result()) Thread.Sleep(200); - }; - - Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Platform; +using osu.Game.IPC; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; + +namespace osu.Game.Tests.Beatmaps.IO +{ + [TestFixture] + public class ImportBeatmapTest + { + private const string osz_path = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; + + [Test] + public void TestImportWhenClosed() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed")) + { + try + { + loadOszIntoOsu(loadOsu(host)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportThenDelete() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete")) + { + try + { + var osu = loadOsu(host); + + var imported = loadOszIntoOsu(osu); + + deleteBeatmapSet(imported, osu); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportThenImport() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) + { + try + { + var osu = loadOsu(host); + + var imported = loadOszIntoOsu(osu); + var importedSecondTime = loadOszIntoOsu(osu); + + // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. + Assert.IsTrue(imported.ID == importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + + var manager = osu.Dependencies.Get(); + + Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); + Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportThenImportDifferentHash() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash")) + { + try + { + var osu = loadOsu(host); + var manager = osu.Dependencies.Get(); + + var imported = loadOszIntoOsu(osu); + + //var change = manager.QueryBeatmapSets(_ => true).First(); + imported.Hash += "-changed"; + manager.Update(imported); + + var importedSecondTime = loadOszIntoOsu(osu); + + // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); + + Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); + Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportThenDeleteThenImport() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport")) + { + try + { + var osu = loadOsu(host); + + var imported = loadOszIntoOsu(osu); + + deleteBeatmapSet(imported, osu); + + var importedSecondTime = loadOszIntoOsu(osu); + + // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. + Assert.IsTrue(imported.ID == importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + [NonParallelizable] + [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] + public void TestImportOverIPC() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("host", true)) + using (HeadlessGameHost client = new CleanRunHeadlessGameHost("client", true)) + { + try + { + Assert.IsTrue(host.IsPrimaryInstance); + Assert.IsFalse(client.IsPrimaryInstance); + + var osu = loadOsu(host); + + var temp = prepareTempCopy(osz_path); + Assert.IsTrue(File.Exists(temp)); + + var importer = new ArchiveImportIPCChannel(client); + if (!importer.ImportAsync(temp).Wait(10000)) + Assert.Fail(@"IPC took too long to send"); + + ensureLoaded(osu); + + waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportWhenFileOpen() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen")) + { + try + { + var osu = loadOsu(host); + var temp = prepareTempCopy(osz_path); + Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated"); + using (File.OpenRead(temp)) + osu.Dependencies.Get().Import(temp); + ensureLoaded(osu); + File.Delete(temp); + Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); + } + finally + { + host.Exit(); + } + } + } + + private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu) + { + var temp = prepareTempCopy(osz_path); + + Assert.IsTrue(File.Exists(temp)); + + var manager = osu.Dependencies.Get(); + + manager.Import(temp); + + var imported = manager.GetAllUsableBeatmapSets(); + + ensureLoaded(osu); + + waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); + + return imported.FirstOrDefault(); + } + + private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) + { + var manager = osu.Dependencies.Get(); + manager.Delete(imported); + + Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0); + Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); + Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); + } + + private string prepareTempCopy(string path) + { + var temp = Path.GetTempFileName(); + return new FileInfo(path).CopyTo(temp, true).FullName; + } + + private OsuGameBase loadOsu(GameHost host) + { + var osu = new OsuGameBase(); + Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + return osu; + } + + private void ensureLoaded(OsuGameBase osu, int timeout = 60000) + { + IEnumerable resultSets = null; + var store = osu.Dependencies.Get(); + waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), + @"BeatmapSet did not import to the database in allocated time.", timeout); + + //ensure we were stored to beatmap database backing... + Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); + IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); + IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526); + + //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. + waitForOrAssert(() => queryBeatmaps().Count() == 12, + @"Beatmaps did not import to the database in allocated time", timeout); + waitForOrAssert(() => queryBeatmapSets().Count() == 1, + @"BeatmapSet did not import to the database in allocated time", timeout); + int countBeatmapSetBeatmaps = 0; + int countBeatmaps = 0; + waitForOrAssert(() => + (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) == + (countBeatmaps = queryBeatmaps().Count()), + $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout); + + var set = queryBeatmapSets().First(); + foreach (BeatmapInfo b in set.Beatmaps) + Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); + Assert.IsTrue(set.Beatmaps.Count > 0); + var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; + Assert.IsTrue(beatmap?.HitObjects.Count > 0); + beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap; + Assert.IsTrue(beatmap?.HitObjects.Count > 0); + beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap; + Assert.IsTrue(beatmap?.HitObjects.Count > 0); + beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; + Assert.IsTrue(beatmap?.HitObjects.Count > 0); + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Action waitAction = () => + { + while (!result()) Thread.Sleep(200); + }; + + Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 29d25accbb..c6863d1cb5 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -1,83 +1,83 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.IO; -using System.Linq; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Tests.Resources; -using osu.Game.Beatmaps.Formats; -using osu.Game.IO.Archives; - -namespace osu.Game.Tests.Beatmaps.IO -{ - [TestFixture] - public class OszArchiveReaderTest - { - [Test] - public void TestReadBeatmaps() - { - using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) - { - var reader = new ZipArchiveReader(osz); - string[] expected = - { - "Soleily - Renatus (Deif) [Platter].osu", - "Soleily - Renatus (Deif) [Rain].osu", - "Soleily - Renatus (Deif) [Salad].osu", - "Soleily - Renatus (ExPew) [Another].osu", - "Soleily - Renatus (ExPew) [Hyper].osu", - "Soleily - Renatus (ExPew) [Normal].osu", - "Soleily - Renatus (Gamu) [Hard].osu", - "Soleily - Renatus (Gamu) [Insane].osu", - "Soleily - Renatus (Gamu) [Normal].osu", - "Soleily - Renatus (MMzz) [Futsuu].osu", - "Soleily - Renatus (MMzz) [Muzukashii].osu", - "Soleily - Renatus (MMzz) [Oni].osu" - }; - var maps = reader.Filenames.ToArray(); - foreach (var map in expected) - Assert.Contains(map, maps); - } - } - - [Test] - public void TestReadMetadata() - { - using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) - { - var reader = new ZipArchiveReader(osz); - - BeatmapMetadata meta; - using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) - meta = Decoder.GetDecoder(stream).Decode(stream).Metadata; - - Assert.AreEqual(241526, meta.OnlineBeatmapSetID); - Assert.AreEqual("Soleily", meta.Artist); - Assert.AreEqual("Soleily", meta.ArtistUnicode); - Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); - Assert.AreEqual("Deif", meta.AuthorString); - Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); - Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime); - Assert.AreEqual(string.Empty, meta.Source); - Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); - Assert.AreEqual("Renatus", meta.Title); - Assert.AreEqual("Renatus", meta.TitleUnicode); - } - } - - [Test] - public void TestReadFile() - { - using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) - { - var reader = new ZipArchiveReader(osz); - using (var stream = new StreamReader( - reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) - { - Assert.AreEqual("osu file format v13", stream.ReadLine()?.Trim()); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Tests.Resources; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO.Archives; + +namespace osu.Game.Tests.Beatmaps.IO +{ + [TestFixture] + public class OszArchiveReaderTest + { + [Test] + public void TestReadBeatmaps() + { + using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) + { + var reader = new ZipArchiveReader(osz); + string[] expected = + { + "Soleily - Renatus (Deif) [Platter].osu", + "Soleily - Renatus (Deif) [Rain].osu", + "Soleily - Renatus (Deif) [Salad].osu", + "Soleily - Renatus (ExPew) [Another].osu", + "Soleily - Renatus (ExPew) [Hyper].osu", + "Soleily - Renatus (ExPew) [Normal].osu", + "Soleily - Renatus (Gamu) [Hard].osu", + "Soleily - Renatus (Gamu) [Insane].osu", + "Soleily - Renatus (Gamu) [Normal].osu", + "Soleily - Renatus (MMzz) [Futsuu].osu", + "Soleily - Renatus (MMzz) [Muzukashii].osu", + "Soleily - Renatus (MMzz) [Oni].osu" + }; + var maps = reader.Filenames.ToArray(); + foreach (var map in expected) + Assert.Contains(map, maps); + } + } + + [Test] + public void TestReadMetadata() + { + using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) + { + var reader = new ZipArchiveReader(osz); + + BeatmapMetadata meta; + using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) + meta = Decoder.GetDecoder(stream).Decode(stream).Metadata; + + Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + Assert.AreEqual("Soleily", meta.Artist); + Assert.AreEqual("Soleily", meta.ArtistUnicode); + Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); + Assert.AreEqual("Deif", meta.AuthorString); + Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); + Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime); + Assert.AreEqual(string.Empty, meta.Source); + Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); + Assert.AreEqual("Renatus", meta.Title); + Assert.AreEqual("Renatus", meta.TitleUnicode); + } + } + + [Test] + public void TestReadFile() + { + using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) + { + var reader = new ZipArchiveReader(osz); + using (var stream = new StreamReader( + reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) + { + Assert.AreEqual("osu file format v13", stream.ReadLine()?.Trim()); + } + } + } + } +} diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index f102e4c59f..e91cd2a624 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -1,247 +1,247 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Online.Chat; - -namespace osu.Game.Tests.Chat -{ - [TestFixture] - public class MessageFormatterTests - { - [Test] - public void TestBareLink() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://www.basic-link.com/?test=test." }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("http://www.basic-link.com/?test=test", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(36, result.Links[0].Length); - } - - [Test] - public void TestMultipleComplexLinks() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(3, result.Links.Count); - - Assert.AreEqual("http://test.io/link#fragment", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(28, result.Links[0].Length); - - Assert.AreEqual("https://twitter.com", result.Links[1].Url); - Assert.AreEqual(45, result.Links[1].Index); - Assert.AreEqual(19, result.Links[1].Length); - - Assert.AreEqual("http://example.com/", result.Links[2].Url); - Assert.AreEqual(108, result.Links[2].Index); - Assert.AreEqual(19, result.Links[2].Length); - } - - [Test] - public void TestAjaxLinks() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "https://twitter.com/#!/hashbanglinks" }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(result.Content, result.Links[0].Url); - Assert.AreEqual(0, result.Links[0].Index); - Assert.AreEqual(36, result.Links[0].Length); - } - - [Test] - public void TestUnixHomeLinks() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "http://www.chiark.greenend.org.uk/~sgtatham/putty/" }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(result.Content, result.Links[0].Url); - Assert.AreEqual(0, result.Links[0].Index); - Assert.AreEqual(50, result.Links[0].Length); - } - - [Test] - public void TestCaseInsensitiveLinks() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF" }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(6, result.Links[0].Index); - Assert.AreEqual(39, result.Links[0].Length); - } - - [Test] - public void TestWikiLink() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]]." }); - - Assert.AreEqual("This is a Wiki Link.", result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(9, result.Links[0].Length); - } - - [Test] - public void TestMultiWikiLink() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]] [[Wiki:Link]][[Wiki.Link]]." }); - - Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent); - Assert.AreEqual(3, result.Links.Count); - - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(9, result.Links[0].Length); - - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); - Assert.AreEqual(20, result.Links[1].Index); - Assert.AreEqual(9, result.Links[1].Length); - - Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); - Assert.AreEqual(29, result.Links[2].Index); - Assert.AreEqual(9, result.Links[2].Length); - } - - [Test] - public void TestOldFormatLink() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (simple test)[https://osu.ppy.sh] of links." }); - - Assert.AreEqual("This is a simple test of links.", result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(11, result.Links[0].Length); - } - - [Test] - public void TestNewFormatLink() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh simple test]." }); - - Assert.AreEqual("This is a simple test.", result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(11, result.Links[0].Length); - } - - [Test] - public void TestMarkdownFormatLink() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [simple test](https://osu.ppy.sh)." }); - - Assert.AreEqual("This is a simple test.", result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(11, result.Links[0].Length); - } - - [Test] - public void TestChannelLink() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is an #english and #japanese." }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(2, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); - Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url); - } - - [Test] - public void TestOsuProtocol() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); - Assert.AreEqual(26, result.Links[0].Index); - Assert.AreEqual(19, result.Links[0].Length); - - result = MessageFormatter.FormatMessage(new Message { Content = "This is a [custom protocol](osu://chan/#english)." }); - - Assert.AreEqual("This is a custom protocol.", result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osu://chan/#english", result.Links[0].Url); - Assert.AreEqual("#english", result.Links[0].Argument); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(15, result.Links[0].Length); - } - - [Test] - public void TestOsuMpProtocol() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "Join my multiplayer game osump://12346." }); - - Assert.AreEqual(result.Content, result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("osump://12346", result.Links[0].Url); - Assert.AreEqual(25, result.Links[0].Index); - Assert.AreEqual(13, result.Links[0].Length); - } - - [Test] - public void TestRecursiveBreaking() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [[simple test]]]." }); - - Assert.AreEqual("This is a [[simple test]].", result.DisplayContent); - Assert.AreEqual(1, result.Links.Count); - Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); - Assert.AreEqual(10, result.Links[0].Index); - Assert.AreEqual(15, result.Links[0].Length); - } - - [Test] - public void TestLinkComplex() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" }); - - Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent); - Assert.AreEqual(5, result.Links.Count); - - Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); - Assert.AreEqual(44, f.Index); - Assert.AreEqual(10, f.Length); - - f = result.Links.Find(l => l.Url == "http://www.simple-test.com"); - Assert.AreEqual(10, f.Index); - Assert.AreEqual(11, f.Length); - - f = result.Links.Find(l => l.Url == "http://google.com"); - Assert.AreEqual(97, f.Index); - Assert.AreEqual(4, f.Length); - - f = result.Links.Find(l => l.Url == "https://osu.ppy.sh"); - Assert.AreEqual(78, f.Index); - Assert.AreEqual(18, f.Length); - - f = result.Links.Find(l => l.Url == "\uD83D\uDE12"); - Assert.AreEqual(101, f.Index); - Assert.AreEqual(3, f.Length); - } - - [Test] - public void TestEmoji() - { - Message result = MessageFormatter.FormatMessage(new Message { Content = "Hello world\uD83D\uDE12<--This is an emoji,There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20" }); - Assert.AreEqual("Hello world\0\0\0<--This is an emoji,There are more:\0\0\0\0\0\0,\0\0\0", result.DisplayContent); - Assert.AreEqual(result.Links.Count, 4); - Assert.AreEqual(result.Links[0].Index, 11); - Assert.AreEqual(result.Links[1].Index, 49); - Assert.AreEqual(result.Links[2].Index, 52); - Assert.AreEqual(result.Links[3].Index, 56); - Assert.AreEqual(result.Links[0].Url, "\uD83D\uDE12"); - Assert.AreEqual(result.Links[1].Url, "\uD83D\uDE10"); - Assert.AreEqual(result.Links[2].Url, "\uD83D\uDE00"); - Assert.AreEqual(result.Links[3].Url, "\uD83D\uDE20"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Online.Chat; + +namespace osu.Game.Tests.Chat +{ + [TestFixture] + public class MessageFormatterTests + { + [Test] + public void TestBareLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://www.basic-link.com/?test=test." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("http://www.basic-link.com/?test=test", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(36, result.Links[0].Length); + } + + [Test] + public void TestMultipleComplexLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(3, result.Links.Count); + + Assert.AreEqual("http://test.io/link#fragment", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(28, result.Links[0].Length); + + Assert.AreEqual("https://twitter.com", result.Links[1].Url); + Assert.AreEqual(45, result.Links[1].Index); + Assert.AreEqual(19, result.Links[1].Length); + + Assert.AreEqual("http://example.com/", result.Links[2].Url); + Assert.AreEqual(108, result.Links[2].Index); + Assert.AreEqual(19, result.Links[2].Length); + } + + [Test] + public void TestAjaxLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "https://twitter.com/#!/hashbanglinks" }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(result.Content, result.Links[0].Url); + Assert.AreEqual(0, result.Links[0].Index); + Assert.AreEqual(36, result.Links[0].Length); + } + + [Test] + public void TestUnixHomeLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "http://www.chiark.greenend.org.uk/~sgtatham/putty/" }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(result.Content, result.Links[0].Url); + Assert.AreEqual(0, result.Links[0].Index); + Assert.AreEqual(50, result.Links[0].Length); + } + + [Test] + public void TestCaseInsensitiveLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF" }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(6, result.Links[0].Index); + Assert.AreEqual(39, result.Links[0].Length); + } + + [Test] + public void TestWikiLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]]." }); + + Assert.AreEqual("This is a Wiki Link.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(9, result.Links[0].Length); + } + + [Test] + public void TestMultiWikiLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]] [[Wiki:Link]][[Wiki.Link]]." }); + + Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent); + Assert.AreEqual(3, result.Links.Count); + + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(9, result.Links[0].Length); + + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); + Assert.AreEqual(20, result.Links[1].Index); + Assert.AreEqual(9, result.Links[1].Length); + + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); + Assert.AreEqual(29, result.Links[2].Index); + Assert.AreEqual(9, result.Links[2].Length); + } + + [Test] + public void TestOldFormatLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (simple test)[https://osu.ppy.sh] of links." }); + + Assert.AreEqual("This is a simple test of links.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(11, result.Links[0].Length); + } + + [Test] + public void TestNewFormatLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh simple test]." }); + + Assert.AreEqual("This is a simple test.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(11, result.Links[0].Length); + } + + [Test] + public void TestMarkdownFormatLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [simple test](https://osu.ppy.sh)." }); + + Assert.AreEqual("This is a simple test.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(11, result.Links[0].Length); + } + + [Test] + public void TestChannelLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is an #english and #japanese." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(2, result.Links.Count); + Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url); + } + + [Test] + public void TestOsuProtocol() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual(26, result.Links[0].Index); + Assert.AreEqual(19, result.Links[0].Length); + + result = MessageFormatter.FormatMessage(new Message { Content = "This is a [custom protocol](osu://chan/#english)." }); + + Assert.AreEqual("This is a custom protocol.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual("#english", result.Links[0].Argument); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(15, result.Links[0].Length); + } + + [Test] + public void TestOsuMpProtocol() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "Join my multiplayer game osump://12346." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("osump://12346", result.Links[0].Url); + Assert.AreEqual(25, result.Links[0].Index); + Assert.AreEqual(13, result.Links[0].Length); + } + + [Test] + public void TestRecursiveBreaking() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [[simple test]]]." }); + + Assert.AreEqual("This is a [[simple test]].", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(15, result.Links[0].Length); + } + + [Test] + public void TestLinkComplex() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" }); + + Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent); + Assert.AreEqual(5, result.Links.Count); + + Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); + Assert.AreEqual(44, f.Index); + Assert.AreEqual(10, f.Length); + + f = result.Links.Find(l => l.Url == "http://www.simple-test.com"); + Assert.AreEqual(10, f.Index); + Assert.AreEqual(11, f.Length); + + f = result.Links.Find(l => l.Url == "http://google.com"); + Assert.AreEqual(97, f.Index); + Assert.AreEqual(4, f.Length); + + f = result.Links.Find(l => l.Url == "https://osu.ppy.sh"); + Assert.AreEqual(78, f.Index); + Assert.AreEqual(18, f.Length); + + f = result.Links.Find(l => l.Url == "\uD83D\uDE12"); + Assert.AreEqual(101, f.Index); + Assert.AreEqual(3, f.Length); + } + + [Test] + public void TestEmoji() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "Hello world\uD83D\uDE12<--This is an emoji,There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20" }); + Assert.AreEqual("Hello world\0\0\0<--This is an emoji,There are more:\0\0\0\0\0\0,\0\0\0", result.DisplayContent); + Assert.AreEqual(result.Links.Count, 4); + Assert.AreEqual(result.Links[0].Index, 11); + Assert.AreEqual(result.Links[1].Index, 49); + Assert.AreEqual(result.Links[2].Index, 52); + Assert.AreEqual(result.Links[3].Index, 56); + Assert.AreEqual(result.Links[0].Url, "\uD83D\uDE12"); + Assert.AreEqual(result.Links[1].Url, "\uD83D\uDE10"); + Assert.AreEqual(result.Links[2].Url, "\uD83D\uDE00"); + Assert.AreEqual(result.Links[3].Url, "\uD83D\uDE20"); + } + } +} diff --git a/osu.Game.Tests/Resources/Resource.cs b/osu.Game.Tests/Resources/Resource.cs index 39c20e6b19..ef90591561 100644 --- a/osu.Game.Tests/Resources/Resource.cs +++ b/osu.Game.Tests/Resources/Resource.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using System.Reflection; - -namespace osu.Game.Tests.Resources -{ - public static class Resource - { - public static Stream OpenResource(string name) - { - var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); - - return Assembly.GetExecutingAssembly().GetManifestResourceStream($@"osu.Game.Tests.Resources.{name}") ?? - Assembly.LoadFrom(Path.Combine(localPath, @"osu.Game.Resources.dll")).GetManifestResourceStream($@"osu.Game.Resources.{name}"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Reflection; + +namespace osu.Game.Tests.Resources +{ + public static class Resource + { + public static Stream OpenResource(string name) + { + var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); + + return Assembly.GetExecutingAssembly().GetManifestResourceStream($@"osu.Game.Tests.Resources.{name}") ?? + Assembly.LoadFrom(Path.Combine(localPath, @"osu.Game.Resources.dll")).GetManifestResourceStream($@"osu.Game.Resources.{name}"); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs index e633d121ca..4134ce3634 100644 --- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs +++ b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseAllPlayers : TestCasePlayer - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseAllPlayers : TestCasePlayer + { + } +} diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index d954d0543c..cecb327b6c 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [Description("Player instantiated with an autoplay mod.")] - public class TestCaseAutoplay : TestCasePlayer - { - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) - { - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - return base.CreatePlayer(beatmap, ruleset); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [Description("Player instantiated with an autoplay mod.")] + public class TestCaseAutoplay : TestCasePlayer + { + protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + { + beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + return base.CreatePlayer(beatmap, ruleset); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs index 04a662426f..1effa14e76 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Screens.Edit.Screens.Compose; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - public class TestCaseBeatDivisorControl : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) }; - - [BackgroundDependencyLoader] - private void load() - { - Child = new BeatDivisorControl(new BindableBeatDivisor()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(90, 90) - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Screens.Edit.Screens.Compose; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseBeatDivisorControl : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) }; + + [BackgroundDependencyLoader] + private void load() + { + Child = new BeatDivisorControl(new BindableBeatDivisor()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(90, 90) + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs index 66cee634f5..596b7839e0 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs @@ -1,205 +1,205 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using NUnit.Framework; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using OpenTK.Graphics; -using osu.Framework.Lists; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBeatSyncedContainer : OsuTestCase - { - private readonly MusicController mc; - - public TestCaseBeatSyncedContainer() - { - Clock = new FramedClock(); - Clock.ProcessFrame(); - - Add(new BeatContainer - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - }); - - Add(mc = new MusicController - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - mc.ToggleVisibility(); - } - - private class BeatContainer : BeatSyncedContainer - { - private const int flash_layer_heigth = 150; - - private readonly InfoString timingPointCount; - private readonly InfoString currentTimingPoint; - private readonly InfoString beatCount; - private readonly InfoString currentBeat; - private readonly InfoString beatsPerMinute; - private readonly InfoString adjustedBeatLength; - private readonly InfoString timeUntilNextBeat; - private readonly InfoString timeSinceLastBeat; - - private readonly Box flashLayer; - - public BeatContainer() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - new Container - { - Name = @"Info Layer", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = flash_layer_heigth }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(150), - }, - new FillFlowContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - timingPointCount = new InfoString(@"Timing points amount"), - currentTimingPoint = new InfoString(@"Current timing point"), - beatCount = new InfoString(@"Beats amount (in the current timing point)"), - currentBeat = new InfoString(@"Current beat"), - beatsPerMinute = new InfoString(@"BPM"), - adjustedBeatLength = new InfoString(@"Adjusted beat length"), - timeUntilNextBeat = new InfoString(@"Time until next beat"), - timeSinceLastBeat = new InfoString(@"Time since last beat"), - } - } - } - }, - new Container - { - Name = @"Color indicator", - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = flash_layer_heigth, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - flashLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - Alpha = 0, - } - } - } - }; - - Beatmap.ValueChanged += delegate - { - timingPointCount.Value = 0; - currentTimingPoint.Value = 0; - beatCount.Value = 0; - currentBeat.Value = 0; - beatsPerMinute.Value = 0; - adjustedBeatLength.Value = 0; - timeUntilNextBeat.Value = 0; - timeSinceLastBeat.Value = 0; - }; - } - - private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; - private TimingControlPoint getNextTimingPoint(TimingControlPoint current) - { - if (timingPoints[timingPoints.Count - 1] == current) - return current; - - return timingPoints[timingPoints.IndexOf(current) + 1]; - } - - private int calculateBeatCount(TimingControlPoint current) - { - if (timingPoints.Count == 0) return 0; - - if (timingPoints[timingPoints.Count - 1] == current) - return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength); - - return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); - } - - protected override void Update() - { - base.Update(); - timeUntilNextBeat.Value = TimeUntilNextBeat; - timeSinceLastBeat.Value = TimeSinceLastBeat; - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) - { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - - timingPointCount.Value = timingPoints.Count; - currentTimingPoint.Value = timingPoints.IndexOf(timingPoint); - beatCount.Value = calculateBeatCount(timingPoint); - currentBeat.Value = beatIndex; - beatsPerMinute.Value = 60000 / timingPoint.BeatLength; - adjustedBeatLength.Value = timingPoint.BeatLength; - - flashLayer.FadeOutFromOne(timingPoint.BeatLength); - } - } - - private class InfoString : FillFlowContainer - { - private const int text_size = 20; - private const int margin = 7; - - private readonly OsuSpriteText valueText; - - public double Value - { - set { valueText.Text = $"{value:G}"; } - } - - public InfoString(string header) - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - Add(new OsuSpriteText { Text = header + @": ", TextSize = text_size }); - Add(valueText = new OsuSpriteText { TextSize = text_size }); - Margin = new MarginPadding(margin); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using NUnit.Framework; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using OpenTK.Graphics; +using osu.Framework.Lists; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseBeatSyncedContainer : OsuTestCase + { + private readonly MusicController mc; + + public TestCaseBeatSyncedContainer() + { + Clock = new FramedClock(); + Clock.ProcessFrame(); + + Add(new BeatContainer + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); + + Add(mc = new MusicController + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + mc.ToggleVisibility(); + } + + private class BeatContainer : BeatSyncedContainer + { + private const int flash_layer_heigth = 150; + + private readonly InfoString timingPointCount; + private readonly InfoString currentTimingPoint; + private readonly InfoString beatCount; + private readonly InfoString currentBeat; + private readonly InfoString beatsPerMinute; + private readonly InfoString adjustedBeatLength; + private readonly InfoString timeUntilNextBeat; + private readonly InfoString timeSinceLastBeat; + + private readonly Box flashLayer; + + public BeatContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new Container + { + Name = @"Info Layer", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = flash_layer_heigth }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(150), + }, + new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + timingPointCount = new InfoString(@"Timing points amount"), + currentTimingPoint = new InfoString(@"Current timing point"), + beatCount = new InfoString(@"Beats amount (in the current timing point)"), + currentBeat = new InfoString(@"Current beat"), + beatsPerMinute = new InfoString(@"BPM"), + adjustedBeatLength = new InfoString(@"Adjusted beat length"), + timeUntilNextBeat = new InfoString(@"Time until next beat"), + timeSinceLastBeat = new InfoString(@"Time since last beat"), + } + } + } + }, + new Container + { + Name = @"Color indicator", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = flash_layer_heigth, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0, + } + } + } + }; + + Beatmap.ValueChanged += delegate + { + timingPointCount.Value = 0; + currentTimingPoint.Value = 0; + beatCount.Value = 0; + currentBeat.Value = 0; + beatsPerMinute.Value = 0; + adjustedBeatLength.Value = 0; + timeUntilNextBeat.Value = 0; + timeSinceLastBeat.Value = 0; + }; + } + + private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private TimingControlPoint getNextTimingPoint(TimingControlPoint current) + { + if (timingPoints[timingPoints.Count - 1] == current) + return current; + + return timingPoints[timingPoints.IndexOf(current) + 1]; + } + + private int calculateBeatCount(TimingControlPoint current) + { + if (timingPoints.Count == 0) return 0; + + if (timingPoints[timingPoints.Count - 1] == current) + return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength); + + return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); + } + + protected override void Update() + { + base.Update(); + timeUntilNextBeat.Value = TimeUntilNextBeat; + timeSinceLastBeat.Value = TimeSinceLastBeat; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + timingPointCount.Value = timingPoints.Count; + currentTimingPoint.Value = timingPoints.IndexOf(timingPoint); + beatCount.Value = calculateBeatCount(timingPoint); + currentBeat.Value = beatIndex; + beatsPerMinute.Value = 60000 / timingPoint.BeatLength; + adjustedBeatLength.Value = timingPoint.BeatLength; + + flashLayer.FadeOutFromOne(timingPoint.BeatLength); + } + } + + private class InfoString : FillFlowContainer + { + private const int text_size = 20; + private const int margin = 7; + + private readonly OsuSpriteText valueText; + + public double Value + { + set { valueText.Text = $"{value:G}"; } + } + + public InfoString(string header) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Add(new OsuSpriteText { Text = header + @": ", TextSize = text_size }); + Add(valueText = new OsuSpriteText { TextSize = text_size }); + Margin = new MarginPadding(margin); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index c68e548f44..f819e882d9 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -1,501 +1,531 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Rulesets; -using osu.Game.Screens.Select; -using osu.Game.Screens.Select.Carousel; -using osu.Game.Screens.Select.Filter; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBeatmapCarousel : OsuTestCase - { - private TestBeatmapCarousel carousel; - private RulesetStore rulesets; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(CarouselItem), - typeof(CarouselGroup), - typeof(CarouselGroupEagerSelect), - typeof(CarouselBeatmap), - typeof(CarouselBeatmapSet), - - typeof(DrawableCarouselItem), - typeof(CarouselItemState), - - typeof(DrawableCarouselBeatmap), - typeof(DrawableCarouselBeatmapSet), - }; - - - private readonly Stack selectedSets = new Stack(); - - private BeatmapInfo currentSelection; - - private const int set_count = 5; - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - - Add(carousel = new TestBeatmapCarousel - { - RelativeSizeAxes = Axes.Both, - }); - - List beatmapSets = new List(); - - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); - - carousel.SelectionChanged = s => currentSelection = s; - - AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); - - bool changed = false; - carousel.BeatmapSetsChanged = () => changed = true; - AddUntilStep(() => changed, "Wait for load"); - - testTraversal(); - testFiltering(); - testRandom(); - testAddRemove(); - testSorting(); - - testRemoveAll(); - testEmptyTraversal(); - testHiding(); - testSelectingFilteredRuleset(); - } - - private void ensureRandomFetchSuccess() => - AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); - - private void checkSelected(int set, int? diff = null) => - AddAssert($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => - { - if (diff != null) - return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); - - return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap); - }); - - private void setSelected(int set, int diff) => - AddStep($"select set{set} diff{diff}", () => - carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); - - private void advanceSelection(bool diff, int direction = 1, int count = 1) - { - if (count == 1) - AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => - carousel.SelectNext(direction, !diff)); - else - { - AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => - carousel.SelectNext(direction, !diff), count); - } - } - - private void checkVisibleItemCount(bool diff, int count) => - AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () => - carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); - - private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); - - private void nextRandom() => - AddStep("select random next", () => - { - carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; - - if (!selectedSets.Any() && carousel.SelectedBeatmap != null) - selectedSets.Push(carousel.SelectedBeatmapSet); - - carousel.SelectNextRandom(); - selectedSets.Push(carousel.SelectedBeatmapSet); - }); - - private void ensureRandomDidntRepeat() => - AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); - - private void prevRandom() => AddStep("select random last", () => - { - carousel.SelectPreviousRandom(); - selectedSets.Pop(); - }); - - private bool selectedBeatmapVisible() - { - var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected); - if (currentlySelected == null) - return true; - return currentlySelected.Item.Visible; - } - - private void checkInvisibleDifficultiesUnselectable() - { - nextRandom(); - AddAssert("Selection is visible", selectedBeatmapVisible); - } - - /// - /// Test keyboard traversal - /// - private void testTraversal() - { - advanceSelection(direction: 1, diff: false); - checkSelected(1, 1); - - advanceSelection(direction: 1, diff: true); - checkSelected(1, 2); - - advanceSelection(direction: -1, diff: false); - checkSelected(set_count, 1); - - advanceSelection(direction: -1, diff: true); - checkSelected(set_count - 1, 3); - - advanceSelection(diff: false); - advanceSelection(diff: false); - checkSelected(1, 2); - - advanceSelection(direction: -1, diff: true); - advanceSelection(direction: -1, diff: true); - checkSelected(set_count, 3); - } - - /// - /// Test filtering - /// - private void testFiltering() - { - // basic filtering - - setSelected(1, 1); - - AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false)); - checkVisibleItemCount(diff: false, count: 1); - checkVisibleItemCount(diff: true, count: 3); - checkSelected(3, 1); - - advanceSelection(diff: true, count: 4); - checkSelected(3, 2); - - AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); - AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); - checkVisibleItemCount(diff: false, count: set_count); - checkVisibleItemCount(diff: true, count: 3); - - // test filtering some difficulties (and keeping current beatmap set selected). - - setSelected(1, 2); - AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false)); - checkSelected(1, 1); - - AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); - checkSelected(1, 1); - - AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false)); - - checkVisibleItemCount(false, 0); - checkVisibleItemCount(true, 0); - AddAssert("Selection is null", () => currentSelection == null); - - advanceSelection(true); - AddAssert("Selection is null", () => currentSelection == null); - - advanceSelection(false); - AddAssert("Selection is null", () => currentSelection == null); - - AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); - - AddAssert("Selection is non-null", () => currentSelection != null); - } - - /// - /// Test random non-repeating algorithm - /// - private void testRandom() - { - setSelected(1, 1); - - nextRandom(); - ensureRandomDidntRepeat(); - nextRandom(); - ensureRandomDidntRepeat(); - nextRandom(); - ensureRandomDidntRepeat(); - - prevRandom(); - ensureRandomFetchSuccess(); - prevRandom(); - ensureRandomFetchSuccess(); - - nextRandom(); - ensureRandomDidntRepeat(); - nextRandom(); - ensureRandomDidntRepeat(); - - nextRandom(); - AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); - - AddStep("Add set with 100 difficulties", () => carousel.UpdateBeatmapSet(createTestBeatmapSetWithManyDifficulties(set_count + 1))); - AddStep("Filter Extra", () => carousel.Filter(new FilterCriteria { SearchText = "Extra 10" }, false)); - checkInvisibleDifficultiesUnselectable(); - checkInvisibleDifficultiesUnselectable(); - checkInvisibleDifficultiesUnselectable(); - checkInvisibleDifficultiesUnselectable(); - checkInvisibleDifficultiesUnselectable(); - AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); - } - - /// - /// Test adding and removing beatmap sets - /// - private void testAddRemove() - { - AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1))); - AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2))); - - checkVisibleItemCount(false, set_count + 2); - - AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 2))); - - checkVisibleItemCount(false, set_count + 1); - - setSelected(set_count + 1, 1); - - AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 1))); - - checkVisibleItemCount(false, set_count); - - checkSelected(set_count); - } - - /// - /// Test sorting - /// - private void testSorting() - { - AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); - AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz"); - AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); - AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); - } - - private void testRemoveAll() - { - setSelected(2, 1); - AddAssert("Selection is non-null", () => currentSelection != null); - - AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet)); - checkSelected(2); - - AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); - AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); - checkSelected(1); - - AddUntilStep(() => - { - if (!carousel.BeatmapSets.Any()) return true; - - carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); - return false; - }, "Remove all"); - - checkNoSelection(); - } - - private void testEmptyTraversal() - { - advanceSelection(direction: 1, diff: false); - checkNoSelection(); - - advanceSelection(direction: 1, diff: true); - checkNoSelection(); - - advanceSelection(direction: -1, diff: false); - checkNoSelection(); - - advanceSelection(direction: -1, diff: true); - checkNoSelection(); - } - - private void testHiding() - { - var hidingSet = createTestBeatmapSet(1); - hidingSet.Beatmaps[1].Hidden = true; - AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet)); - setSelected(1, 1); - - checkVisibleItemCount(true, 2); - advanceSelection(true); - checkSelected(1, 3); - - setHidden(3); - checkSelected(1, 1); - - setHidden(2, false); - advanceSelection(true); - checkSelected(1, 2); - - setHidden(1); - checkSelected(1, 2); - - setHidden(2); - checkNoSelection(); - - void setHidden(int diff, bool hidden = true) - { - AddStep((hidden ? "" : "un") + $"hide diff {diff}", () => - { - hidingSet.Beatmaps[diff - 1].Hidden = hidden; - carousel.UpdateBeatmapSet(hidingSet); - }); - } - } - - private void testSelectingFilteredRuleset() - { - var testMixed = createTestBeatmapSet(set_count + 1); - AddStep("add mixed ruleset beatmapset", () => - { - for (int i = 0; i <= 2; i++) - { - testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i); - testMixed.Beatmaps[i].RulesetID = i; - } - - carousel.UpdateBeatmapSet(testMixed); - }); - AddStep("filter to ruleset 0", () => - carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); - AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); - AddAssert("unfiltered beatmap selected", () => carousel.SelectedBeatmap.Equals(testMixed.Beatmaps[0])); - - AddStep("remove mixed set", () => - { - carousel.RemoveBeatmapSet(testMixed); - testMixed = null; - }); - var testSingle = createTestBeatmapSet(set_count + 2); - testSingle.Beatmaps.ForEach(b => - { - b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); - b.RulesetID = b.Ruleset.ID ?? 1; - }); - AddStep("add single ruleset beatmapset", () => carousel.UpdateBeatmapSet(testSingle)); - AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false)); - checkNoSelection(); - AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle)); - } - - private BeatmapSetInfo createTestBeatmapSet(int id) - { - return new BeatmapSetInfo - { - ID = id, - OnlineBeatmapSetID = id, - Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), - Metadata = new BeatmapMetadata - { - OnlineBeatmapSetID = id, - // Create random metadata, then we can check if sorting works based on these - Artist = $"peppy{id.ToString().PadLeft(6, '0')}", - Title = $"test set #{id}!", - AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) - }, - Beatmaps = new List(new[] - { - new BeatmapInfo - { - OnlineBeatmapID = id * 10, - Path = "normal.osu", - Version = "Normal", - StarDifficulty = 2, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = id * 10 + 1, - Path = "hard.osu", - Version = "Hard", - StarDifficulty = 5, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 5, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = id * 10 + 2, - Path = "insane.osu", - Version = "Insane", - StarDifficulty = 6, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 7, - } - }, - }), - }; - } - - private BeatmapSetInfo createTestBeatmapSetWithManyDifficulties(int id) - { - var toReturn = new BeatmapSetInfo - { - ID = id, - OnlineBeatmapSetID = id, - Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), - Metadata = new BeatmapMetadata - { - OnlineBeatmapSetID = id, - // Create random metadata, then we can check if sorting works based on these - Artist = $"peppy{id.ToString().PadLeft(6, '0')}", - Title = $"test set #{id}!", - AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) - }, - Beatmaps = new List(), - }; - for (int b = 1; b < 101; b++) - { - toReturn.Beatmaps.Add(new BeatmapInfo - { - OnlineBeatmapID = b * 10, - Path = $"extra{b}.osu", - Version = $"Extra {b}", - StarDifficulty = 2, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }); - } - return toReturn; - } - - private class TestBeatmapCarousel : BeatmapCarousel - { - public new List Items => base.Items; - - public bool PendingFilterTask => FilterTask != null; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; +using osu.Game.Screens.Select.Filter; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseBeatmapCarousel : OsuTestCase + { + private TestBeatmapCarousel carousel; + private RulesetStore rulesets; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CarouselItem), + typeof(CarouselGroup), + typeof(CarouselGroupEagerSelect), + typeof(CarouselBeatmap), + typeof(CarouselBeatmapSet), + + typeof(DrawableCarouselItem), + typeof(CarouselItemState), + + typeof(DrawableCarouselBeatmap), + typeof(DrawableCarouselBeatmapSet), + }; + + + private readonly Stack selectedSets = new Stack(); + private readonly HashSet eagerSelectedIDs = new HashSet(); + + private BeatmapInfo currentSelection; + + private const int set_count = 5; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + + Add(carousel = new TestBeatmapCarousel + { + RelativeSizeAxes = Axes.Both, + }); + + List beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + + carousel.SelectionChanged = s => currentSelection = s; + + AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); + + bool changed = false; + carousel.BeatmapSetsChanged = () => changed = true; + AddUntilStep(() => changed, "Wait for load"); + + testTraversal(); + testFiltering(); + testRandom(); + testAddRemove(); + testSorting(); + + testRemoveAll(); + testEmptyTraversal(); + testHiding(); + testSelectingFilteredRuleset(); + testCarouselRootIsRandom(); + } + + private void ensureRandomFetchSuccess() => + AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + + private void checkSelected(int set, int? diff = null) => + AddAssert($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => + { + if (diff != null) + return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); + + return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap); + }); + + private void setSelected(int set, int diff) => + AddStep($"select set{set} diff{diff}", () => + carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); + + private void advanceSelection(bool diff, int direction = 1, int count = 1) + { + if (count == 1) + AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff)); + else + { + AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff), count); + } + } + + private void checkVisibleItemCount(bool diff, int count) => + AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () => + carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); + + private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null); + + private void nextRandom() => + AddStep("select random next", () => + { + carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; + + if (!selectedSets.Any() && carousel.SelectedBeatmap != null) + selectedSets.Push(carousel.SelectedBeatmapSet); + + carousel.SelectNextRandom(); + selectedSets.Push(carousel.SelectedBeatmapSet); + }); + + private void ensureRandomDidntRepeat() => + AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); + + private void prevRandom() => AddStep("select random last", () => + { + carousel.SelectPreviousRandom(); + selectedSets.Pop(); + }); + + private bool selectedBeatmapVisible() + { + var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected); + if (currentlySelected == null) + return true; + return currentlySelected.Item.Visible; + } + + private void checkInvisibleDifficultiesUnselectable() + { + nextRandom(); + AddAssert("Selection is visible", selectedBeatmapVisible); + } + + private void checkNonmatchingFilter() + { + AddStep("Toggle non-matching filter", () => + { + carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false); + carousel.Filter(new FilterCriteria(), false); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); + } + ); + } + + /// + /// Test keyboard traversal + /// + private void testTraversal() + { + advanceSelection(direction: 1, diff: false); + checkSelected(1, 1); + + advanceSelection(direction: 1, diff: true); + checkSelected(1, 2); + + advanceSelection(direction: -1, diff: false); + checkSelected(set_count, 1); + + advanceSelection(direction: -1, diff: true); + checkSelected(set_count - 1, 3); + + advanceSelection(diff: false); + advanceSelection(diff: false); + checkSelected(1, 2); + + advanceSelection(direction: -1, diff: true); + advanceSelection(direction: -1, diff: true); + checkSelected(set_count, 3); + } + + /// + /// Test filtering + /// + private void testFiltering() + { + // basic filtering + + setSelected(1, 1); + + AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false)); + checkVisibleItemCount(diff: false, count: 1); + checkVisibleItemCount(diff: true, count: 3); + checkSelected(3, 1); + + advanceSelection(diff: true, count: 4); + checkSelected(3, 2); + + AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); + AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); + checkVisibleItemCount(diff: false, count: set_count); + checkVisibleItemCount(diff: true, count: 3); + + // test filtering some difficulties (and keeping current beatmap set selected). + + setSelected(1, 2); + AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false)); + checkSelected(1, 1); + + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + checkSelected(1, 1); + + AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false)); + + checkVisibleItemCount(false, 0); + checkVisibleItemCount(true, 0); + AddAssert("Selection is null", () => currentSelection == null); + + advanceSelection(true); + AddAssert("Selection is null", () => currentSelection == null); + + advanceSelection(false); + AddAssert("Selection is null", () => currentSelection == null); + + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + + AddAssert("Selection is non-null", () => currentSelection != null); + } + + /// + /// Test random non-repeating algorithm + /// + private void testRandom() + { + setSelected(1, 1); + + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + prevRandom(); + ensureRandomFetchSuccess(); + prevRandom(); + ensureRandomFetchSuccess(); + + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + nextRandom(); + AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); + + AddStep("Add set with 100 difficulties", () => carousel.UpdateBeatmapSet(createTestBeatmapSetWithManyDifficulties(set_count + 1))); + AddStep("Filter Extra", () => carousel.Filter(new FilterCriteria { SearchText = "Extra 10" }, false)); + checkInvisibleDifficultiesUnselectable(); + checkInvisibleDifficultiesUnselectable(); + checkInvisibleDifficultiesUnselectable(); + checkInvisibleDifficultiesUnselectable(); + checkInvisibleDifficultiesUnselectable(); + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + } + + /// + /// Test adding and removing beatmap sets + /// + private void testAddRemove() + { + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1))); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2))); + + checkVisibleItemCount(false, set_count + 2); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 2))); + + checkVisibleItemCount(false, set_count + 1); + + setSelected(set_count + 1, 1); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 1))); + + checkVisibleItemCount(false, set_count); + + checkSelected(set_count); + } + + /// + /// Test sorting + /// + private void testSorting() + { + AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); + AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz"); + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); + } + + private void testRemoveAll() + { + setSelected(2, 1); + AddAssert("Selection is non-null", () => currentSelection != null); + + AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet)); + checkSelected(2); + + AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); + AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); + checkSelected(1); + + AddUntilStep(() => + { + if (!carousel.BeatmapSets.Any()) return true; + + carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); + return false; + }, "Remove all"); + + checkNoSelection(); + } + + private void testEmptyTraversal() + { + advanceSelection(direction: 1, diff: false); + checkNoSelection(); + + advanceSelection(direction: 1, diff: true); + checkNoSelection(); + + advanceSelection(direction: -1, diff: false); + checkNoSelection(); + + advanceSelection(direction: -1, diff: true); + checkNoSelection(); + } + + private void testHiding() + { + var hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; + AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet)); + setSelected(1, 1); + + checkVisibleItemCount(true, 2); + advanceSelection(true); + checkSelected(1, 3); + + setHidden(3); + checkSelected(1, 1); + + setHidden(2, false); + advanceSelection(true); + checkSelected(1, 2); + + setHidden(1); + checkSelected(1, 2); + + setHidden(2); + checkNoSelection(); + + void setHidden(int diff, bool hidden = true) + { + AddStep((hidden ? "" : "un") + $"hide diff {diff}", () => + { + hidingSet.Beatmaps[diff - 1].Hidden = hidden; + carousel.UpdateBeatmapSet(hidingSet); + }); + } + } + + private void testSelectingFilteredRuleset() + { + var testMixed = createTestBeatmapSet(set_count + 1); + AddStep("add mixed ruleset beatmapset", () => + { + for (int i = 0; i <= 2; i++) + { + testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i); + testMixed.Beatmaps[i].RulesetID = i; + } + + carousel.UpdateBeatmapSet(testMixed); + }); + AddStep("filter to ruleset 0", () => + carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); + AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); + AddAssert("unfiltered beatmap selected", () => carousel.SelectedBeatmap.Equals(testMixed.Beatmaps[0])); + + AddStep("remove mixed set", () => + { + carousel.RemoveBeatmapSet(testMixed); + testMixed = null; + }); + var testSingle = createTestBeatmapSet(set_count + 2); + testSingle.Beatmaps.ForEach(b => + { + b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); + b.RulesetID = b.Ruleset.ID ?? 1; + }); + AddStep("add single ruleset beatmapset", () => carousel.UpdateBeatmapSet(testSingle)); + AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false)); + checkNoSelection(); + AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle)); + } + + private void testCarouselRootIsRandom() + { + List beatmapSets = new List(); + + for (int i = 1; i <= 50; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + + AddStep("Load 50 Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); + advanceSelection(direction: 1, diff: false); + checkNonmatchingFilter(); + checkNonmatchingFilter(); + checkNonmatchingFilter(); + checkNonmatchingFilter(); + checkNonmatchingFilter(); + AddAssert("Selection was random", () => eagerSelectedIDs.Count > 1); + } + + private BeatmapSetInfo createTestBeatmapSet(int id) + { + return new BeatmapSetInfo + { + ID = id, + OnlineBeatmapSetID = id, + Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), + Metadata = new BeatmapMetadata + { + OnlineBeatmapSetID = id, + // Create random metadata, then we can check if sorting works based on these + Artist = $"peppy{id.ToString().PadLeft(6, '0')}", + Title = $"test set #{id}!", + AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) + }, + Beatmaps = new List(new[] + { + new BeatmapInfo + { + OnlineBeatmapID = id * 10, + Path = "normal.osu", + Version = "Normal", + StarDifficulty = 2, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = id * 10 + 1, + Path = "hard.osu", + Version = "Hard", + StarDifficulty = 5, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 5, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = id * 10 + 2, + Path = "insane.osu", + Version = "Insane", + StarDifficulty = 6, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 7, + } + }, + }), + }; + } + + private BeatmapSetInfo createTestBeatmapSetWithManyDifficulties(int id) + { + var toReturn = new BeatmapSetInfo + { + ID = id, + OnlineBeatmapSetID = id, + Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), + Metadata = new BeatmapMetadata + { + OnlineBeatmapSetID = id, + // Create random metadata, then we can check if sorting works based on these + Artist = $"peppy{id.ToString().PadLeft(6, '0')}", + Title = $"test set #{id}!", + AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5)) + }, + Beatmaps = new List(), + }; + for (int b = 1; b < 101; b++) + { + toReturn.Beatmaps.Add(new BeatmapInfo + { + OnlineBeatmapID = b * 10, + Path = $"extra{b}.osu", + Version = $"Extra {b}", + StarDifficulty = 2, + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + } + }); + } + return toReturn; + } + + private class TestBeatmapCarousel : BeatmapCarousel + { + public new List Items => base.Items; + + public bool PendingFilterTask => FilterTask != null; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index 3e94de0f3e..41d85673f8 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Screens.Select; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] - public class TestCaseBeatmapDetailArea : OsuTestCase - { - public TestCaseBeatmapDetailArea() - { - Add(new BeatmapDetailArea - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(550f, 450f), - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Screens.Select; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] + public class TestCaseBeatmapDetailArea : OsuTestCase + { + public TestCaseBeatmapDetailArea() + { + Add(new BeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(550f, 450f), + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs index e5924fccf8..0f7d871e5e 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs @@ -1,115 +1,115 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Screens.Select; - -namespace osu.Game.Tests.Visual -{ - [Description("PlaySongSelect beatmap details")] - public class TestCaseBeatmapDetails : OsuTestCase - { - public TestCaseBeatmapDetails() - { - BeatmapDetails details; - Add(details = new BeatmapDetails - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(150), - }); - - AddStep("beatmap all metrics", () => details.Beatmap = new BeatmapInfo - { - Version = "All Metrics", - Metadata = new BeatmapMetadata - { - Source = "osu!lazer", - Tags = "this beatmap has all the metrics", - }, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 7, - DrainRate = 1, - OverallDifficulty = 5.7f, - ApproachRate = 3.5f, - }, - StarDifficulty = 5.3f, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }); - - AddStep("beatmap ratings", () => details.Beatmap = new BeatmapInfo - { - Version = "Only Ratings", - Metadata = new BeatmapMetadata - { - Source = "osu!lazer", - Tags = "this beatmap has ratings metrics but not retries or fails", - }, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 6, - DrainRate = 9, - OverallDifficulty = 6, - ApproachRate = 6, - }, - StarDifficulty = 4.8f, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - }, - }); - - AddStep("beatmap fails retries", () => details.Beatmap = new BeatmapInfo - { - Version = "Only Retries and Fails", - Metadata = new BeatmapMetadata - { - Source = "osu!lazer", - Tags = "this beatmap has retries and fails but no ratings", - }, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 3.7f, - DrainRate = 6, - OverallDifficulty = 6, - ApproachRate = 7, - }, - StarDifficulty = 2.91f, - Metrics = new BeatmapMetrics - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }); - - AddStep("beatmap no metrics", () => details.Beatmap = new BeatmapInfo - { - Version = "No Metrics", - Metadata = new BeatmapMetadata - { - Source = "osu!lazer", - Tags = "this beatmap has no metrics", - }, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 5, - OverallDifficulty = 5.5f, - ApproachRate = 6.5f, - }, - StarDifficulty = 1.97f, - Metrics = new BeatmapMetrics(), - }); - - AddStep("null beatmap", () => details.Beatmap = null); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual +{ + [Description("PlaySongSelect beatmap details")] + public class TestCaseBeatmapDetails : OsuTestCase + { + public TestCaseBeatmapDetails() + { + BeatmapDetails details; + Add(details = new BeatmapDetails + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(150), + }); + + AddStep("beatmap all metrics", () => details.Beatmap = new BeatmapInfo + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }); + + AddStep("beatmap ratings", () => details.Beatmap = new BeatmapInfo + { + Version = "Only Ratings", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has ratings metrics but not retries or fails", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 6, + DrainRate = 9, + OverallDifficulty = 6, + ApproachRate = 6, + }, + StarDifficulty = 4.8f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + }, + }); + + AddStep("beatmap fails retries", () => details.Beatmap = new BeatmapInfo + { + Version = "Only Retries and Fails", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has retries and fails but no ratings", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 3.7f, + DrainRate = 6, + OverallDifficulty = 6, + ApproachRate = 7, + }, + StarDifficulty = 2.91f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }); + + AddStep("beatmap no metrics", () => details.Beatmap = new BeatmapInfo + { + Version = "No Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has no metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5.5f, + ApproachRate = 6.5f, + }, + StarDifficulty = 1.97f, + Metrics = new BeatmapMetrics(), + }); + + AddStep("null beatmap", () => details.Beatmap = null); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 3ccdaa90d9..790c4cedc3 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -1,163 +1,163 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Select; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBeatmapInfoWedge : OsuTestCase - { - private RulesetStore rulesets; - private TestBeatmapInfoWedge infoWedge; - private readonly List beatmaps = new List(); - private readonly Bindable beatmap = new Bindable(); - - [BackgroundDependencyLoader] - private void load(OsuGameBase game, RulesetStore rulesets) - { - this.rulesets = rulesets; - - beatmap.BindTo(game.Beatmap); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(infoWedge = new TestBeatmapInfoWedge - { - Size = new Vector2(0.5f, 245), - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20 } - }); - - AddStep("show", () => - { - infoWedge.State = Visibility.Visible; - infoWedge.UpdateBeatmap(beatmap); - }); - - AddWaitStep(3); - - AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); - - AddWaitStep(3); - - AddStep("show", () => { infoWedge.State = Visibility.Visible; }); - - foreach (var rulesetInfo in rulesets.AvailableRulesets) - { - var ruleset = rulesetInfo.CreateInstance(); - beatmaps.Add(createTestBeatmap(rulesetInfo)); - - var name = rulesetInfo.ShortName; - selectBeatmap(name); - - // TODO: adjust cases once more info is shown for other gamemodes - switch (ruleset) - { - case OsuRuleset osu: - testOsuBeatmap(osu); - testInfoLabels(5); - break; - default: - testInfoLabels(2); - break; - } - } - - testNullBeatmap(); - } - - private void testOsuBeatmap(OsuRuleset ruleset) - { - AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version"); - AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); - AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist"); - AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Text == $"{ruleset.ShortName}Author")); - } - - private void testInfoLabels(int expectedCount) - { - AddAssert("check infolabels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any()); - AddAssert("check infolabels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount); - } - - private void testNullBeatmap() - { - selectNullBeatmap(); - AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); - AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Title); - AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Artist); - AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); - AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); - } - - private void selectBeatmap(string name) - { - var infoBefore = infoWedge.Info; - - AddStep($"select {name} beatmap", () => - { - beatmap.Value = new TestWorkingBeatmap(beatmaps.First(b => b.BeatmapInfo.Ruleset.ShortName == name)); - infoWedge.UpdateBeatmap(beatmap); - }); - - AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); - } - - private void selectNullBeatmap() - { - AddStep("select null beatmap", () => - { - beatmap.Value = beatmap.Default; - infoWedge.UpdateBeatmap(beatmap); - }); - } - - private Beatmap createTestBeatmap(RulesetInfo ruleset) - { - List objects = new List(); - for (double i = 0; i < 50000; i += 1000) - objects.Add(new HitObject { StartTime = i }); - - return new Beatmap - { - BeatmapInfo = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - AuthorString = $"{ruleset.ShortName}Author", - Artist = $"{ruleset.ShortName}Artist", - Source = $"{ruleset.ShortName}Source", - Title = $"{ruleset.ShortName}Title" - }, - Ruleset = ruleset, - StarDifficulty = 6, - Version = $"{ruleset.ShortName}Version" - }, - HitObjects = objects - }; - } - - private class TestBeatmapInfoWedge : BeatmapInfoWedge - { - public new BufferedWedgeInfo Info => base.Info; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Select; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseBeatmapInfoWedge : OsuTestCase + { + private RulesetStore rulesets; + private TestBeatmapInfoWedge infoWedge; + private readonly List beatmaps = new List(); + private readonly Bindable beatmap = new Bindable(); + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, RulesetStore rulesets) + { + this.rulesets = rulesets; + + beatmap.BindTo(game.Beatmap); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(infoWedge = new TestBeatmapInfoWedge + { + Size = new Vector2(0.5f, 245), + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20 } + }); + + AddStep("show", () => + { + infoWedge.State = Visibility.Visible; + infoWedge.UpdateBeatmap(beatmap); + }); + + AddWaitStep(3); + + AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); + + AddWaitStep(3); + + AddStep("show", () => { infoWedge.State = Visibility.Visible; }); + + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + var ruleset = rulesetInfo.CreateInstance(); + beatmaps.Add(createTestBeatmap(rulesetInfo)); + + var name = rulesetInfo.ShortName; + selectBeatmap(name); + + // TODO: adjust cases once more info is shown for other gamemodes + switch (ruleset) + { + case OsuRuleset osu: + testOsuBeatmap(osu); + testInfoLabels(5); + break; + default: + testInfoLabels(2); + break; + } + } + + testNullBeatmap(); + } + + private void testOsuBeatmap(OsuRuleset ruleset) + { + AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version"); + AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist"); + AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Text == $"{ruleset.ShortName}Author")); + } + + private void testInfoLabels(int expectedCount) + { + AddAssert("check infolabels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any()); + AddAssert("check infolabels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount); + } + + private void testNullBeatmap() + { + selectNullBeatmap(); + AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); + AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); + AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); + } + + private void selectBeatmap(string name) + { + var infoBefore = infoWedge.Info; + + AddStep($"select {name} beatmap", () => + { + beatmap.Value = new TestWorkingBeatmap(beatmaps.First(b => b.BeatmapInfo.Ruleset.ShortName == name)); + infoWedge.UpdateBeatmap(beatmap); + }); + + AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); + } + + private void selectNullBeatmap() + { + AddStep("select null beatmap", () => + { + beatmap.Value = beatmap.Default; + infoWedge.UpdateBeatmap(beatmap); + }); + } + + private Beatmap createTestBeatmap(RulesetInfo ruleset) + { + List objects = new List(); + for (double i = 0; i < 50000; i += 1000) + objects.Add(new HitObject { StartTime = i }); + + return new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + AuthorString = $"{ruleset.ShortName}Author", + Artist = $"{ruleset.ShortName}Artist", + Source = $"{ruleset.ShortName}Source", + Title = $"{ruleset.ShortName}Title" + }, + Ruleset = ruleset, + StarDifficulty = 6, + Version = $"{ruleset.ShortName}Version" + }, + HitObjects = objects + }; + } + + private class TestBeatmapInfoWedge : BeatmapInfoWedge + { + public new BufferedWedgeInfo Info => base.Info; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs index cfeed4b1f0..ffa433ef11 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Game.Graphics; -using osu.Game.Screens.Select.Options; -using OpenTK.Graphics; -using OpenTK.Input; - -namespace osu.Game.Tests.Visual -{ - [Description("bottom beatmap details")] - public class TestCaseBeatmapOptionsOverlay : OsuTestCase - { - public TestCaseBeatmapOptionsOverlay() - { - var overlay = new BeatmapOptionsOverlay(); - - overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, Color4.Purple, null, Key.Number1); - overlay.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, Color4.Purple, null, Key.Number2); - overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.fa_pencil, Color4.Yellow, null, Key.Number3); - overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, Color4.Pink, null, Key.Number4, float.MaxValue); - - Add(overlay); - - AddStep(@"Toggle", overlay.ToggleVisibility); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Game.Graphics; +using osu.Game.Screens.Select.Options; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace osu.Game.Tests.Visual +{ + [Description("bottom beatmap details")] + public class TestCaseBeatmapOptionsOverlay : OsuTestCase + { + public TestCaseBeatmapOptionsOverlay() + { + var overlay = new BeatmapOptionsOverlay(); + + overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, Color4.Purple, null, Key.Number1); + overlay.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, Color4.Purple, null, Key.Number2); + overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.fa_pencil, Color4.Yellow, null, Key.Number3); + overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, Color4.Pink, null, Key.Number4, float.MaxValue); + + Add(overlay); + + AddStep(@"Toggle", overlay.ToggleVisibility); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index 5ce5eb222e..6cb6a342a8 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -1,313 +1,313 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.MathUtils; -using osu.Game.Graphics; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.BeatmapSet.Scores; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Users; -using System.Collections.Generic; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Tests.Visual -{ - [System.ComponentModel.Description("in BeatmapOverlay")] - public class TestCaseBeatmapScoresContainer : OsuTestCase - { - private readonly IEnumerable scores; - private readonly IEnumerable anotherScores; - private readonly OnlineScore topScore; - private readonly Box background; - - public TestCaseBeatmapScoresContainer() - { - Container container; - ScoresContainer scoresContainer; - - Child = container = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0.8f, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - scoresContainer = new ScoresContainer(), - } - }; - - AddStep("scores pack 1", () => scoresContainer.Scores = scores); - AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores); - AddStep("only top score", () => scoresContainer.Scores = new[] { topScore }); - AddStep("remove scores", scoresContainer.CleanAllScores); - AddStep("turn on loading", () => scoresContainer.IsLoading = true); - AddStep("turn off loading", () => scoresContainer.IsLoading = false); - AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); - AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); - - scores = new[] - { - new OnlineScore - { - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - TotalScore = 1234567890, - Accuracy = 1, - }, - new OnlineScore - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new OnlineScore - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new OnlineScore - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - new OnlineScore - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.F, - TotalScore = 123456, - Accuracy = 0.6543, - }, - }; - foreach(var s in scores) - { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - anotherScores = new[] - { - new OnlineScore - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new OnlineScore - { - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - TotalScore = 1234567890, - Accuracy = 1, - }, - new OnlineScore - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.F, - TotalScore = 123456, - Accuracy = 0.6543, - }, - new OnlineScore - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new OnlineScore - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - }; - foreach (var s in anotherScores) - { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - topScore = new OnlineScore - { - User = new User - { - Id = 2705430, - Username = @"Mooha", - Country = new Country - { - FullName = @"France", - FlagName = @"FR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.B, - TotalScore = 987654321, - Accuracy = 0.8487, - }; - topScore.Statistics.Add(HitResult.Great, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Good, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray2; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; +using System.Collections.Generic; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Tests.Visual +{ + [System.ComponentModel.Description("in BeatmapOverlay")] + public class TestCaseBeatmapScoresContainer : OsuTestCase + { + private readonly IEnumerable scores; + private readonly IEnumerable anotherScores; + private readonly OnlineScore topScore; + private readonly Box background; + + public TestCaseBeatmapScoresContainer() + { + Container container; + ScoresContainer scoresContainer; + + Child = container = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Width = 0.8f, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + scoresContainer = new ScoresContainer(), + } + }; + + AddStep("scores pack 1", () => scoresContainer.Scores = scores); + AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores); + AddStep("only top score", () => scoresContainer.Scores = new[] { topScore }); + AddStep("remove scores", scoresContainer.CleanAllScores); + AddStep("turn on loading", () => scoresContainer.IsLoading = true); + AddStep("turn off loading", () => scoresContainer.IsLoading = false); + AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); + AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); + + scores = new[] + { + new OnlineScore + { + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + new OsuModHardRock(), + }, + Rank = ScoreRank.XH, + TotalScore = 1234567890, + Accuracy = 1, + }, + new OnlineScore + { + User = new User + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + }, + Rank = ScoreRank.S, + TotalScore = 1234789, + Accuracy = 0.9997, + }, + new OnlineScore + { + User = new User + { + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + }, + Rank = ScoreRank.B, + TotalScore = 12345678, + Accuracy = 0.9854, + }, + new OnlineScore + { + User = new User + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + }, + Rank = ScoreRank.C, + TotalScore = 1234567, + Accuracy = 0.8765, + }, + new OnlineScore + { + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.F, + TotalScore = 123456, + Accuracy = 0.6543, + }, + }; + foreach(var s in scores) + { + s.Statistics.Add(HitResult.Great, RNG.Next(2000)); + s.Statistics.Add(HitResult.Good, RNG.Next(2000)); + s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + } + + anotherScores = new[] + { + new OnlineScore + { + User = new User + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + }, + Rank = ScoreRank.S, + TotalScore = 1234789, + Accuracy = 0.9997, + }, + new OnlineScore + { + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + new OsuModHardRock(), + }, + Rank = ScoreRank.XH, + TotalScore = 1234567890, + Accuracy = 1, + }, + new OnlineScore + { + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.F, + TotalScore = 123456, + Accuracy = 0.6543, + }, + new OnlineScore + { + User = new User + { + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + }, + Rank = ScoreRank.B, + TotalScore = 12345678, + Accuracy = 0.9854, + }, + new OnlineScore + { + User = new User + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + }, + Rank = ScoreRank.C, + TotalScore = 1234567, + Accuracy = 0.8765, + }, + }; + foreach (var s in anotherScores) + { + s.Statistics.Add(HitResult.Great, RNG.Next(2000)); + s.Statistics.Add(HitResult.Good, RNG.Next(2000)); + s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + } + + topScore = new OnlineScore + { + User = new User + { + Id = 2705430, + Username = @"Mooha", + Country = new Country + { + FullName = @"France", + FlagName = @"FR", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModFlashlight(), + new OsuModHardRock(), + }, + Rank = ScoreRank.B, + TotalScore = 987654321, + Accuracy = 0.8487, + }; + topScore.Statistics.Add(HitResult.Great, RNG.Next(2000)); + topScore.Statistics.Add(HitResult.Good, RNG.Next(2000)); + topScore.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray2; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs index 6605c61026..69955a90c4 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -1,379 +1,379 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Game.Rulesets; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBeatmapSetOverlay : OsuTestCase - { - private readonly BeatmapSetOverlay overlay; - - public TestCaseBeatmapSetOverlay() - { - Add(overlay = new BeatmapSetOverlay()); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - var mania = rulesets.GetRuleset(3); - var taiko = rulesets.GetRuleset(1); - - AddStep(@"show first", () => - { - overlay.ShowBeatmapSet(new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = @"Lachryma ", - Artist = @"Kaneko Chiharu", - Source = @"SOUND VOLTEX III GRAVITY WARS", - Tags = @"sdvx grace the 5th kac original song contest konami bemani", - Author = new User - { - Username = @"Fresh Chicken", - Id = 3984370, - }, - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Preview = @"https://b.ppy.sh/preview/415886.mp3", - PlayCount = 681380, - FavouriteCount = 356, - Submitted = new DateTime(2016, 2, 10), - Ranked = new DateTime(2016, 6, 19), - Status = BeatmapSetOnlineStatus.Ranked, - BPM = 236, - HasVideo = true, - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/415886/covers/cover.jpg?1465651778", - }, - }, - Beatmaps = new List - { - new BeatmapInfo - { - StarDifficulty = 1.36, - Version = @"BASIC", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 6.5f, - OverallDifficulty = 6.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 115000, - CircleCount = 265, - SliderCount = 71, - PlayCount = 47906, - PassCount = 19899, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 2.22, - Version = @"NOVICE", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 7, - OverallDifficulty = 7, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 592, - SliderCount = 62, - PlayCount = 162021, - PassCount = 72116, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 3.49, - Version = @"ADVANCED", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 7.5f, - OverallDifficulty = 7.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 1042, - SliderCount = 79, - PlayCount = 225178, - PassCount = 73001, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 4.24, - Version = @"EXHAUST", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 8, - OverallDifficulty = 8, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 1352, - SliderCount = 69, - PlayCount = 131545, - PassCount = 42703, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 5.26, - Version = @"GRAVITY", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 8.5f, - OverallDifficulty = 8.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 1730, - SliderCount = 115, - PlayCount = 117673, - PassCount = 24241, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - }, - }); - }); - - AddStep(@"show second", () => - { - overlay.ShowBeatmapSet(new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = @"Soumatou Labyrinth", - Artist = @"Yunomi with Momobako&miko", - Tags = @"mmbk.com yuzu__rinrin charlotte", - Author = new User - { - Username = @"komasy", - Id = 1980256, - }, - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Preview = @"https://b.ppy.sh/preview/625493.mp3", - PlayCount = 22996, - FavouriteCount = 58, - Submitted = new DateTime(2016, 6, 11), - Ranked = new DateTime(2016, 7, 12), - Status = BeatmapSetOnlineStatus.Pending, - BPM = 160, - HasVideo = false, - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/625493/covers/cover.jpg?1499167472", - }, - }, - Beatmaps = new List - { - new BeatmapInfo - { - StarDifficulty = 1.40, - Version = @"yzrin's Kantan", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 2, - DrainRate = 7, - OverallDifficulty = 3, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 262, - SliderCount = 0, - PlayCount = 3952, - PassCount = 1373, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 2.23, - Version = @"Futsuu", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 2, - DrainRate = 6, - OverallDifficulty = 4, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 464, - SliderCount = 0, - PlayCount = 4833, - PassCount = 920, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 3.19, - Version = @"Muzukashii", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 2, - DrainRate = 6, - OverallDifficulty = 5, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 712, - SliderCount = 0, - PlayCount = 4405, - PassCount = 854, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 3.97, - Version = @"Charlotte's Oni", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 6, - OverallDifficulty = 5.5f, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 943, - SliderCount = 0, - PlayCount = 3950, - PassCount = 693, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 5.08, - Version = @"Labyrinth Oni", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 5, - OverallDifficulty = 6, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 1068, - SliderCount = 0, - PlayCount = 5856, - PassCount = 1207, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - }, - }); - }); - - AddStep(@"hide", overlay.Hide); - AddStep(@"show without reload", overlay.Show); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseBeatmapSetOverlay : OsuTestCase + { + private readonly BeatmapSetOverlay overlay; + + public TestCaseBeatmapSetOverlay() + { + Add(overlay = new BeatmapSetOverlay()); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + var mania = rulesets.GetRuleset(3); + var taiko = rulesets.GetRuleset(1); + + AddStep(@"show first", () => + { + overlay.ShowBeatmapSet(new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = @"Lachryma ", + Artist = @"Kaneko Chiharu", + Source = @"SOUND VOLTEX III GRAVITY WARS", + Tags = @"sdvx grace the 5th kac original song contest konami bemani", + Author = new User + { + Username = @"Fresh Chicken", + Id = 3984370, + }, + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Preview = @"https://b.ppy.sh/preview/415886.mp3", + PlayCount = 681380, + FavouriteCount = 356, + Submitted = new DateTime(2016, 2, 10), + Ranked = new DateTime(2016, 6, 19), + Status = BeatmapSetOnlineStatus.Ranked, + BPM = 236, + HasVideo = true, + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh/beatmaps/415886/covers/cover.jpg?1465651778", + }, + }, + Beatmaps = new List + { + new BeatmapInfo + { + StarDifficulty = 1.36, + Version = @"BASIC", + Ruleset = mania, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 6.5f, + OverallDifficulty = 6.5f, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 115000, + CircleCount = 265, + SliderCount = 71, + PlayCount = 47906, + PassCount = 19899, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 2.22, + Version = @"NOVICE", + Ruleset = mania, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 7, + OverallDifficulty = 7, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 118000, + CircleCount = 592, + SliderCount = 62, + PlayCount = 162021, + PassCount = 72116, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 3.49, + Version = @"ADVANCED", + Ruleset = mania, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 7.5f, + OverallDifficulty = 7.5f, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 118000, + CircleCount = 1042, + SliderCount = 79, + PlayCount = 225178, + PassCount = 73001, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 4.24, + Version = @"EXHAUST", + Ruleset = mania, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 8, + OverallDifficulty = 8, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 118000, + CircleCount = 1352, + SliderCount = 69, + PlayCount = 131545, + PassCount = 42703, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 5.26, + Version = @"GRAVITY", + Ruleset = mania, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 8.5f, + OverallDifficulty = 8.5f, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 118000, + CircleCount = 1730, + SliderCount = 115, + PlayCount = 117673, + PassCount = 24241, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + }, + }); + }); + + AddStep(@"show second", () => + { + overlay.ShowBeatmapSet(new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = @"Soumatou Labyrinth", + Artist = @"Yunomi with Momobako&miko", + Tags = @"mmbk.com yuzu__rinrin charlotte", + Author = new User + { + Username = @"komasy", + Id = 1980256, + }, + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Preview = @"https://b.ppy.sh/preview/625493.mp3", + PlayCount = 22996, + FavouriteCount = 58, + Submitted = new DateTime(2016, 6, 11), + Ranked = new DateTime(2016, 7, 12), + Status = BeatmapSetOnlineStatus.Pending, + BPM = 160, + HasVideo = false, + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh/beatmaps/625493/covers/cover.jpg?1499167472", + }, + }, + Beatmaps = new List + { + new BeatmapInfo + { + StarDifficulty = 1.40, + Version = @"yzrin's Kantan", + Ruleset = taiko, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 2, + DrainRate = 7, + OverallDifficulty = 3, + ApproachRate = 10, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 193000, + CircleCount = 262, + SliderCount = 0, + PlayCount = 3952, + PassCount = 1373, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 2.23, + Version = @"Futsuu", + Ruleset = taiko, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 2, + DrainRate = 6, + OverallDifficulty = 4, + ApproachRate = 10, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 193000, + CircleCount = 464, + SliderCount = 0, + PlayCount = 4833, + PassCount = 920, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 3.19, + Version = @"Muzukashii", + Ruleset = taiko, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 2, + DrainRate = 6, + OverallDifficulty = 5, + ApproachRate = 10, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 193000, + CircleCount = 712, + SliderCount = 0, + PlayCount = 4405, + PassCount = 854, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 3.97, + Version = @"Charlotte's Oni", + Ruleset = taiko, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 6, + OverallDifficulty = 5.5f, + ApproachRate = 10, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 193000, + CircleCount = 943, + SliderCount = 0, + PlayCount = 3950, + PassCount = 693, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + new BeatmapInfo + { + StarDifficulty = 5.08, + Version = @"Labyrinth Oni", + Ruleset = taiko, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 6, + ApproachRate = 10, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 193000, + CircleCount = 1068, + SliderCount = 0, + PlayCount = 5856, + PassCount = 1207, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }, + }, + }); + }); + + AddStep(@"hide", overlay.Hide); + AddStep(@"show without reload", overlay.Show); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs index e3cef06f2f..73a97c6269 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBreadcrumbs : OsuTestCase - { - private readonly BreadcrumbControl breadcrumbs; - - public TestCaseBreadcrumbs() - { - - Add(breadcrumbs = new BreadcrumbControl - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Width = 0.5f, - }); - - AddStep(@"first", () => breadcrumbs.Current.Value = BreadcrumbTab.Click); - AddStep(@"second", () => breadcrumbs.Current.Value = BreadcrumbTab.The); - AddStep(@"third", () => breadcrumbs.Current.Value = BreadcrumbTab.Circles); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - breadcrumbs.StripColour = colours.Blue; - } - - private enum BreadcrumbTab - { - Click, - The, - Circles, - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseBreadcrumbs : OsuTestCase + { + private readonly BreadcrumbControl breadcrumbs; + + public TestCaseBreadcrumbs() + { + + Add(breadcrumbs = new BreadcrumbControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Width = 0.5f, + }); + + AddStep(@"first", () => breadcrumbs.Current.Value = BreadcrumbTab.Click); + AddStep(@"second", () => breadcrumbs.Current.Value = BreadcrumbTab.The); + AddStep(@"third", () => breadcrumbs.Current.Value = BreadcrumbTab.Circles); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + breadcrumbs.StripColour = colours.Blue; + } + + private enum BreadcrumbTab + { + Click, + The, + Circles, + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs index 51b8c61963..a6a6130a8f 100644 --- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs @@ -1,91 +1,91 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Timing; -using osu.Game.Beatmaps.Timing; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBreakOverlay : OsuTestCase - { - private readonly BreakOverlay breakOverlay; - - public TestCaseBreakOverlay() - { - Clock = new FramedClock(); - - Child = breakOverlay = new BreakOverlay(true); - - AddStep("2s break", () => startBreak(2000)); - AddStep("5s break", () => startBreak(5000)); - AddStep("10s break", () => startBreak(10000)); - AddStep("15s break", () => startBreak(15000)); - AddStep("2s, 2s", startMultipleBreaks); - AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks); - } - - private void startBreak(double duration) - { - breakOverlay.Breaks = new List - { - new BreakPeriod - { - StartTime = Clock.CurrentTime, - EndTime = Clock.CurrentTime + duration, - } - }; - } - - private void startMultipleBreaks() - { - double currentTime = Clock.CurrentTime; - - breakOverlay.Breaks = new List - { - new BreakPeriod - { - StartTime = currentTime, - EndTime = currentTime + 2000, - }, - new BreakPeriod - { - StartTime = currentTime + 4000, - EndTime = currentTime + 6000, - } - }; - } - - private void startAnotherMultipleBreaks() - { - double currentTime = Clock.CurrentTime; - - breakOverlay.Breaks = new List - { - new BreakPeriod // Duration is less than 650 - too short to appear - { - StartTime = currentTime, - EndTime = currentTime + 500, - }, - new BreakPeriod - { - StartTime = currentTime + 1500, - EndTime = currentTime + 2200, - }, - new BreakPeriod - { - StartTime = currentTime + 3200, - EndTime = currentTime + 4200, - }, - new BreakPeriod - { - StartTime = currentTime + 5200, - EndTime = currentTime + 7200, - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Timing; +using osu.Game.Beatmaps.Timing; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseBreakOverlay : OsuTestCase + { + private readonly BreakOverlay breakOverlay; + + public TestCaseBreakOverlay() + { + Clock = new FramedClock(); + + Child = breakOverlay = new BreakOverlay(true); + + AddStep("2s break", () => startBreak(2000)); + AddStep("5s break", () => startBreak(5000)); + AddStep("10s break", () => startBreak(10000)); + AddStep("15s break", () => startBreak(15000)); + AddStep("2s, 2s", startMultipleBreaks); + AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks); + } + + private void startBreak(double duration) + { + breakOverlay.Breaks = new List + { + new BreakPeriod + { + StartTime = Clock.CurrentTime, + EndTime = Clock.CurrentTime + duration, + } + }; + } + + private void startMultipleBreaks() + { + double currentTime = Clock.CurrentTime; + + breakOverlay.Breaks = new List + { + new BreakPeriod + { + StartTime = currentTime, + EndTime = currentTime + 2000, + }, + new BreakPeriod + { + StartTime = currentTime + 4000, + EndTime = currentTime + 6000, + } + }; + } + + private void startAnotherMultipleBreaks() + { + double currentTime = Clock.CurrentTime; + + breakOverlay.Breaks = new List + { + new BreakPeriod // Duration is less than 650 - too short to appear + { + StartTime = currentTime, + EndTime = currentTime + 500, + }, + new BreakPeriod + { + StartTime = currentTime + 1500, + EndTime = currentTime + 2200, + }, + new BreakPeriod + { + StartTime = currentTime + 3200, + EndTime = currentTime + 4200, + }, + new BreakPeriod + { + StartTime = currentTime + 5200, + EndTime = currentTime + 7200, + } + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs index 93740593cb..5eb81cdf9f 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Shapes; -using osu.Game.Screens.Menu; -using OpenTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseButtonSystem : OsuTestCase - { - public TestCaseButtonSystem() - { - OsuLogo logo; - ButtonSystem buttons; - - Children = new Drawable[] - { - new Box - { - Colour = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke), - RelativeSizeAxes = Axes.Both, - }, - buttons = new ButtonSystem(), - logo = new OsuLogo() - }; - - buttons.SetOsuLogo(logo); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Shapes; +using osu.Game.Screens.Menu; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseButtonSystem : OsuTestCase + { + public TestCaseButtonSystem() + { + OsuLogo logo; + ButtonSystem buttons; + + Children = new Drawable[] + { + new Box + { + Colour = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke), + RelativeSizeAxes = Axes.Both, + }, + buttons = new ButtonSystem(), + logo = new OsuLogo() + }; + + buttons.SetOsuLogo(logo); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index 048106da26..c03b12bdc1 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [Description("Testing chat api and overlay")] - public class TestCaseChatDisplay : OsuTestCase - { - public TestCaseChatDisplay() - { - Add(new ChatOverlay - { - State = Visibility.Visible - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual +{ + [Description("Testing chat api and overlay")] + public class TestCaseChatDisplay : OsuTestCase + { + public TestCaseChatDisplay() + { + Add(new ChatOverlay + { + State = Visibility.Visible + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index cf2e0081f6..dbae467fd3 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -1,218 +1,218 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Chat; -using osu.Game.Users; -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseChatLink : OsuTestCase - { - private readonly TestChatLineContainer textContainer; - private Color4 linkColour; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(ChatLine), - typeof(Message), - typeof(LinkFlowContainer), - typeof(DummyEchoMessage), - typeof(LocalEchoMessage), - typeof(MessageFormatter) - }; - - private DependencyContainer dependencies; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - - public TestCaseChatLink() - { - Add(textContainer = new TestChatLineContainer - { - Padding = new MarginPadding { Left = 20, Right = 20 }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - linkColour = colours.Blue; - - var chatManager = new ChannelManager(); - chatManager.AvailableChannels.Add(new Channel { Name = "#english"}); - chatManager.AvailableChannels.Add(new Channel { Name = "#japanese" }); - dependencies.Cache(chatManager); - - dependencies.Cache(new ChatOverlay()); - - testLinksGeneral(); - testEcho(); - } - - private void clear() => AddStep("clear messages", textContainer.Clear); - - private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) - { - int index = textContainer.Count + 1; - var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); - textContainer.Add(newLine); - - AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - AddAssert($"msg #{index} has the right action", hasExpectedActions); - AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); - AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); - - bool hasExpectedActions() - { - var expectedActionsList = expectedActions.ToList(); - - if (expectedActionsList.Count != newLine.Message.Links.Count) - return false; - - for (int i = 0; i < newLine.Message.Links.Count; i++) - { - var action = newLine.Message.Links[i].Action; - if (action != expectedActions[i]) return false; - } - - return true; - } - - bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font == "Exo2.0-MediumItalic"); - - bool isShowingLinks() - { - bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); - - Color4 textColour = isAction && hasBackground ? OsuColour.FromHex(newLine.Message.Sender.Colour) : Color4.White; - - var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); - var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); - - return linkSprites.All(d => d.Colour == linkColour) - && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); - } - } - - private void testLinksGeneral() - { - addMessageWithChecks("test!"); - addMessageWithChecks("osu.ppy.sh!"); - addMessageWithChecks("https://osu.ppy.sh!", 1, expectedActions: LinkAction.External); - addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); - addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); - addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[osu forums](https://osu.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); - addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); - addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); - addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, - expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); - // note that there's 0 links here (they get removed if a channel is not found) - addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); - addMessageWithChecks("I am important!", 0, false, true); - addMessageWithChecks("feels important", 0, true, true); - addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); - addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); - addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); - } - - private void testEcho() - { - int echoCounter = 0; - - addEchoWithWait("sent!", "received!"); - addEchoWithWait("https://osu.ppy.sh/home", null, 500); - addEchoWithWait("[https://osu.ppy.sh/forum let's try multiple words too!]"); - addEchoWithWait("(long loading times! clickable while loading?)[https://osu.ppy.sh/home]", null, 5000); - - void addEchoWithWait(string text, string completeText = null, double delay = 250) - { - var newLine = new ChatLine(new DummyEchoMessage(text)); - - AddStep($"send msg #{++echoCounter} after {delay}ms", () => - { - textContainer.Add(newLine); - Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); - }); - - AddUntilStep(() => textContainer.All(line => line.Message is DummyMessage), $"wait for msg #{echoCounter}"); - } - } - - private class DummyEchoMessage : LocalEchoMessage - { - public DummyEchoMessage(string text) - { - Content = text; - Timestamp = DateTimeOffset.Now; - Sender = DummyMessage.TEST_SENDER; - } - } - - private class DummyMessage : Message - { - private static long messageCounter; - - internal static readonly User TEST_SENDER_BACKGROUND = new User - { - Username = @"i-am-important", - Id = 42, - Colour = "#250cc9", - }; - - internal static readonly User TEST_SENDER = new User - { - Username = @"Somebody", - Id = 1, - }; - - public new DateTimeOffset Timestamp = DateTimeOffset.Now; - - public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0) - : base(messageCounter++) - { - Content = text; - IsAction = isAction; - Sender = new User - { - Username = $"User {number}", - Id = number, - Colour = isImportant ? "#250cc9" : null, - }; - } - } - - private class TestChatLineContainer : FillFlowContainer - { - protected override int Compare(Drawable x, Drawable y) - { - var xC = (ChatLine)x; - var yC = (ChatLine)y; - - return xC.Message.CompareTo(yC.Message); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat; +using osu.Game.Users; +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseChatLink : OsuTestCase + { + private readonly TestChatLineContainer textContainer; + private Color4 linkColour; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ChatLine), + typeof(Message), + typeof(LinkFlowContainer), + typeof(DummyEchoMessage), + typeof(LocalEchoMessage), + typeof(MessageFormatter) + }; + + private DependencyContainer dependencies; + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + + public TestCaseChatLink() + { + Add(textContainer = new TestChatLineContainer + { + Padding = new MarginPadding { Left = 20, Right = 20 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + linkColour = colours.Blue; + + var chatManager = new ChannelManager(); + chatManager.AvailableChannels.Add(new Channel { Name = "#english"}); + chatManager.AvailableChannels.Add(new Channel { Name = "#japanese" }); + dependencies.Cache(chatManager); + + dependencies.Cache(new ChatOverlay()); + + testLinksGeneral(); + testEcho(); + } + + private void clear() => AddStep("clear messages", textContainer.Clear); + + private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) + { + int index = textContainer.Count + 1; + var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); + textContainer.Add(newLine); + + AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); + AddAssert($"msg #{index} has the right action", hasExpectedActions); + AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); + AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); + + bool hasExpectedActions() + { + var expectedActionsList = expectedActions.ToList(); + + if (expectedActionsList.Count != newLine.Message.Links.Count) + return false; + + for (int i = 0; i < newLine.Message.Links.Count; i++) + { + var action = newLine.Message.Links[i].Action; + if (action != expectedActions[i]) return false; + } + + return true; + } + + bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font == "Exo2.0-MediumItalic"); + + bool isShowingLinks() + { + bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); + + Color4 textColour = isAction && hasBackground ? OsuColour.FromHex(newLine.Message.Sender.Colour) : Color4.White; + + var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); + var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); + + return linkSprites.All(d => d.Colour == linkColour) + && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); + } + } + + private void testLinksGeneral() + { + addMessageWithChecks("test!"); + addMessageWithChecks("osu.ppy.sh!"); + addMessageWithChecks("https://osu.ppy.sh!", 1, expectedActions: LinkAction.External); + addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); + addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[osu forums](https://osu.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); + addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); + addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, + expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); + // note that there's 0 links here (they get removed if a channel is not found) + addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); + addMessageWithChecks("I am important!", 0, false, true); + addMessageWithChecks("feels important", 0, true, true); + addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); + addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); + addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); + } + + private void testEcho() + { + int echoCounter = 0; + + addEchoWithWait("sent!", "received!"); + addEchoWithWait("https://osu.ppy.sh/home", null, 500); + addEchoWithWait("[https://osu.ppy.sh/forum let's try multiple words too!]"); + addEchoWithWait("(long loading times! clickable while loading?)[https://osu.ppy.sh/home]", null, 5000); + + void addEchoWithWait(string text, string completeText = null, double delay = 250) + { + var newLine = new ChatLine(new DummyEchoMessage(text)); + + AddStep($"send msg #{++echoCounter} after {delay}ms", () => + { + textContainer.Add(newLine); + Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); + }); + + AddUntilStep(() => textContainer.All(line => line.Message is DummyMessage), $"wait for msg #{echoCounter}"); + } + } + + private class DummyEchoMessage : LocalEchoMessage + { + public DummyEchoMessage(string text) + { + Content = text; + Timestamp = DateTimeOffset.Now; + Sender = DummyMessage.TEST_SENDER; + } + } + + private class DummyMessage : Message + { + private static long messageCounter; + + internal static readonly User TEST_SENDER_BACKGROUND = new User + { + Username = @"i-am-important", + Id = 42, + Colour = "#250cc9", + }; + + internal static readonly User TEST_SENDER = new User + { + Username = @"Somebody", + Id = 1, + }; + + public new DateTimeOffset Timestamp = DateTimeOffset.Now; + + public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0) + : base(messageCounter++) + { + Content = text; + IsAction = isAction; + Sender = new User + { + Username = $"User {number}", + Id = number, + Colour = isImportant ? "#250cc9" : null, + }; + } + } + + private class TestChatLineContainer : FillFlowContainer + { + protected override int Compare(Drawable x, Drawable y) + { + var xC = (ChatLine)x; + var yC = (ChatLine)y; + + return xC.Message.CompareTo(yC.Message); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/TestCaseContextMenu.cs index 45c12cf4af..80505b219b 100644 --- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/TestCaseContextMenu.cs @@ -1,98 +1,98 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Graphics.Cursor; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseContextMenu : OsuTestCase - { - private const int start_time = 0; - private const int duration = 1000; - - private readonly Container container; - - public TestCaseContextMenu() - { - Add(new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - container = new MyContextMenuContainer - { - Size = new Vector2(200), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Green, - } - }, - new AnotherContextMenuContainer - { - Size = new Vector2(200), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Red, - } - } - } - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // Move box along a square trajectory - container.Loop(c => c - .MoveTo(new Vector2(0, 100), duration).Then() - .MoveTo(new Vector2(100, 100), duration).Then() - .MoveTo(new Vector2(100, 0), duration).Then() - .MoveTo(Vector2.Zero, duration) - ); - } - - private class MyContextMenuContainer : Container, IHasContextMenu - { - public MenuItem[] ContextMenuItems => new MenuItem[] - { - new OsuMenuItem(@"Some option"), - new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted), - new OsuMenuItem(@"Another option"), - new OsuMenuItem(@"Choose me please"), - new OsuMenuItem(@"And me too"), - new OsuMenuItem(@"Trying to fill"), - new OsuMenuItem(@"Destructive option", MenuItemType.Destructive), - }; - } - - private class AnotherContextMenuContainer : Container, IHasContextMenu - { - public MenuItem[] ContextMenuItems => new MenuItem[] - { - new OsuMenuItem(@"Simple option"), - new OsuMenuItem(@"Simple very very long option"), - new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)), - new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)), - new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)), - new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Graphics.Cursor; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseContextMenu : OsuTestCase + { + private const int start_time = 0; + private const int duration = 1000; + + private readonly Container container; + + public TestCaseContextMenu() + { + Add(new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + container = new MyContextMenuContainer + { + Size = new Vector2(200), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + } + }, + new AnotherContextMenuContainer + { + Size = new Vector2(200), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + } + } + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Move box along a square trajectory + container.Loop(c => c + .MoveTo(new Vector2(0, 100), duration).Then() + .MoveTo(new Vector2(100, 100), duration).Then() + .MoveTo(new Vector2(100, 0), duration).Then() + .MoveTo(Vector2.Zero, duration) + ); + } + + private class MyContextMenuContainer : Container, IHasContextMenu + { + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem(@"Some option"), + new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted), + new OsuMenuItem(@"Another option"), + new OsuMenuItem(@"Choose me please"), + new OsuMenuItem(@"And me too"), + new OsuMenuItem(@"Trying to fill"), + new OsuMenuItem(@"Destructive option", MenuItemType.Destructive), + }; + } + + private class AnotherContextMenuContainer : Container, IHasContextMenu + { + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem(@"Simple option"), + new OsuMenuItem(@"Simple very very long option"), + new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)), + new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)), + new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)), + new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)), + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs index 4f4fdbeb5b..977f241f7a 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/TestCaseCursors.cs @@ -1,261 +1,261 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseCursors : ManualInputManagerTestCase - { - private readonly CursorOverrideContainer cursorOverrideContainer; - private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; - - public TestCaseCursors() - { - Child = cursorOverrideContainer = new CursorOverrideContainer - { - RelativeSizeAxes = Axes.Both, - Children = new[] - { - // Middle user - cursorBoxes[0] = new CustomCursorBox(Color4.Green) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.5f), - }, - // Top-left user - cursorBoxes[1] = new CustomCursorBox(Color4.Blue) - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Bottom-right user - cursorBoxes[2] = new CustomCursorBox(Color4.Red) - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Bottom-left local - cursorBoxes[3] = new CustomCursorBox(Color4.Magenta, false) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Top-right local - cursorBoxes[4] = new CustomCursorBox(Color4.Cyan, false) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f) - }, - // Left-local - cursorBoxes[5] = new CustomCursorBox(Color4.Yellow, false) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.2f, 1), - }, - } - }; - - AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b)); - - testUserCursor(); - testLocalCursor(); - testUserCursorOverride(); - testMultipleLocalCursors(); - ReturnUserInput(); - } - - /// - /// -- Green Box -- - /// Tests whether hovering in and out of a drawable that provides the user cursor (green) - /// results in the correct visibility state for that cursor. - /// - private void testUserCursor() - { - AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); - AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); - AddStep("Move out", moveOut); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); - } - - /// - /// -- Purple Box -- - /// Tests whether hovering in and out of a drawable that provides a local cursor (purple) - /// results in the correct visibility and state for that cursor. - /// - private void testLocalCursor() - { - AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); - AddAssert("Check global cursor at mouse", () => checkAtMouse(cursorOverrideContainer.Cursor)); - AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); - } - - /// - /// -- Blue-Green Box Boundary -- - /// Tests whether overriding a user cursor (green) with another user cursor (blue) - /// results in the correct visibility and states for the cursors. - /// - private void testUserCursorOverride() - { - AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); - AddStep("Move out", moveOut); - AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor)); - } - - /// - /// -- Yellow-Purple Box Boundary -- - /// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time. - /// - private void testMultipleLocalCursors() - { - AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); - AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - } - - /// - /// -- Yellow-Blue Box Boundary -- - /// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue). - /// - private void testUserOverrideWithLocal() - { - AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); - AddStep("Move out", moveOut); - AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - } - - /// - /// Moves the cursor to a point not covered by any cursor containers. - /// - private void moveOut() - => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.TopLeft.Y)); - - /// - /// Checks if a cursor is visible. - /// - /// The cursor to check. - private bool checkVisible(CursorContainer cursorContainer) => cursorContainer.State == Visibility.Visible; - - /// - /// Checks if a cursor is at the current inputmanager screen position. - /// - /// The cursor to check. - private bool checkAtMouse(CursorContainer cursorContainer) - => Precision.AlmostEquals(InputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition)); - - private class CustomCursorBox : Container, IProvideCursor - { - public bool SmoothTransition; - - public CursorContainer Cursor { get; } - public bool ProvidingUserCursor { get; } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor; - - private readonly Box background; - - public CustomCursorBox(Color4 cursorColour, bool providesUserCursor = true) - { - ProvidingUserCursor = providesUserCursor; - - Colour = cursorColour; - Masking = true; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = providesUserCursor ? "User cursor" : "Local cursor" - }, - Cursor = new TestCursorContainer - { - State = providesUserCursor ? Visibility.Hidden : Visibility.Visible, - } - }; - } - - protected override bool OnHover(InputState state) - { - background.FadeTo(0.4f, 250, Easing.OutQuint); - return false; - } - - protected override void OnHoverLost(InputState state) - { - background.FadeTo(0.1f, 250); - base.OnHoverLost(state); - } - } - - private class TestCursorContainer : CursorContainer - { - protected override Drawable CreateCursor() => new TestCursor(); - - private class TestCursor : CircularContainer - { - public TestCursor() - { - Origin = Anchor.Centre; - - Size = new Vector2(50); - Masking = true; - - Blending = BlendingMode.Additive; - Alpha = 0.5f; - - Child = new Box { RelativeSizeAxes = Axes.Both }; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseCursors : ManualInputManagerTestCase + { + private readonly CursorOverrideContainer cursorOverrideContainer; + private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; + + public TestCaseCursors() + { + Child = cursorOverrideContainer = new CursorOverrideContainer + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + // Middle user + cursorBoxes[0] = new CustomCursorBox(Color4.Green) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + }, + // Top-left user + cursorBoxes[1] = new CustomCursorBox(Color4.Blue) + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Bottom-right user + cursorBoxes[2] = new CustomCursorBox(Color4.Red) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Bottom-left local + cursorBoxes[3] = new CustomCursorBox(Color4.Magenta, false) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Top-right local + cursorBoxes[4] = new CustomCursorBox(Color4.Cyan, false) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f) + }, + // Left-local + cursorBoxes[5] = new CustomCursorBox(Color4.Yellow, false) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.2f, 1), + }, + } + }; + + AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b)); + + testUserCursor(); + testLocalCursor(); + testUserCursorOverride(); + testMultipleLocalCursors(); + ReturnUserInput(); + } + + /// + /// -- Green Box -- + /// Tests whether hovering in and out of a drawable that provides the user cursor (green) + /// results in the correct visibility state for that cursor. + /// + private void testUserCursor() + { + AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); + AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); + AddStep("Move out", moveOut); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); + } + + /// + /// -- Purple Box -- + /// Tests whether hovering in and out of a drawable that provides a local cursor (purple) + /// results in the correct visibility and state for that cursor. + /// + private void testLocalCursor() + { + AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); + AddAssert("Check global cursor at mouse", () => checkAtMouse(cursorOverrideContainer.Cursor)); + AddStep("Move out", moveOut); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); + } + + /// + /// -- Blue-Green Box Boundary -- + /// Tests whether overriding a user cursor (green) with another user cursor (blue) + /// results in the correct visibility and states for the cursors. + /// + private void testUserCursorOverride() + { + AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); + AddStep("Move out", moveOut); + AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor)); + } + + /// + /// -- Yellow-Purple Box Boundary -- + /// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time. + /// + private void testMultipleLocalCursors() + { + AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); + AddStep("Move out", moveOut); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + } + + /// + /// -- Yellow-Blue Box Boundary -- + /// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue). + /// + private void testUserOverrideWithLocal() + { + AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); + AddStep("Move out", moveOut); + AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + } + + /// + /// Moves the cursor to a point not covered by any cursor containers. + /// + private void moveOut() + => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.TopLeft.Y)); + + /// + /// Checks if a cursor is visible. + /// + /// The cursor to check. + private bool checkVisible(CursorContainer cursorContainer) => cursorContainer.State == Visibility.Visible; + + /// + /// Checks if a cursor is at the current inputmanager screen position. + /// + /// The cursor to check. + private bool checkAtMouse(CursorContainer cursorContainer) + => Precision.AlmostEquals(InputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition)); + + private class CustomCursorBox : Container, IProvideCursor + { + public bool SmoothTransition; + + public CursorContainer Cursor { get; } + public bool ProvidingUserCursor { get; } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor; + + private readonly Box background; + + public CustomCursorBox(Color4 cursorColour, bool providesUserCursor = true) + { + ProvidingUserCursor = providesUserCursor; + + Colour = cursorColour; + Masking = true; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = providesUserCursor ? "User cursor" : "Local cursor" + }, + Cursor = new TestCursorContainer + { + State = providesUserCursor ? Visibility.Hidden : Visibility.Visible, + } + }; + } + + protected override bool OnHover(InputState state) + { + background.FadeTo(0.4f, 250, Easing.OutQuint); + return false; + } + + protected override void OnHoverLost(InputState state) + { + background.FadeTo(0.1f, 250); + base.OnHoverLost(state); + } + } + + private class TestCursorContainer : CursorContainer + { + protected override Drawable CreateCursor() => new TestCursor(); + + private class TestCursor : CircularContainer + { + public TestCursor() + { + Origin = Anchor.Centre; + + Size = new Vector2(50); + Masking = true; + + Blending = BlendingMode.Additive; + Alpha = 0.5f; + + Child = new Box { RelativeSizeAxes = Axes.Both }; + } + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs index e9512b29f7..f0907ed28c 100644 --- a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs @@ -1,75 +1,75 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Graphics; -using osu.Game.Overlays; -using osu.Game.Overlays.Dialog; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseDialogOverlay : OsuTestCase - { - public TestCaseDialogOverlay() - { - DialogOverlay overlay; - - Add(overlay = new DialogOverlay()); - - AddStep("dialog #1", () => overlay.Push(new PopupDialog - { - Icon = FontAwesome.fa_trash_o, - HeaderText = @"Confirm deletion of", - BodyText = @"Ayase Rie - Yuima-ru*World TVver.", - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"I never want to see this again.", - Action = () => System.Console.WriteLine(@"OK"), - }, - new PopupDialogCancelButton - { - Text = @"Firetruck, I still want quick ranks!", - Action = () => System.Console.WriteLine(@"Cancel"), - }, - }, - })); - - AddStep("dialog #2", () => overlay.Push(new PopupDialog - { - Icon = FontAwesome.fa_gear, - HeaderText = @"What do you want to do with", - BodyText = "Camellia as \"Bang Riot\" - Blastix Riotz", - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Manage collections", - }, - new PopupDialogOkButton - { - Text = @"Delete...", - }, - new PopupDialogOkButton - { - Text = @"Remove from unplayed", - }, - new PopupDialogOkButton - { - Text = @"Clear local scores", - }, - new PopupDialogOkButton - { - Text = @"Edit", - }, - new PopupDialogCancelButton - { - Text = @"Cancel", - }, - }, - })); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseDialogOverlay : OsuTestCase + { + public TestCaseDialogOverlay() + { + DialogOverlay overlay; + + Add(overlay = new DialogOverlay()); + + AddStep("dialog #1", () => overlay.Push(new PopupDialog + { + Icon = FontAwesome.fa_trash_o, + HeaderText = @"Confirm deletion of", + BodyText = @"Ayase Rie - Yuima-ru*World TVver.", + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"I never want to see this again.", + Action = () => System.Console.WriteLine(@"OK"), + }, + new PopupDialogCancelButton + { + Text = @"Firetruck, I still want quick ranks!", + Action = () => System.Console.WriteLine(@"Cancel"), + }, + }, + })); + + AddStep("dialog #2", () => overlay.Push(new PopupDialog + { + Icon = FontAwesome.fa_gear, + HeaderText = @"What do you want to do with", + BodyText = "Camellia as \"Bang Riot\" - Blastix Riotz", + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Manage collections", + }, + new PopupDialogOkButton + { + Text = @"Delete...", + }, + new PopupDialogOkButton + { + Text = @"Remove from unplayed", + }, + new PopupDialogOkButton + { + Text = @"Clear local scores", + }, + new PopupDialogOkButton + { + Text = @"Edit", + }, + new PopupDialogCancelButton + { + Text = @"Cancel", + }, + }, + })); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseDirect.cs b/osu.Game.Tests/Visual/TestCaseDirect.cs index 3f3dbb0bca..2f45c87161 100644 --- a/osu.Game.Tests/Visual/TestCaseDirect.cs +++ b/osu.Game.Tests/Visual/TestCaseDirect.cs @@ -1,223 +1,223 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Game.Rulesets; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseDirect : OsuTestCase - { - private DirectOverlay direct; - private RulesetStore rulesets; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(direct = new DirectOverlay()); - newBeatmaps(); - - AddStep(@"toggle", direct.ToggleVisibility); - AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13)); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - - private void newBeatmaps() - { - var ruleset = rulesets.GetRuleset(0); - - direct.BeatmapSets = new[] - { - new BeatmapSetInfo - { - OnlineBeatmapSetID = 578332, - Metadata = new BeatmapMetadata - { - Title = @"OrVid", - Artist = @"An", - AuthorString = @"RLC", - Source = @"", - Tags = @"acuticnotes an-fillnote revid tear tearvid encrpted encryption axi axivid quad her hervid recoll", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/578332/covers/card.jpg?1494591390", - Cover = @"https://assets.ppy.sh/beatmaps/578332/covers/cover.jpg?1494591390", - }, - Preview = @"https://b.ppy.sh/preview/578332.mp3", - PlayCount = 97, - FavouriteCount = 72, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 5.35f, - Metadata = new BeatmapMetadata(), - }, - }, - }, - new BeatmapSetInfo - { - OnlineBeatmapSetID = 599627, - Metadata = new BeatmapMetadata - { - Title = @"tiny lamp", - Artist = @"fhana", - AuthorString = @"Sotarks", - Source = @"ぎんぎつね", - Tags = @"lantis junichi sato yuxuki waga kevin mitsunaga towana gingitsune opening op full ver version kalibe collab collaboration", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/599627/covers/card.jpg?1494539318", - Cover = @"https://assets.ppy.sh/beatmaps/599627/covers/cover.jpg?1494539318", - }, - Preview = @"https//b.ppy.sh/preview/599627.mp3", - PlayCount = 3082, - FavouriteCount = 14, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 5.81f, - Metadata = new BeatmapMetadata(), - }, - }, - }, - new BeatmapSetInfo - { - OnlineBeatmapSetID = 513268, - Metadata = new BeatmapMetadata - { - Title = @"At Gwanghwamun", - Artist = @"KYUHYUN", - AuthorString = @"Cerulean Veyron", - Source = @"", - Tags = @"soul ballad kh super junior sj suju 슈퍼주니어 kt뮤직 sm엔터테인먼트 s.m.entertainment kt music 1st mini album ep", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/513268/covers/card.jpg?1494502863", - Cover = @"https://assets.ppy.sh/beatmaps/513268/covers/cover.jpg?1494502863", - }, - Preview = @"https//b.ppy.sh/preview/513268.mp3", - PlayCount = 2762, - FavouriteCount = 15, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 0.9f, - Metadata = new BeatmapMetadata(), - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 1.1f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 2.02f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 3.49f, - }, - }, - }, - new BeatmapSetInfo - { - OnlineBeatmapSetID = 586841, - Metadata = new BeatmapMetadata - { - Title = @"RHAPSODY OF BLUE SKY", - Artist = @"fhana", - AuthorString = @"[Kamiya]", - Source = @"小林さんちのメイドラゴン", - Tags = @"kobayashi san chi no maidragon aozora no opening anime maid dragon oblivion karen dynamix imoutosan pata-mon gxytcgxytc", - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Card = @"https://assets.ppy.sh/beatmaps/586841/covers/card.jpg?1494052741", - Cover = @"https://assets.ppy.sh/beatmaps/586841/covers/cover.jpg?1494052741", - }, - Preview = @"https//b.ppy.sh/preview/586841.mp3", - PlayCount = 62317, - FavouriteCount = 161, - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 1.26f, - Metadata = new BeatmapMetadata(), - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 2.01f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 2.87f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 3.76f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 3.93f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 4.37f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 5.13f, - }, - new BeatmapInfo - { - Ruleset = ruleset, - StarDifficulty = 5.42f, - }, - }, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Rulesets; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseDirect : OsuTestCase + { + private DirectOverlay direct; + private RulesetStore rulesets; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(direct = new DirectOverlay()); + newBeatmaps(); + + AddStep(@"toggle", direct.ToggleVisibility); + AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13)); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + + private void newBeatmaps() + { + var ruleset = rulesets.GetRuleset(0); + + direct.BeatmapSets = new[] + { + new BeatmapSetInfo + { + OnlineBeatmapSetID = 578332, + Metadata = new BeatmapMetadata + { + Title = @"OrVid", + Artist = @"An", + AuthorString = @"RLC", + Source = @"", + Tags = @"acuticnotes an-fillnote revid tear tearvid encrpted encryption axi axivid quad her hervid recoll", + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Card = @"https://assets.ppy.sh/beatmaps/578332/covers/card.jpg?1494591390", + Cover = @"https://assets.ppy.sh/beatmaps/578332/covers/cover.jpg?1494591390", + }, + Preview = @"https://b.ppy.sh/preview/578332.mp3", + PlayCount = 97, + FavouriteCount = 72, + }, + Beatmaps = new List + { + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 5.35f, + Metadata = new BeatmapMetadata(), + }, + }, + }, + new BeatmapSetInfo + { + OnlineBeatmapSetID = 599627, + Metadata = new BeatmapMetadata + { + Title = @"tiny lamp", + Artist = @"fhana", + AuthorString = @"Sotarks", + Source = @"ぎんぎつね", + Tags = @"lantis junichi sato yuxuki waga kevin mitsunaga towana gingitsune opening op full ver version kalibe collab collaboration", + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Card = @"https://assets.ppy.sh/beatmaps/599627/covers/card.jpg?1494539318", + Cover = @"https://assets.ppy.sh/beatmaps/599627/covers/cover.jpg?1494539318", + }, + Preview = @"https//b.ppy.sh/preview/599627.mp3", + PlayCount = 3082, + FavouriteCount = 14, + }, + Beatmaps = new List + { + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 5.81f, + Metadata = new BeatmapMetadata(), + }, + }, + }, + new BeatmapSetInfo + { + OnlineBeatmapSetID = 513268, + Metadata = new BeatmapMetadata + { + Title = @"At Gwanghwamun", + Artist = @"KYUHYUN", + AuthorString = @"Cerulean Veyron", + Source = @"", + Tags = @"soul ballad kh super junior sj suju 슈퍼주니어 kt뮤직 sm엔터테인먼트 s.m.entertainment kt music 1st mini album ep", + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Card = @"https://assets.ppy.sh/beatmaps/513268/covers/card.jpg?1494502863", + Cover = @"https://assets.ppy.sh/beatmaps/513268/covers/cover.jpg?1494502863", + }, + Preview = @"https//b.ppy.sh/preview/513268.mp3", + PlayCount = 2762, + FavouriteCount = 15, + }, + Beatmaps = new List + { + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 0.9f, + Metadata = new BeatmapMetadata(), + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 1.1f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 2.02f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 3.49f, + }, + }, + }, + new BeatmapSetInfo + { + OnlineBeatmapSetID = 586841, + Metadata = new BeatmapMetadata + { + Title = @"RHAPSODY OF BLUE SKY", + Artist = @"fhana", + AuthorString = @"[Kamiya]", + Source = @"小林さんちのメイドラゴン", + Tags = @"kobayashi san chi no maidragon aozora no opening anime maid dragon oblivion karen dynamix imoutosan pata-mon gxytcgxytc", + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Card = @"https://assets.ppy.sh/beatmaps/586841/covers/card.jpg?1494052741", + Cover = @"https://assets.ppy.sh/beatmaps/586841/covers/cover.jpg?1494052741", + }, + Preview = @"https//b.ppy.sh/preview/586841.mp3", + PlayCount = 62317, + FavouriteCount = 161, + }, + Beatmaps = new List + { + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 1.26f, + Metadata = new BeatmapMetadata(), + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 2.01f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 2.87f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 3.76f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 3.93f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 4.37f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 5.13f, + }, + new BeatmapInfo + { + Ruleset = ruleset, + StarDifficulty = 5.42f, + }, + }, + }, + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs index 4268fd305e..25f8ba06c4 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs +++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs @@ -1,132 +1,132 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; -using osu.Game.Screens.Multiplayer; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseDrawableRoom : OsuTestCase - { - private RulesetStore rulesets; - - protected override void LoadComplete() - { - base.LoadComplete(); - - DrawableRoom first; - Add(new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - Width = 580f, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - first = new DrawableRoom(new Room - { - Name = { Value = @"Great Room Right Here" }, - Host = { Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" } } }, - Status = { Value = new RoomStatusOpen() }, - Type = { Value = new GameTypeTeamVersus() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 4.65, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata - { - Title = @"Critical Crystal", - Artist = @"Seiryu", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh//beatmaps/376340/covers/cover.jpg?1456478455", - }, - }, - }, - }, - }, - Participants = - { - Value = new[] - { - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1355 } } }, - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 8756 } } }, - }, - }, - }), - new DrawableRoom(new Room - { - Name = { Value = @"Relax It's The Weekend" }, - Host = { Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" } } }, - Status = { Value = new RoomStatusPlaying() }, - Type = { Value = new GameTypeTagTeam() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 1.96, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata - { - Title = @"Serendipity", - Artist = @"ZAQ", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh//beatmaps/526839/covers/cover.jpg?1493815706", - }, - }, - }, - }, - }, - Participants = - { - Value = new[] - { - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 578975 } } }, - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24554 } } }, - }, - }, - }), - } - }); - - AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name"); - AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } }); - AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying()); - AddStep(@"change type", () => first.Room.Type.Value = new GameTypeVersus()); - AddStep(@"change beatmap", () => first.Room.Beatmap.Value = null); - AddStep(@"change participants", () => first.Room.Participants.Value = new[] - { - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } }, - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } }, - }); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Screens.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseDrawableRoom : OsuTestCase + { + private RulesetStore rulesets; + + protected override void LoadComplete() + { + base.LoadComplete(); + + DrawableRoom first; + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 580f, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + first = new DrawableRoom(new Room + { + Name = { Value = @"Great Room Right Here" }, + Host = { Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" } } }, + Status = { Value = new RoomStatusOpen() }, + Type = { Value = new GameTypeTeamVersus() }, + Beatmap = + { + Value = new BeatmapInfo + { + StarDifficulty = 4.65, + Ruleset = rulesets.GetRuleset(3), + Metadata = new BeatmapMetadata + { + Title = @"Critical Crystal", + Artist = @"Seiryu", + }, + BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh//beatmaps/376340/covers/cover.jpg?1456478455", + }, + }, + }, + }, + }, + Participants = + { + Value = new[] + { + new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1355 } } }, + new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 8756 } } }, + }, + }, + }), + new DrawableRoom(new Room + { + Name = { Value = @"Relax It's The Weekend" }, + Host = { Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" } } }, + Status = { Value = new RoomStatusPlaying() }, + Type = { Value = new GameTypeTagTeam() }, + Beatmap = + { + Value = new BeatmapInfo + { + StarDifficulty = 1.96, + Ruleset = rulesets.GetRuleset(0), + Metadata = new BeatmapMetadata + { + Title = @"Serendipity", + Artist = @"ZAQ", + }, + BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh//beatmaps/526839/covers/cover.jpg?1493815706", + }, + }, + }, + }, + }, + Participants = + { + Value = new[] + { + new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 578975 } } }, + new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24554 } } }, + }, + }, + }), + } + }); + + AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name"); + AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } }); + AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying()); + AddStep(@"change type", () => first.Room.Type.Value = new GameTypeVersus()); + AddStep(@"change beatmap", () => first.Room.Beatmap.Value = null); + AddStep(@"change participants", () => first.Room.Participants.Value = new[] + { + new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } }, + new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } }, + }); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/TestCaseDrawings.cs index 204b4f133e..a6a3ef6747 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/TestCaseDrawings.cs @@ -1,83 +1,83 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.ComponentModel; -using osu.Game.Screens.Tournament; -using osu.Game.Screens.Tournament.Teams; - -namespace osu.Game.Tests.Visual -{ - [Description("for tournament use")] - public class TestCaseDrawings : OsuTestCase - { - public TestCaseDrawings() - { - Add(new Drawings - { - TeamList = new TestTeamList(), - }); - } - - private class TestTeamList : ITeamList - { - public IEnumerable Teams { get; } = new[] - { - new DrawingsTeam - { - FlagName = "GB", - FullName = "United Kingdom", - Acronym = "UK" - }, - new DrawingsTeam - { - FlagName = "FR", - FullName = "France", - Acronym = "FRA" - }, - new DrawingsTeam - { - FlagName = "CN", - FullName = "China", - Acronym = "CHN" - }, - new DrawingsTeam - { - FlagName = "AU", - FullName = "Australia", - Acronym = "AUS" - }, - new DrawingsTeam - { - FlagName = "JP", - FullName = "Japan", - Acronym = "JPN" - }, - new DrawingsTeam - { - FlagName = "RO", - FullName = "Romania", - Acronym = "ROM" - }, - new DrawingsTeam - { - FlagName = "IT", - FullName = "Italy", - Acronym = "PIZZA" - }, - new DrawingsTeam - { - FlagName = "VE", - FullName = "Venezuela", - Acronym = "VNZ" - }, - new DrawingsTeam - { - FlagName = "US", - FullName = "United States of America", - Acronym = "USA" - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.ComponentModel; +using osu.Game.Screens.Tournament; +using osu.Game.Screens.Tournament.Teams; + +namespace osu.Game.Tests.Visual +{ + [Description("for tournament use")] + public class TestCaseDrawings : OsuTestCase + { + public TestCaseDrawings() + { + Add(new Drawings + { + TeamList = new TestTeamList(), + }); + } + + private class TestTeamList : ITeamList + { + public IEnumerable Teams { get; } = new[] + { + new DrawingsTeam + { + FlagName = "GB", + FullName = "United Kingdom", + Acronym = "UK" + }, + new DrawingsTeam + { + FlagName = "FR", + FullName = "France", + Acronym = "FRA" + }, + new DrawingsTeam + { + FlagName = "CN", + FullName = "China", + Acronym = "CHN" + }, + new DrawingsTeam + { + FlagName = "AU", + FullName = "Australia", + Acronym = "AUS" + }, + new DrawingsTeam + { + FlagName = "JP", + FullName = "Japan", + Acronym = "JPN" + }, + new DrawingsTeam + { + FlagName = "RO", + FullName = "Romania", + Acronym = "ROM" + }, + new DrawingsTeam + { + FlagName = "IT", + FullName = "Italy", + Acronym = "PIZZA" + }, + new DrawingsTeam + { + FlagName = "VE", + FullName = "Venezuela", + Acronym = "VNZ" + }, + new DrawingsTeam + { + FlagName = "US", + FullName = "United States of America", + Acronym = "USA" + }, + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index cd25bc1683..96a754a5ce 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -1,40 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Timing; -using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Edit.Screens.Compose; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseEditorCompose : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(Compose) }; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) - { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - - var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); - - var compose = new Compose(); - compose.Beatmap.BindTo(osuGame.Beatmap); - - Child = compose; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Screens.Compose; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseEditorCompose : EditorClockTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(Compose) }; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + + var compose = new Compose(); + compose.Beatmap.BindTo(osuGame.Beatmap); + + Child = compose; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs index d9850139cd..09f390ab74 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseEditorComposeRadioButtons : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) }; - - public TestCaseEditorComposeRadioButtons() - { - RadioButtonCollection collection; - Add(collection = new RadioButtonCollection - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 150, - Items = new[] - { - new RadioButton("Item 1", () => { }), - new RadioButton("Item 2", () => { }), - new RadioButton("Item 3", () => { }), - new RadioButton("Item 4", () => { }), - new RadioButton("Item 5", () => { }) - } - }); - - for (int i = 0; i < collection.Items.Count; i++) - { - int l = i; - AddStep($"Select item {l + 1}", () => collection.Items[l].Select()); - AddStep($"Deselect item {l + 1}", () => collection.Items[l].Deselect()); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseEditorComposeRadioButtons : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) }; + + public TestCaseEditorComposeRadioButtons() + { + RadioButtonCollection collection; + Add(collection = new RadioButtonCollection + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 150, + Items = new[] + { + new RadioButton("Item 1", () => { }), + new RadioButton("Item 2", () => { }), + new RadioButton("Item 3", () => { }), + new RadioButton("Item 4", () => { }), + new RadioButton("Item 5", () => { }) + } + }); + + for (int i = 0; i < collection.Items.Count; i++) + { + int l = i; + AddStep($"Select item {l + 1}", () => collection.Items[l].Select()); + AddStep($"Deselect item {l + 1}", () => collection.Items[l].Deselect()); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs index d15ee32d8d..a5053bafe8 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs @@ -1,48 +1,48 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseEditorComposeTimeline : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) }; - - private readonly ScrollableTimeline timeline; - - public TestCaseEditorComposeTimeline() - { - Children = new Drawable[] - { - new MusicController - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - State = Visibility.Visible - }, - timeline = new ScrollableTimeline - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(1000, 100) - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) - { - timeline.Beatmap.BindTo(osuGame.Beatmap); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Screens.Compose.Timeline; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseEditorComposeTimeline : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) }; + + private readonly ScrollableTimeline timeline; + + public TestCaseEditorComposeTimeline() + { + Children = new Drawable[] + { + new MusicController + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + State = Visibility.Visible + }, + timeline = new ScrollableTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1000, 100) + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + timeline.Beatmap.BindTo(osuGame.Beatmap); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs index ee98fa087a..cb4438b2ba 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs @@ -1,98 +1,98 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Menus; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseEditorMenuBar : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) }; - - public TestCaseEditorMenuBar() - { - Add(new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = 50, - Y = 50, - Child = new EditorMenuBar - { - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new MenuItem("File") - { - Items = new[] - { - new EditorMenuItem("Clear All Notes"), - new EditorMenuItem("Open Difficulty..."), - new EditorMenuItem("Save"), - new EditorMenuItem("Create a new Difficulty..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Revert to Saved"), - new EditorMenuItem("Revert to Saved (Full)"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Test Beatmap"), - new EditorMenuItem("Open AiMod"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Upload Beatmap..."), - new EditorMenuItem("Export Package"), - new EditorMenuItem("Export Map Package"), - new EditorMenuItem("Import from..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Open Song Folder"), - new EditorMenuItem("Open .osu in Notepad"), - new EditorMenuItem("Open .osb in Notepad"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit"), - } - }, - new MenuItem("Timing") - { - Items = new[] - { - new EditorMenuItem("Time Signature"), - new EditorMenuItem("Metronome Clicks"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Add Timing Section"), - new EditorMenuItem("Add Inheriting Section"), - new EditorMenuItem("Reset Current Section"), - new EditorMenuItem("Delete Timing Section"), - new EditorMenuItem("Resnap Current Section"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Timing Setup"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Resnap All Notes", MenuItemType.Destructive), - new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), - new EditorMenuItem("Recalculate Slider Lengths", MenuItemType.Destructive), - new EditorMenuItem("Delete All Timing Sections", MenuItemType.Destructive), - new EditorMenuItemSpacer(), - new EditorMenuItem("Set Current Position as Preview Point"), - } - }, - new MenuItem("Testing") - { - Items = new[] - { - new EditorMenuItem("Item 1"), - new EditorMenuItem("Item 2"), - new EditorMenuItem("Item 3"), - } - }, - } - } - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Menus; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseEditorMenuBar : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) }; + + public TestCaseEditorMenuBar() + { + Add(new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = 50, + Y = 50, + Child = new EditorMenuBar + { + RelativeSizeAxes = Axes.Both, + Items = new[] + { + new MenuItem("File") + { + Items = new[] + { + new EditorMenuItem("Clear All Notes"), + new EditorMenuItem("Open Difficulty..."), + new EditorMenuItem("Save"), + new EditorMenuItem("Create a new Difficulty..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Revert to Saved"), + new EditorMenuItem("Revert to Saved (Full)"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Test Beatmap"), + new EditorMenuItem("Open AiMod"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Upload Beatmap..."), + new EditorMenuItem("Export Package"), + new EditorMenuItem("Export Map Package"), + new EditorMenuItem("Import from..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Open Song Folder"), + new EditorMenuItem("Open .osu in Notepad"), + new EditorMenuItem("Open .osb in Notepad"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit"), + } + }, + new MenuItem("Timing") + { + Items = new[] + { + new EditorMenuItem("Time Signature"), + new EditorMenuItem("Metronome Clicks"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Add Timing Section"), + new EditorMenuItem("Add Inheriting Section"), + new EditorMenuItem("Reset Current Section"), + new EditorMenuItem("Delete Timing Section"), + new EditorMenuItem("Resnap Current Section"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Timing Setup"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Resnap All Notes", MenuItemType.Destructive), + new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), + new EditorMenuItem("Recalculate Slider Lengths", MenuItemType.Destructive), + new EditorMenuItem("Delete All Timing Sections", MenuItemType.Destructive), + new EditorMenuItemSpacer(), + new EditorMenuItem("Set Current Position as Preview Point"), + } + }, + new MenuItem("Testing") + { + Items = new[] + { + new EditorMenuItem("Item 1"), + new EditorMenuItem("Item 2"), + new EditorMenuItem("Item 3"), + } + }, + } + } + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs index e9e966a826..582ab5ecc9 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs @@ -1,468 +1,437 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Screens.Compose; -using osu.Game.Tests.Beatmaps; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - public class TestCaseEditorSeekSnapping : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(HitObjectComposer) }; - - private Track track; - private HitObjectComposer composer; - - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(4); - - private DecoupleableInterpolatingFramedClock clock; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) - { - clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); - dependencies.Cache(beatDivisor); - - var testBeatmap = new Beatmap - { - ControlPointInfo = new ControlPointInfo - { - TimingPoints = - { - new TimingControlPoint { Time = 0, BeatLength = 200}, - new TimingControlPoint { Time = 100, BeatLength = 400 }, - new TimingControlPoint { Time = 175, BeatLength = 800 }, - new TimingControlPoint { Time = 350, BeatLength = 200 }, - new TimingControlPoint { Time = 450, BeatLength = 100 }, - new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 } - } - }, - HitObjects = - { - new HitCircle { StartTime = 0 }, - new HitCircle { StartTime = 5000 } - } - }; - - osuGame.Beatmap.Value = new TestWorkingBeatmap(testBeatmap); - track = osuGame.Beatmap.Value.Track; - - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { composer = new TestHitObjectComposer(new OsuRuleset()) }, - new Drawable[] { new TimingPointVisualiser(testBeatmap, track) { Clock = clock } }, - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Distributed), - new Dimension(GridSizeMode.AutoSize), - } - }; - - testSeekNoSnapping(); - testSeekSnappingOnBeat(); - testSeekSnappingInBetweenBeat(); - testSeekForwardNoSnapping(); - testSeekForwardSnappingOnBeat(); - testSeekForwardSnappingFromInBetweenBeat(); - testSeekBackwardSnappingOnBeat(); - testSeekBackwardSnappingFromInBetweenBeat(); - testSeekingWithFloatingPointBeatLength(); - } - - /// - /// Tests whether time is correctly seeked without snapping. - /// - private void testSeekNoSnapping() - { - reset(); - - // Forwards - AddStep("Seek(0)", () => composer.SeekTo(0)); - AddAssert("Time = 0", () => clock.CurrentTime == 0); - AddStep("Seek(33)", () => composer.SeekTo(33)); - AddAssert("Time = 33", () => clock.CurrentTime == 33); - AddStep("Seek(89)", () => composer.SeekTo(89)); - AddAssert("Time = 89", () => clock.CurrentTime == 89); - - // Backwards - AddStep("Seek(25)", () => composer.SeekTo(25)); - AddAssert("Time = 25", () => clock.CurrentTime == 25); - AddStep("Seek(0)", () => composer.SeekTo(0)); - AddAssert("Time = 0", () => clock.CurrentTime == 0); - } - - /// - /// Tests whether seeking to exact beat times puts us on the beat time. - /// These are the white/yellow ticks on the graph. - /// - private void testSeekSnappingOnBeat() - { - reset(); - - AddStep("Seek(0), Snap", () => composer.SeekTo(0, true)); - AddAssert("Time = 0", () => clock.CurrentTime == 0); - AddStep("Seek(50), Snap", () => composer.SeekTo(50, true)); - AddAssert("Time = 50", () => clock.CurrentTime == 50); - AddStep("Seek(100), Snap", () => composer.SeekTo(100, true)); - AddAssert("Time = 100", () => clock.CurrentTime == 100); - AddStep("Seek(175), Snap", () => composer.SeekTo(175, true)); - AddAssert("Time = 175", () => clock.CurrentTime == 175); - AddStep("Seek(350), Snap", () => composer.SeekTo(350, true)); - AddAssert("Time = 350", () => clock.CurrentTime == 350); - AddStep("Seek(400), Snap", () => composer.SeekTo(400, true)); - AddAssert("Time = 400", () => clock.CurrentTime == 400); - AddStep("Seek(450), Snap", () => composer.SeekTo(450, true)); - AddAssert("Time = 450", () => clock.CurrentTime == 450); - } - - /// - /// Tests whether seeking to somewhere in the middle between beats puts us on the expected beats. - /// For example, snapping between a white/yellow beat should put us on either the yellow or white, depending on which one we're closer too. - /// If - /// - private void testSeekSnappingInBetweenBeat() - { - reset(); - - AddStep("Seek(24), Snap", () => composer.SeekTo(24, true)); - AddAssert("Time = 0", () => clock.CurrentTime == 0); - AddStep("Seek(26), Snap", () => composer.SeekTo(26, true)); - AddAssert("Time = 50", () => clock.CurrentTime == 50); - AddStep("Seek(150), Snap", () => composer.SeekTo(150, true)); - AddAssert("Time = 100", () => clock.CurrentTime == 100); - AddStep("Seek(170), Snap", () => composer.SeekTo(170, true)); - AddAssert("Time = 175", () => clock.CurrentTime == 175); - AddStep("Seek(274), Snap", () => composer.SeekTo(274, true)); - AddAssert("Time = 175", () => clock.CurrentTime == 175); - AddStep("Seek(276), Snap", () => composer.SeekTo(276, true)); - AddAssert("Time = 350", () => clock.CurrentTime == 350); - } - - /// - /// Tests that when seeking forward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it). - /// - private void testSeekForwardNoSnapping() - { - reset(); - - AddStep("SeekForward", () => composer.SeekForward()); - AddAssert("Time = 50", () => clock.CurrentTime == 50); - AddStep("SeekForward", () => composer.SeekForward()); - AddAssert("Time = 100", () => clock.CurrentTime == 100); - AddStep("SeekForward", () => composer.SeekForward()); - AddAssert("Time = 200", () => clock.CurrentTime == 200); - AddStep("SeekForward", () => composer.SeekForward()); - AddAssert("Time = 400", () => clock.CurrentTime == 400); - AddStep("SeekForward", () => composer.SeekForward()); - AddAssert("Time = 450", () => clock.CurrentTime == 450); - } - - /// - /// Tests that when seeking forward with beat snapping, all beats are snapped to and timing points are never skipped. - /// - private void testSeekForwardSnappingOnBeat() - { - reset(); - - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 50", () => clock.CurrentTime == 50); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 100", () => clock.CurrentTime == 100); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 175", () => clock.CurrentTime == 175); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 350", () => clock.CurrentTime == 350); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 400", () => clock.CurrentTime == 400); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 450", () => clock.CurrentTime == 450); - } - - /// - /// Tests that when seeking forward from in-between two beats, the next beat or timing point is snapped to, and no beats are skipped. - /// This will also test being extremely close to the next beat/timing point, to ensure rounding is not an issue. - /// - private void testSeekForwardSnappingFromInBetweenBeat() - { - reset(); - - AddStep("Seek(49)", () => composer.SeekTo(49)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 50", () => clock.CurrentTime == 50); - AddStep("Seek(49.999)", () => composer.SeekTo(49.999)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 50", () => clock.CurrentTime == 50); - AddStep("Seek(99)", () => composer.SeekTo(99)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 100", () => clock.CurrentTime == 100); - AddStep("Seek(99.999)", () => composer.SeekTo(99.999)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 100", () => clock.CurrentTime == 100); - AddStep("Seek(174)", () => composer.SeekTo(174)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 175", () => clock.CurrentTime == 175); - AddStep("Seek(349)", () => composer.SeekTo(349)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 350", () => clock.CurrentTime == 350); - AddStep("Seek(399)", () => composer.SeekTo(399)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 400", () => clock.CurrentTime == 400); - AddStep("Seek(449)", () => composer.SeekTo(449)); - AddStep("SeekForward, Snap", () => composer.SeekForward(true)); - AddAssert("Time = 450", () => clock.CurrentTime == 450); - } - - /// - /// Tests that when seeking backward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it). - /// - private void testSeekBackwardNoSnapping() - { - reset(); - - AddStep("Seek(450)", () => composer.SeekTo(450)); - AddStep("SeekBackward", () => composer.SeekBackward()); - AddAssert("Time = 425", () => clock.CurrentTime == 425); - AddStep("SeekBackward", () => composer.SeekBackward()); - AddAssert("Time = 375", () => clock.CurrentTime == 375); - AddStep("SeekBackward", () => composer.SeekBackward()); - AddAssert("Time = 325", () => clock.CurrentTime == 325); - AddStep("SeekBackward", () => composer.SeekBackward()); - AddAssert("Time = 125", () => clock.CurrentTime == 125); - AddStep("SeekBackward", () => composer.SeekBackward()); - AddAssert("Time = 25", () => clock.CurrentTime == 25); - AddStep("SeekBackward", () => composer.SeekBackward()); - AddAssert("Time = 0", () => clock.CurrentTime == 0); - } - - /// - /// Tests that when seeking backward with beat snapping, all beats are snapped to and timing points are never skipped. - /// - private void testSeekBackwardSnappingOnBeat() - { - reset(); - - AddStep("Seek(450)", () => composer.SeekTo(450)); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 400", () => clock.CurrentTime == 400); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 350", () => clock.CurrentTime == 350); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 175", () => clock.CurrentTime == 175); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 100", () => clock.CurrentTime == 100); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 50", () => clock.CurrentTime == 50); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 0", () => clock.CurrentTime == 0); - } - - /// - /// Tests that when seeking backward from in-between two beats, the previous beat or timing point is snapped to, and no beats are skipped. - /// This will also test being extremely close to the previous beat/timing point, to ensure rounding is not an issue. - /// - private void testSeekBackwardSnappingFromInBetweenBeat() - { - reset(); - - AddStep("Seek(451)", () => composer.SeekTo(451)); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 450", () => clock.CurrentTime == 450); - AddStep("Seek(450.999)", () => composer.SeekTo(450.999)); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 450", () => clock.CurrentTime == 450); - AddStep("Seek(401)", () => composer.SeekTo(401)); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 400", () => clock.CurrentTime == 400); - AddStep("Seek(401.999)", () => composer.SeekTo(401.999)); - AddStep("SeekBackward, Snap", () => composer.SeekBackward(true)); - AddAssert("Time = 400", () => clock.CurrentTime == 400); - } - - /// - /// Tests that there are no rounding issues when snapping to beats within a timing point with a floating-point beatlength. - /// - private void testSeekingWithFloatingPointBeatLength() - { - reset(); - - double lastTime = 0; - - AddStep("Seek(0)", () => composer.SeekTo(0)); - - for (int i = 0; i < 20; i++) - { - AddStep("SeekForward, Snap", () => - { - lastTime = clock.CurrentTime; - composer.SeekForward(true); - }); - AddAssert("Time > lastTime", () => clock.CurrentTime > lastTime); - } - - for (int i = 0; i < 20; i++) - { - AddStep("SeekBackward, Snap", () => - { - lastTime = clock.CurrentTime; - composer.SeekBackward(true); - }); - AddAssert("Time < lastTime", () => clock.CurrentTime < lastTime); - } - - AddAssert("Time = 0", () => clock.CurrentTime == 0); - } - - private void reset() - { - AddStep("Reset", () => composer.SeekTo(0)); - } - - private class TestHitObjectComposer : HitObjectComposer - { - public TestHitObjectComposer(Ruleset ruleset) - : base(ruleset) - { - } - - protected override IReadOnlyList CompositionTools => new ICompositionTool[0]; - } - - private class TimingPointVisualiser : CompositeDrawable - { - private readonly Track track; - - private readonly Drawable tracker; - - public TimingPointVisualiser(Beatmap beatmap, Track track) - { - this.track = track; - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Width = 0.75f; - - FillFlowContainer timelineContainer; - - InternalChildren = new Drawable[] - { - new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(85f) - }, - new Container - { - Name = "Tracks", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(15), - Children = new[] - { - tracker = new Box - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - RelativePositionAxes = Axes.X, - Width = 2, - Colour = Color4.Red, - }, - timelineContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 5) - }, - } - } - }; - - var timingPoints = beatmap.ControlPointInfo.TimingPoints; - - for (int i = 0; i < timingPoints.Count; i++) - { - TimingControlPoint next = i == timingPoints.Count - 1 ? null : timingPoints[i + 1]; - timelineContainer.Add(new TimingPointTimeline(timingPoints[i], next?.Time ?? beatmap.HitObjects.Last().StartTime, track.Length)); - } - } - - protected override void Update() - { - base.Update(); - - tracker.X = (float)(Time.Current / track.Length); - } - - private class TimingPointTimeline : CompositeDrawable - { - public TimingPointTimeline(TimingControlPoint timingPoint, double endTime, double fullDuration) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Box createMainTick(double time) => new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomCentre, - RelativePositionAxes = Axes.X, - X = (float)(time / fullDuration), - Height = 10, - Width = 2 - }; - - Box createBeatTick(double time) => new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomCentre, - RelativePositionAxes = Axes.X, - X = (float)(time / fullDuration), - Height = 5, - Width = 2, - Colour = time > endTime ? Color4.Gray : Color4.Yellow - }; - - AddInternal(createMainTick(timingPoint.Time)); - AddInternal(createMainTick(endTime)); - - for (double t = timingPoint.Time + timingPoint.BeatLength / 4; t < fullDuration; t += timingPoint.BeatLength / 4) - AddInternal(createBeatTick(t)); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorSeekSnapping : EditorClockTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(HitObjectComposer) }; + + public TestCaseEditorSeekSnapping() + { + BeatDivisor.Value = 4; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + var testBeatmap = new Beatmap + { + ControlPointInfo = new ControlPointInfo + { + TimingPoints = + { + new TimingControlPoint { Time = 0, BeatLength = 200}, + new TimingControlPoint { Time = 100, BeatLength = 400 }, + new TimingControlPoint { Time = 175, BeatLength = 800 }, + new TimingControlPoint { Time = 350, BeatLength = 200 }, + new TimingControlPoint { Time = 450, BeatLength = 100 }, + new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 } + } + }, + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 5000 } + } + }; + + osuGame.Beatmap.Value = new TestWorkingBeatmap(testBeatmap); + + Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; + + testSeekNoSnapping(); + testSeekSnappingOnBeat(); + testSeekSnappingInBetweenBeat(); + testSeekForwardNoSnapping(); + testSeekForwardSnappingOnBeat(); + testSeekForwardSnappingFromInBetweenBeat(); + testSeekBackwardSnappingOnBeat(); + testSeekBackwardSnappingFromInBetweenBeat(); + testSeekingWithFloatingPointBeatLength(); + } + + /// + /// Tests whether time is correctly seeked without snapping. + /// + private void testSeekNoSnapping() + { + reset(); + + // Forwards + AddStep("Seek(0)", () => Clock.Seek(0)); + AddAssert("Time = 0", () => Clock.CurrentTime == 0); + AddStep("Seek(33)", () => Clock.Seek(33)); + AddAssert("Time = 33", () => Clock.CurrentTime == 33); + AddStep("Seek(89)", () => Clock.Seek(89)); + AddAssert("Time = 89", () => Clock.CurrentTime == 89); + + // Backwards + AddStep("Seek(25)", () => Clock.Seek(25)); + AddAssert("Time = 25", () => Clock.CurrentTime == 25); + AddStep("Seek(0)", () => Clock.Seek(0)); + AddAssert("Time = 0", () => Clock.CurrentTime == 0); + } + + /// + /// Tests whether seeking to exact beat times puts us on the beat time. + /// These are the white/yellow ticks on the graph. + /// + private void testSeekSnappingOnBeat() + { + reset(); + + AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0)); + AddAssert("Time = 0", () => Clock.CurrentTime == 0); + AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50)); + AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100)); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175)); + AddAssert("Time = 175", () => Clock.CurrentTime == 175); + AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350)); + AddAssert("Time = 350", () => Clock.CurrentTime == 350); + AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400)); + AddAssert("Time = 400", () => Clock.CurrentTime == 400); + AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450)); + AddAssert("Time = 450", () => Clock.CurrentTime == 450); + } + + /// + /// Tests whether seeking to somewhere in the middle between beats puts us on the expected beats. + /// For example, snapping between a white/yellow beat should put us on either the yellow or white, depending on which one we're closer too. + /// If + /// + private void testSeekSnappingInBetweenBeat() + { + reset(); + + AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24)); + AddAssert("Time = 0", () => Clock.CurrentTime == 0); + AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26)); + AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150)); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170)); + AddAssert("Time = 175", () => Clock.CurrentTime == 175); + AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274)); + AddAssert("Time = 175", () => Clock.CurrentTime == 175); + AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276)); + AddAssert("Time = 350", () => Clock.CurrentTime == 350); + } + + /// + /// Tests that when seeking forward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it). + /// + private void testSeekForwardNoSnapping() + { + reset(); + + AddStep("SeekForward", () => Clock.SeekForward()); + AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddStep("SeekForward", () => Clock.SeekForward()); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddStep("SeekForward", () => Clock.SeekForward()); + AddAssert("Time = 200", () => Clock.CurrentTime == 200); + AddStep("SeekForward", () => Clock.SeekForward()); + AddAssert("Time = 400", () => Clock.CurrentTime == 400); + AddStep("SeekForward", () => Clock.SeekForward()); + AddAssert("Time = 450", () => Clock.CurrentTime == 450); + } + + /// + /// Tests that when seeking forward with beat snapping, all beats are snapped to and timing points are never skipped. + /// + private void testSeekForwardSnappingOnBeat() + { + reset(); + + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 175", () => Clock.CurrentTime == 175); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 350", () => Clock.CurrentTime == 350); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 400", () => Clock.CurrentTime == 400); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 450", () => Clock.CurrentTime == 450); + } + + /// + /// Tests that when seeking forward from in-between two beats, the next beat or timing point is snapped to, and no beats are skipped. + /// This will also test being extremely close to the next beat/timing point, to ensure rounding is not an issue. + /// + private void testSeekForwardSnappingFromInBetweenBeat() + { + reset(); + + AddStep("Seek(49)", () => Clock.Seek(49)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddStep("Seek(49.999)", () => Clock.Seek(49.999)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddStep("Seek(99)", () => Clock.Seek(99)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddStep("Seek(99.999)", () => Clock.Seek(99.999)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddStep("Seek(174)", () => Clock.Seek(174)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 175", () => Clock.CurrentTime == 175); + AddStep("Seek(349)", () => Clock.Seek(349)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 350", () => Clock.CurrentTime == 350); + AddStep("Seek(399)", () => Clock.Seek(399)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 400", () => Clock.CurrentTime == 400); + AddStep("Seek(449)", () => Clock.Seek(449)); + AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); + AddAssert("Time = 450", () => Clock.CurrentTime == 450); + } + + /// + /// Tests that when seeking backward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it). + /// + private void testSeekBackwardNoSnapping() + { + reset(); + + AddStep("Seek(450)", () => Clock.Seek(450)); + AddStep("SeekBackward", () => Clock.SeekBackward()); + AddAssert("Time = 425", () => Clock.CurrentTime == 425); + AddStep("SeekBackward", () => Clock.SeekBackward()); + AddAssert("Time = 375", () => Clock.CurrentTime == 375); + AddStep("SeekBackward", () => Clock.SeekBackward()); + AddAssert("Time = 325", () => Clock.CurrentTime == 325); + AddStep("SeekBackward", () => Clock.SeekBackward()); + AddAssert("Time = 125", () => Clock.CurrentTime == 125); + AddStep("SeekBackward", () => Clock.SeekBackward()); + AddAssert("Time = 25", () => Clock.CurrentTime == 25); + AddStep("SeekBackward", () => Clock.SeekBackward()); + AddAssert("Time = 0", () => Clock.CurrentTime == 0); + } + + /// + /// Tests that when seeking backward with beat snapping, all beats are snapped to and timing points are never skipped. + /// + private void testSeekBackwardSnappingOnBeat() + { + reset(); + + AddStep("Seek(450)", () => Clock.Seek(450)); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 400", () => Clock.CurrentTime == 400); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 350", () => Clock.CurrentTime == 350); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 175", () => Clock.CurrentTime == 175); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 0", () => Clock.CurrentTime == 0); + } + + /// + /// Tests that when seeking backward from in-between two beats, the previous beat or timing point is snapped to, and no beats are skipped. + /// This will also test being extremely close to the previous beat/timing point, to ensure rounding is not an issue. + /// + private void testSeekBackwardSnappingFromInBetweenBeat() + { + reset(); + + AddStep("Seek(451)", () => Clock.Seek(451)); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 450", () => Clock.CurrentTime == 450); + AddStep("Seek(450.999)", () => Clock.Seek(450.999)); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 450", () => Clock.CurrentTime == 450); + AddStep("Seek(401)", () => Clock.Seek(401)); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 400", () => Clock.CurrentTime == 400); + AddStep("Seek(401.999)", () => Clock.Seek(401.999)); + AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true)); + AddAssert("Time = 400", () => Clock.CurrentTime == 400); + } + + /// + /// Tests that there are no rounding issues when snapping to beats within a timing point with a floating-point beatlength. + /// + private void testSeekingWithFloatingPointBeatLength() + { + reset(); + + double lastTime = 0; + + AddStep("Seek(0)", () => Clock.Seek(0)); + + for (int i = 0; i < 20; i++) + { + AddStep("SeekForward, Snap", () => + { + lastTime = Clock.CurrentTime; + Clock.SeekForward(true); + }); + AddAssert("Time > lastTime", () => Clock.CurrentTime > lastTime); + } + + for (int i = 0; i < 20; i++) + { + AddStep("SeekBackward, Snap", () => + { + lastTime = Clock.CurrentTime; + Clock.SeekBackward(true); + }); + AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime); + } + + AddAssert("Time = 0", () => Clock.CurrentTime == 0); + } + + private void reset() + { + AddStep("Reset", () => Clock.Seek(0)); + } + + private class TestHitObjectComposer : HitObjectComposer + { + public TestHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => new ICompositionTool[0]; + } + + private class TimingPointVisualiser : CompositeDrawable + { + private readonly double length; + + private readonly Drawable tracker; + + public TimingPointVisualiser(Beatmap beatmap, double length) + { + this.length = length; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Width = 0.75f; + + FillFlowContainer timelineContainer; + + InternalChildren = new Drawable[] + { + new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(85f) + }, + new Container + { + Name = "Tracks", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), + Children = new[] + { + tracker = new Box + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Width = 2, + Colour = Color4.Red, + }, + timelineContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 5) + }, + } + } + }; + + var timingPoints = beatmap.ControlPointInfo.TimingPoints; + + for (int i = 0; i < timingPoints.Count; i++) + { + TimingControlPoint next = i == timingPoints.Count - 1 ? null : timingPoints[i + 1]; + timelineContainer.Add(new TimingPointTimeline(timingPoints[i], next?.Time ?? length, length)); + } + } + + protected override void Update() + { + base.Update(); + + tracker.X = (float)(Time.Current / length); + } + + private class TimingPointTimeline : CompositeDrawable + { + public TimingPointTimeline(TimingControlPoint timingPoint, double endTime, double fullDuration) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Box createMainTick(double time) => new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomCentre, + RelativePositionAxes = Axes.X, + X = (float)(time / fullDuration), + Height = 10, + Width = 2 + }; + + Box createBeatTick(double time) => new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomCentre, + RelativePositionAxes = Axes.X, + X = (float)(time / fullDuration), + Height = 5, + Width = 2, + Colour = time > endTime ? Color4.Gray : Color4.Yellow + }; + + AddInternal(createMainTick(timingPoint.Time)); + AddInternal(createMainTick(endTime)); + + for (double t = timingPoint.Time + timingPoint.BeatLength / 4; t < fullDuration; t += timingPoint.BeatLength / 4) + AddInternal(createBeatTick(t)); + } + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs index bbe2956c5d..d01c2d2b92 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -1,51 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using OpenTK; -using osu.Game.Screens.Edit.Components.Timelines.Summary; -using osu.Framework.Configuration; -using osu.Framework.Timing; -using osu.Game.Rulesets.Osu; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseEditorSummaryTimeline : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; - - private readonly Bindable beatmap = new Bindable(); - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - [BackgroundDependencyLoader] - private void load() - { - beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - - var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); - - SummaryTimeline summaryTimeline; - Add(summaryTimeline = new SummaryTimeline - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 50) - }); - - summaryTimeline.Beatmap.BindTo(beatmap); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using OpenTK; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseEditorSummaryTimeline : EditorClockTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + + SummaryTimeline summaryTimeline; + Add(summaryTimeline = new SummaryTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 50) + }); + + summaryTimeline.Beatmap.BindTo(osuGame.Beatmap); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseGamefield.cs b/osu.Game.Tests/Visual/TestCaseGamefield.cs deleted file mode 100644 index 80b3f9eb40..0000000000 --- a/osu.Game.Tests/Visual/TestCaseGamefield.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseGamefield : OsuTestCase - { - protected override void LoadComplete() - { - base.LoadComplete(); - - /*int time = 500; - for (int i = 0; i < 100; i++) - { - objects.Add(new HitCircle - { - StartTime = time, - Position = new Vector2(RNG.Next(0, (int)OsuPlayfield.BASE_SIZE.X), RNG.Next(0, (int)OsuPlayfield.BASE_SIZE.Y)), - Scale = RNG.NextSingle(0.5f, 1.0f), - }); - - time += RNG.Next(50, 500); - }*/ - - var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint - { - BeatLength = 200 - }); - - /*WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap - { - HitObjects = objects, - BeatmapInfo = new BeatmapInfo - { - Difficulty = new BeatmapDifficulty(), - Ruleset = rulesets.Query().First(), - Metadata = new BeatmapMetadata - { - Artist = @"Unknown", - Title = @"Sample Beatmap", - Author = @"peppy", - }, - }, - ControlPointInfo = controlPointInfo - }); - - AddRange(new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - //ensure we are at offset 0 - Clock = new FramedClock(), - Children = new Drawable[] - { - new OsuRulesetContainer(new OsuRuleset(), beatmap, false) - { - Scale = new Vector2(0.5f), - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft - }, - new TaikoRulesetContainer(new TaikoRuleset(),beatmap, false) - { - Scale = new Vector2(0.5f), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }, - new CatchRulesetContainer(new CatchRuleset(),beatmap, false) - { - Scale = new Vector2(0.5f), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft - }, - new ManiaRulesetContainer(new ManiaRuleset(),beatmap, false) - { - Scale = new Vector2(0.5f), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight - } - } - } - });*/ - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index 9aa5607f6d..130685a4cf 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -1,258 +1,258 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using OpenTK.Input; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Logging; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [Description("player pause/fail screens")] - public class TestCaseGameplayMenuOverlay : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; - - private FailOverlay failOverlay; - private PauseContainer.PauseOverlay pauseOverlay; - - [BackgroundDependencyLoader] - private void load() - { - Add(pauseOverlay = new PauseContainer.PauseOverlay - { - OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); - - Add(failOverlay = new FailOverlay - { - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); - - var retryCount = 0; - - AddStep("Add retry", () => - { - retryCount++; - pauseOverlay.Retries = failOverlay.Retries = retryCount; - }); - - AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility()); - AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility()); - - testHideResets(); - - testEnterWithoutSelection(); - testKeyUpFromInitial(); - testKeyDownFromInitial(); - testKeyUpWrapping(); - testKeyDownWrapping(); - - testMouseSelectionAfterKeySelection(); - testKeySelectionAfterMouseSelection(); - - testMouseDeselectionResets(); - - testClickSelection(); - testEnterKeySelection(); - } - - /// - /// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected. - /// - private void testHideResets() - { - AddStep("Show overlay", () => failOverlay.Show()); - - AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnMouseMove(null)); - AddStep("Hide overlay", () => failOverlay.Hide()); - - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); - } - - /// - /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. - /// - private void testEnterWithoutSelection() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter })); - AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); - } - - /// - /// Tests that pressing the up arrow from the initial state selects the last button. - /// - private void testKeyUpFromInitial() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); - AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); - } - - /// - /// Tests that pressing the down arrow from the initial state selects the first button. - /// - private void testKeyDownFromInitial() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); - } - - /// - /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly. - /// - private void testKeyUpWrapping() - { - AddStep("Show overlay", () => failOverlay.Show()); - - AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); - AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); - AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); - - AddStep("Hide overlay", () => failOverlay.Hide()); - } - - /// - /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly. - /// - private void testKeyDownWrapping() - { - AddStep("Show overlay", () => failOverlay.Show()); - - AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); - AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); - AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); - - AddStep("Hide overlay", () => failOverlay.Hide()); - } - - /// - /// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button. - /// - private void testMouseSelectionAfterKeySelection() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - var secondButton = pauseOverlay.Buttons.Skip(1).First(); - - AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); - AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); - AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected); - AddAssert("Second button selected", () => secondButton.Selected); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); - } - - /// - /// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button. - /// - private void testKeySelectionAfterMouseSelection() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - var secondButton = pauseOverlay.Buttons.Skip(1).First(); - - AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); - AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); - AddAssert("Second button not selected", () => !secondButton.Selected); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); - - AddStep("Hide overlay", () => pauseOverlay.Hide()); - } - - /// - /// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state. - /// - private void testMouseDeselectionResets() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - var secondButton = pauseOverlay.Buttons.Skip(1).First(); - - AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); - AddStep("Unhover second button", () => secondButton.TriggerOnHoverLost(null)); - AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition - - AddStep("Hide overlay", () => pauseOverlay.Hide()); - } - - /// - /// Tests that clicking on a button correctly causes a click event for that button. - /// - private void testClickSelection() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - var retryButton = pauseOverlay.Buttons.Skip(1).First(); - - bool triggered = false; - AddStep("Click retry button", () => - { - var lastAction = pauseOverlay.OnRetry; - pauseOverlay.OnRetry = () => triggered = true; - - retryButton.TriggerOnClick(); - pauseOverlay.OnRetry = lastAction; - }); - - AddAssert("Action was triggered", () => triggered); - AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); - } - - /// - /// Tests that pressing the enter key with a button selected correctly causes a click event for that button. - /// - private void testEnterKeySelection() - { - AddStep("Show overlay", () => pauseOverlay.Show()); - - AddStep("Select second button", () => - { - pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); - pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); - }); - - var retryButton = pauseOverlay.Buttons.Skip(1).First(); - - bool triggered = false; - AddStep("Press enter", () => - { - var lastAction = pauseOverlay.OnRetry; - pauseOverlay.OnRetry = () => triggered = true; - - retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }); - pauseOverlay.OnRetry = lastAction; - }); - - AddAssert("Action was triggered", () => triggered); - AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Logging; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [Description("player pause/fail screens")] + public class TestCaseGameplayMenuOverlay : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; + + private FailOverlay failOverlay; + private PauseContainer.PauseOverlay pauseOverlay; + + [BackgroundDependencyLoader] + private void load() + { + Add(pauseOverlay = new PauseContainer.PauseOverlay + { + OnResume = () => Logger.Log(@"Resume"), + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }); + + Add(failOverlay = new FailOverlay + { + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }); + + var retryCount = 0; + + AddStep("Add retry", () => + { + retryCount++; + pauseOverlay.Retries = failOverlay.Retries = retryCount; + }); + + AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility()); + AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility()); + + testHideResets(); + + testEnterWithoutSelection(); + testKeyUpFromInitial(); + testKeyDownFromInitial(); + testKeyUpWrapping(); + testKeyDownWrapping(); + + testMouseSelectionAfterKeySelection(); + testKeySelectionAfterMouseSelection(); + + testMouseDeselectionResets(); + + testClickSelection(); + testEnterKeySelection(); + } + + /// + /// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected. + /// + private void testHideResets() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnMouseMove(null)); + AddStep("Hide overlay", () => failOverlay.Hide()); + + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); + } + + /// + /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. + /// + private void testEnterWithoutSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter })); + AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the up arrow from the initial state selects the last button. + /// + private void testKeyUpFromInitial() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the down arrow from the initial state selects the first button. + /// + private void testKeyDownFromInitial() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly. + /// + private void testKeyUpWrapping() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + + AddStep("Hide overlay", () => failOverlay.Hide()); + } + + /// + /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly. + /// + private void testKeyDownWrapping() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => failOverlay.Hide()); + } + + /// + /// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button. + /// + private void testMouseSelectionAfterKeySelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); + AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected); + AddAssert("Second button selected", () => secondButton.Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button. + /// + private void testKeySelectionAfterMouseSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); + AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Second button not selected", () => !secondButton.Selected); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state. + /// + private void testMouseDeselectionResets() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null)); + AddStep("Unhover second button", () => secondButton.TriggerOnHoverLost(null)); + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that clicking on a button correctly causes a click event for that button. + /// + private void testClickSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var retryButton = pauseOverlay.Buttons.Skip(1).First(); + + bool triggered = false; + AddStep("Click retry button", () => + { + var lastAction = pauseOverlay.OnRetry; + pauseOverlay.OnRetry = () => triggered = true; + + retryButton.TriggerOnClick(); + pauseOverlay.OnRetry = lastAction; + }); + + AddAssert("Action was triggered", () => triggered); + AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); + } + + /// + /// Tests that pressing the enter key with a button selected correctly causes a click event for that button. + /// + private void testEnterKeySelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Select second button", () => + { + pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); + pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); + }); + + var retryButton = pauseOverlay.Buttons.Skip(1).First(); + + bool triggered = false; + AddStep("Press enter", () => + { + var lastAction = pauseOverlay.OnRetry; + pauseOverlay.OnRetry = () => triggered = true; + + retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }); + pauseOverlay.OnRetry = lastAction; + }); + + AddAssert("Action was triggered", () => triggered); + AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/TestCaseGraph.cs index 285a43707a..40b05d51e6 100644 --- a/osu.Game.Tests/Visual/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseGraph.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseGraph : OsuTestCase - { - public TestCaseGraph() - { - BarGraph graph; - - Children = new[] - { - graph = new BarGraph - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0.5f), - }, - }; - - AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i)); - AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); - AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); - AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop); - AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); - AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); - AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseGraph : OsuTestCase + { + public TestCaseGraph() + { + BarGraph graph; + + Children = new[] + { + graph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f), + }, + }; + + AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i)); + AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); + AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); + AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop); + AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); + AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); + AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs index 2e94baa9fc..a576ac84e7 100644 --- a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs +++ b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs @@ -1,49 +1,49 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Overlays.Profile.Sections; -using osu.Game.Overlays.Profile.Sections.Historical; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseHistoricalSection : OsuTestCase - { - public override IReadOnlyList RequiredTypes => - new[] - { - typeof(HistoricalSection), - typeof(PaginatedMostPlayedBeatmapContainer), - typeof(DrawableMostPlayedRow), - typeof(DrawableProfileRow) - }; - - public TestCaseHistoricalSection() - { - HistoricalSection section; - - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }); - - Add(new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = section = new HistoricalSection(), - }); - - AddStep("Show peppy", () => section.User.Value = new User { Id = 2 }); - AddStep("Show WubWoofWolf", () => section.User.Value = new User { Id = 39828 }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Overlays.Profile.Sections.Historical; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseHistoricalSection : OsuTestCase + { + public override IReadOnlyList RequiredTypes => + new[] + { + typeof(HistoricalSection), + typeof(PaginatedMostPlayedBeatmapContainer), + typeof(DrawableMostPlayedRow), + typeof(DrawableProfileRow) + }; + + public TestCaseHistoricalSection() + { + HistoricalSection section; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }); + + Add(new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = section = new HistoricalSection(), + }); + + AddStep("Show peppy", () => section.User.Value = new User { Id = 2 }); + AddStep("Show WubWoofWolf", () => section.User.Value = new User { Id = 39828 }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index 72d60d8e01..d0c46ecdd7 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -1,72 +1,72 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Timing; -using OpenTK; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Screens.Compose.Layers; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseHitObjectComposer : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(MaskSelection), - typeof(DragLayer), - typeof(HitObjectComposer), - typeof(OsuHitObjectComposer), - typeof(HitObjectMaskLayer), - typeof(NotNullAttribute) - }; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) - { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new Beatmap - { - HitObjects = new List - { - new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, - new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, - new Slider - { - Position = new Vector2(128, 256), - ControlPoints = new List - { - Vector2.Zero, - new Vector2(216, 0), - }, - Distance = 400, - Velocity = 1, - TickDistance = 100, - Scale = 0.5f, - } - }, - }); - - var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); - - Child = new OsuHitObjectComposer(new OsuRuleset()); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Timing; +using OpenTK; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Screens.Compose.Layers; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseHitObjectComposer : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(MaskSelection), + typeof(DragLayer), + typeof(HitObjectComposer), + typeof(OsuHitObjectComposer), + typeof(HitObjectMaskLayer), + typeof(NotNullAttribute) + }; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + osuGame.Beatmap.Value = new TestWorkingBeatmap(new Beatmap + { + HitObjects = new List + { + new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, + new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, + new Slider + { + Position = new Vector2(128, 256), + ControlPoints = new List + { + Vector2.Zero, + new Vector2(216, 0), + }, + Distance = 400, + Velocity = 1, + TickDistance = 100, + Scale = 0.5f, + } + }, + }); + + var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); + + Child = new OsuHitObjectComposer(new OsuRuleset()); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseIconButton.cs b/osu.Game.Tests/Visual/TestCaseIconButton.cs index fae79e25bd..16363da527 100644 --- a/osu.Game.Tests/Visual/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/TestCaseIconButton.cs @@ -1,113 +1,113 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseIconButton : OsuTestCase - { - public TestCaseIconButton() - { - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Spacing = new Vector2(10, 10), - Children = new[] - { - new NamedIconButton("No change", new IconButton()), - new NamedIconButton("Background colours", new IconButton - { - FlashColour = Color4.DarkGreen, - HoverColour = Color4.Green, - }), - new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }), - new NamedIconButton("Unchanging size", new IconButton(), false), - new NamedIconButton("Icon colours", new IconButton - { - IconColour = Color4.Green, - IconHoverColour = Color4.Red - }) - } - }; - } - - private class NamedIconButton : Container - { - public NamedIconButton(string name, IconButton button, bool allowSizeChange = true) - { - AutoSizeAxes = Axes.Y; - Width = 200; - - Container iconContainer; - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = name - }, - new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, - }, - iconContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = button - } - } - } - } - } - }; - - if (allowSizeChange) - iconContainer.AutoSizeAxes = Axes.Both; - else - { - iconContainer.RelativeSizeAxes = Axes.X; - iconContainer.Height = 30; - } - - button.Anchor = Anchor.Centre; - button.Origin = Anchor.Centre; - button.Icon = FontAwesome.fa_osu_osu_o; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseIconButton : OsuTestCase + { + public TestCaseIconButton() + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Spacing = new Vector2(10, 10), + Children = new[] + { + new NamedIconButton("No change", new IconButton()), + new NamedIconButton("Background colours", new IconButton + { + FlashColour = Color4.DarkGreen, + HoverColour = Color4.Green, + }), + new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }), + new NamedIconButton("Unchanging size", new IconButton(), false), + new NamedIconButton("Icon colours", new IconButton + { + IconColour = Color4.Green, + IconHoverColour = Color4.Red + }) + } + }; + } + + private class NamedIconButton : Container + { + public NamedIconButton(string name, IconButton button, bool allowSizeChange = true) + { + AutoSizeAxes = Axes.Y; + Width = 200; + + Container iconContainer; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = name + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + }, + iconContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = button + } + } + } + } + } + }; + + if (allowSizeChange) + iconContainer.AutoSizeAxes = Axes.Both; + else + { + iconContainer.RelativeSizeAxes = Axes.X; + iconContainer.Height = 30; + } + + button.Anchor = Anchor.Centre; + button.Origin = Anchor.Centre; + button.Icon = FontAwesome.fa_osu_osu_o; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs index 4af6255b48..4982686505 100644 --- a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs +++ b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs @@ -1,54 +1,54 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Screens.Menu; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseIntroSequence : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuLogo), - }; - - public TestCaseIntroSequence() - { - OsuLogo logo; - - var rateAdjustClock = new StopwatchClock(true); - var framedClock = new FramedClock(rateAdjustClock); - framedClock.ProcessFrame(); - - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Clock = framedClock, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - logo = new OsuLogo - { - Anchor = Anchor.Centre, - } - } - }); - - AddStep(@"Restart", logo.PlayIntro); - AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseIntroSequence : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + public TestCaseIntroSequence() + { + OsuLogo logo; + + var rateAdjustClock = new StopwatchClock(true); + var framedClock = new FramedClock(rateAdjustClock); + framedClock.ProcessFrame(); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Clock = framedClock, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + logo = new OsuLogo + { + Anchor = Anchor.Centre, + } + } + }); + + AddStep(@"Restart", logo.PlayIntro); + AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs b/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs index e39b9f6683..fe88b46414 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseKeyConfiguration : OsuTestCase - { - private readonly KeyBindingOverlay overlay; - - public TestCaseKeyConfiguration() - { - Child = overlay = new KeyBindingOverlay(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - overlay.Show(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseKeyConfiguration : OsuTestCase + { + private readonly KeyBindingOverlay overlay; + + public TestCaseKeyConfiguration() + { + Child = overlay = new KeyBindingOverlay(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + overlay.Show(); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index bf73c6899b..a20f67e336 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.MathUtils; -using osu.Game.Screens.Play; -using OpenTK.Input; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseKeyCounter : OsuTestCase - { - public TestCaseKeyCounter() - { - KeyCounterCollection kc = new KeyCounterCollection - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - IsCounting = true, - Children = new KeyCounter[] - { - new KeyCounterKeyboard(Key.Z), - new KeyCounterKeyboard(Key.X), - new KeyCounterMouse(MouseButton.Left), - new KeyCounterMouse(MouseButton.Right), - }, - }; - - AddStep("Add random", () => - { - Key key = (Key)((int)Key.A + RNG.Next(26)); - kc.Add(new KeyCounterKeyboard(key)); - }); - AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v); - - Add(kc); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Game.Screens.Play; +using OpenTK.Input; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseKeyCounter : OsuTestCase + { + public TestCaseKeyCounter() + { + KeyCounterCollection kc = new KeyCounterCollection + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + IsCounting = true, + Children = new KeyCounter[] + { + new KeyCounterKeyboard(Key.Z), + new KeyCounterKeyboard(Key.X), + new KeyCounterMouse(MouseButton.Left), + new KeyCounterMouse(MouseButton.Right), + }, + }; + + AddStep("Add random", () => + { + Key key = (Key)((int)Key.A + RNG.Next(26)); + kc.Add(new KeyCounterKeyboard(key)); + }); + AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v); + + Add(kc); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index 7b00f4a1d5..7f5bce3b84 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -1,278 +1,278 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; -using osu.Framework.Allocation; -using OpenTK; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; - -namespace osu.Game.Tests.Visual -{ - [Description("PlaySongSelect leaderboard")] - public class TestCaseLeaderboard : OsuTestCase - { - private RulesetStore rulesets; - - private readonly FailableLeaderboard leaderboard; - - public TestCaseLeaderboard() - { - Add(leaderboard = new FailableLeaderboard - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = LeaderboardScope.Global, - }); - - AddStep(@"New Scores", newScores); - AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); - AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); - AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); - AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); - AddStep(@"Real beatmap", realBeatmap); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - - private void newScores() - { - var scores = new[] - { - new Score - { - Rank = ScoreRank.XH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - }, - new Score - { - Rank = ScoreRank.X, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - }, - new Score - { - Rank = ScoreRank.SH, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - }, - new Score - { - Rank = ScoreRank.S, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - }, - new Score - { - Rank = ScoreRank.A, - Accuracy = 1, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 2243452, - Username = @"Satoruu", - Country = new Country - { - FullName = @"Venezuela", - FlagName = @"VE", - }, - }, - }, - new Score - { - Rank = ScoreRank.B, - Accuracy = 0.9826, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 2705430, - Username = @"Mooha", - Country = new Country - { - FullName = @"France", - FlagName = @"FR", - }, - }, - }, - new Score - { - Rank = ScoreRank.C, - Accuracy = 0.9654, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - }, - new Score - { - Rank = ScoreRank.F, - Accuracy = 0.6025, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 2051389, - Username = @"FunOrange", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - }, - new Score - { - Rank = ScoreRank.F, - Accuracy = 0.5140, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 6169483, - Username = @"-Hebel-", - Country = new Country - { - FullName = @"Mexico", - FlagName = @"MX", - }, - }, - }, - new Score - { - Rank = ScoreRank.F, - Accuracy = 0.4222, - MaxCombo = 244, - TotalScore = 1707827, - //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - User = new User - { - Id = 6702666, - Username = @"prhtnsm", - Country = new Country - { - FullName = @"Germany", - FlagName = @"DE", - }, - }, - }, - }; - - leaderboard.Scores = scores; - } - - private void realBeatmap() - { - leaderboard.Beatmap = new BeatmapInfo - { - StarDifficulty = 1.36, - Version = @"BASIC", - OnlineBeatmapID = 1113057, - Ruleset = rulesets.GetRuleset(0), - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 6.5f, - OverallDifficulty = 6.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 115000, - CircleCount = 265, - SliderCount = 71, - PlayCount = 47906, - PassCount = 19899, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }; - } - - private class FailableLeaderboard : Leaderboard - { - public void SetRetrievalState(PlaceholderState state) - { - PlaceholderState = state; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Users; +using osu.Framework.Allocation; +using OpenTK; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; + +namespace osu.Game.Tests.Visual +{ + [Description("PlaySongSelect leaderboard")] + public class TestCaseLeaderboard : OsuTestCase + { + private RulesetStore rulesets; + + private readonly FailableLeaderboard leaderboard; + + public TestCaseLeaderboard() + { + Add(leaderboard = new FailableLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = LeaderboardScope.Global, + }); + + AddStep(@"New Scores", newScores); + AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); + AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); + AddStep(@"Real beatmap", realBeatmap); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + + private void newScores() + { + var scores = new[] + { + new Score + { + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + }, + new Score + { + Rank = ScoreRank.X, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + }, + new Score + { + Rank = ScoreRank.SH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, + }, + }, + new Score + { + Rank = ScoreRank.S, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + }, + new Score + { + Rank = ScoreRank.A, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 2243452, + Username = @"Satoruu", + Country = new Country + { + FullName = @"Venezuela", + FlagName = @"VE", + }, + }, + }, + new Score + { + Rank = ScoreRank.B, + Accuracy = 0.9826, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 2705430, + Username = @"Mooha", + Country = new Country + { + FullName = @"France", + FlagName = @"FR", + }, + }, + }, + new Score + { + Rank = ScoreRank.C, + Accuracy = 0.9654, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + }, + new Score + { + Rank = ScoreRank.F, + Accuracy = 0.6025, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 2051389, + Username = @"FunOrange", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + }, + new Score + { + Rank = ScoreRank.F, + Accuracy = 0.5140, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 6169483, + Username = @"-Hebel-", + Country = new Country + { + FullName = @"Mexico", + FlagName = @"MX", + }, + }, + }, + new Score + { + Rank = ScoreRank.F, + Accuracy = 0.4222, + MaxCombo = 244, + TotalScore = 1707827, + //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, + User = new User + { + Id = 6702666, + Username = @"prhtnsm", + Country = new Country + { + FullName = @"Germany", + FlagName = @"DE", + }, + }, + }, + }; + + leaderboard.Scores = scores; + } + + private void realBeatmap() + { + leaderboard.Beatmap = new BeatmapInfo + { + StarDifficulty = 1.36, + Version = @"BASIC", + OnlineBeatmapID = 1113057, + Ruleset = rulesets.GetRuleset(0), + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 6.5f, + OverallDifficulty = 6.5f, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 115000, + CircleCount = 265, + SliderCount = 71, + PlayCount = 47906, + PassCount = 19899, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }; + } + + private class FailableLeaderboard : Leaderboard + { + public void SetRetrievalState(PlaceholderState state) + { + PlaceholderState = state; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs index 8d91a0f0dd..c2df8d33ca 100644 --- a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Game.Overlays; -using osu.Game.Overlays.MedalSplash; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMedalOverlay : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(MedalOverlay), - typeof(DrawableMedal), - }; - - public TestCaseMedalOverlay() - { - AddStep(@"display", () => - { - LoadComponentAsync(new MedalOverlay(new Medal - { - Name = @"Animations", - InternalName = @"all-intro-doubletime", - Description = @"More complex than you think.", - }), Add); - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Overlays; +using osu.Game.Overlays.MedalSplash; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseMedalOverlay : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(MedalOverlay), + typeof(DrawableMedal), + }; + + public TestCaseMedalOverlay() + { + AddStep(@"display", () => + { + LoadComponentAsync(new MedalOverlay(new Medal + { + Name = @"Animations", + InternalName = @"all-intro-doubletime", + Description = @"More complex than you think.", + }), Add); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 039d8bfdb6..dad8fb8fed 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -1,233 +1,233 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets; -using osu.Game.Screens.Play.HUD; -using OpenTK; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using System.Linq; -using System.Collections.Generic; -using osu.Game.Rulesets.Osu; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; -using OpenTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [Description("mod select and icon display")] - public class TestCaseMods : OsuTestCase - { - private const string unranked_suffix = " (Unranked)"; - - private RulesetStore rulesets; - private ModDisplay modDisplay; - private TestModSelectOverlay modSelect; - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(modSelect = new TestModSelectOverlay - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); - - Add(modDisplay = new ModDisplay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Position = new Vector2(0, 25), - }); - - modDisplay.Current.BindTo(modSelect.SelectedMods); - - AddStep("Toggle", modSelect.ToggleVisibility); - AddStep("Hide", modSelect.Hide); - AddStep("Show", modSelect.Show); - - foreach (var rulesetInfo in rulesets.AvailableRulesets) - { - Ruleset ruleset = rulesetInfo.CreateInstance(); - AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo); - - switch (ruleset) { - case OsuRuleset or: - testOsuMods(or); - break; - case ManiaRuleset mr: - testManiaMods(mr); - break; - } - } - } - - private void testOsuMods(OsuRuleset ruleset) - { - var easierMods = ruleset.GetModsFor(ModType.DifficultyReduction); - var harderMods = ruleset.GetModsFor(ModType.DifficultyIncrease); - var assistMods = ruleset.GetModsFor(ModType.Special); - - var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); - var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); - - var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - - var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); - - var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); - var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); - - testSingleMod(noFailMod); - testMultiMod(doubleTimeMod); - testIncompatibleMods(easy, hardRock); - testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); - - testUnimplmentedMod(autoPilotMod); - } - - private void testManiaMods(ManiaRuleset ruleset) - { - testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom)); - } - - private void testSingleMod(Mod mod) - { - selectNext(mod); - checkSelected(mod); - - selectPrevious(mod); - checkNotSelected(mod); - - selectNext(mod); - selectNext(mod); - checkNotSelected(mod); - - selectPrevious(mod); - selectPrevious(mod); - checkNotSelected(mod); - } - - private void testMultiMod(MultiMod multiMod) - { - foreach (var mod in multiMod.Mods) - { - selectNext(mod); - checkSelected(mod); - } - - for (int index = multiMod.Mods.Length - 1; index >= 0; index--) - selectPrevious(multiMod.Mods[index]); - - foreach (var mod in multiMod.Mods) - checkNotSelected(mod); - } - - private void testUnimplmentedMod(Mod mod) - { - selectNext(mod); - checkNotSelected(mod); - } - - private void testIncompatibleMods(Mod modA, Mod modB) - { - selectNext(modA); - checkSelected(modA); - checkNotSelected(modB); - - selectNext(modB); - checkSelected(modB); - checkNotSelected(modA); - - selectPrevious(modB); - checkNotSelected(modA); - checkNotSelected(modB); - } - - private void testDeselectAll(IEnumerable mods) - { - foreach (var mod in mods) - selectNext(mod); - - AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); - AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke); - AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); - } - - private void testMultiplierTextColour(Mod mod, Color4 colour) - { - checkLabelColor(Color4.White); - selectNext(mod); - AddWaitStep(1, "wait for changing colour"); - checkLabelColor(colour); - selectPrevious(mod); - AddWaitStep(1, "wait for changing colour"); - checkLabelColor(Color4.White); - } - - private void testMultiplierTextUnranked(Mod mod) - { - AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); - selectNext(mod); - AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); - selectPrevious(mod); - AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); - } - - private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); - - private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1)); - - private void checkSelected(Mod mod) - { - AddAssert($"check {mod.Name} is selected", () => - { - var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; - }); - } - - private void checkNotSelected(Mod mod) - { - AddAssert($"check {mod.Name} is not selected", () => - { - var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.All(m => m.GetType() != mod.GetType()) && button.SelectedMod?.GetType() != mod.GetType(); - }); - } - - private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color); - - private class TestModSelectOverlay : ModSelectOverlay - { - public ModButton GetModButton(Mod mod) - { - var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); - return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); - } - - public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; - public new TriangleButton DeselectAllButton => base.DeselectAllButton; - - public new Color4 LowMultiplierColour => base.LowMultiplierColour; - public new Color4 HighMultiplierColour => base.HighMultiplierColour; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Screens.Play.HUD; +using OpenTK; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using System.Linq; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [Description("mod select and icon display")] + public class TestCaseMods : OsuTestCase + { + private const string unranked_suffix = " (Unranked)"; + + private RulesetStore rulesets; + private ModDisplay modDisplay; + private TestModSelectOverlay modSelect; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }); + + Add(modDisplay = new ModDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Position = new Vector2(0, 25), + }); + + modDisplay.Current.BindTo(modSelect.SelectedMods); + + AddStep("Toggle", modSelect.ToggleVisibility); + AddStep("Hide", modSelect.Hide); + AddStep("Show", modSelect.Show); + + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + Ruleset ruleset = rulesetInfo.CreateInstance(); + AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo); + + switch (ruleset) { + case OsuRuleset or: + testOsuMods(or); + break; + case ManiaRuleset mr: + testManiaMods(mr); + break; + } + } + } + + private void testOsuMods(OsuRuleset ruleset) + { + var easierMods = ruleset.GetModsFor(ModType.DifficultyReduction); + var harderMods = ruleset.GetModsFor(ModType.DifficultyIncrease); + var assistMods = ruleset.GetModsFor(ModType.Special); + + var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); + + var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); + + var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + + var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); + var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); + + testSingleMod(noFailMod); + testMultiMod(doubleTimeMod); + testIncompatibleMods(easy, hardRock); + testDeselectAll(easierMods.Where(m => !(m is MultiMod))); + testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); + testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + + testUnimplmentedMod(autoPilotMod); + } + + private void testManiaMods(ManiaRuleset ruleset) + { + testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom)); + } + + private void testSingleMod(Mod mod) + { + selectNext(mod); + checkSelected(mod); + + selectPrevious(mod); + checkNotSelected(mod); + + selectNext(mod); + selectNext(mod); + checkNotSelected(mod); + + selectPrevious(mod); + selectPrevious(mod); + checkNotSelected(mod); + } + + private void testMultiMod(MultiMod multiMod) + { + foreach (var mod in multiMod.Mods) + { + selectNext(mod); + checkSelected(mod); + } + + for (int index = multiMod.Mods.Length - 1; index >= 0; index--) + selectPrevious(multiMod.Mods[index]); + + foreach (var mod in multiMod.Mods) + checkNotSelected(mod); + } + + private void testUnimplmentedMod(Mod mod) + { + selectNext(mod); + checkNotSelected(mod); + } + + private void testIncompatibleMods(Mod modA, Mod modB) + { + selectNext(modA); + checkSelected(modA); + checkNotSelected(modB); + + selectNext(modB); + checkSelected(modB); + checkNotSelected(modA); + + selectPrevious(modB); + checkNotSelected(modA); + checkNotSelected(modB); + } + + private void testDeselectAll(IEnumerable mods) + { + foreach (var mod in mods) + selectNext(mod); + + AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); + AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke); + AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); + } + + private void testMultiplierTextColour(Mod mod, Color4 colour) + { + checkLabelColor(Color4.White); + selectNext(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(colour); + selectPrevious(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(Color4.White); + } + + private void testMultiplierTextUnranked(Mod mod) + { + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectNext(mod); + AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectPrevious(mod); + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + } + + private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); + + private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1)); + + private void checkSelected(Mod mod) + { + AddAssert($"check {mod.Name} is selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + }); + } + + private void checkNotSelected(Mod mod) + { + AddAssert($"check {mod.Name} is not selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.All(m => m.GetType() != mod.GetType()) && button.SelectedMod?.GetType() != mod.GetType(); + }); + } + + private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color); + + private class TestModSelectOverlay : ModSelectOverlay + { + public ModButton GetModButton(Mod mod) + { + var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); + return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + } + + public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; + public new TriangleButton DeselectAllButton => base.DeselectAllButton; + + public new Color4 LowMultiplierColour => base.LowMultiplierColour; + public new Color4 HighMultiplierColour => base.HighMultiplierColour; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/TestCaseMusicController.cs index 2ddc57d7b4..10c813b2f8 100644 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/TestCaseMusicController.cs @@ -1,42 +1,42 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMusicController : OsuTestCase - { - private readonly Bindable beatmapBacking = new Bindable(); - - public TestCaseMusicController() - { - Clock = new FramedClock(); - - var mc = new MusicController - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre - }; - Add(mc); - - AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); - AddStep(@"show", () => mc.State = Visibility.Visible); - AddToggleStep(@"toggle beatmap lock", state => beatmapBacking.Disabled = state); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - beatmapBacking.BindTo(game.Beatmap); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseMusicController : OsuTestCase + { + private readonly Bindable beatmapBacking = new Bindable(); + + public TestCaseMusicController() + { + Clock = new FramedClock(); + + var mc = new MusicController + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre + }; + Add(mc); + + AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); + AddStep(@"show", () => mc.State = Visibility.Visible); + AddToggleStep(@"toggle beatmap lock", state => beatmapBacking.Disabled = state); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + beatmapBacking.BindTo(game.Beatmap); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs index 2ba57f2bd2..2c56f08f42 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs @@ -1,166 +1,166 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.MathUtils; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseNotificationOverlay : OsuTestCase - { - private readonly NotificationOverlay manager; - private readonly List progressingNotifications = new List(); - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(NotificationSection), - typeof(SimpleNotification), - typeof(ProgressNotification), - typeof(ProgressCompletionNotification), - typeof(IHasCompletionTarget), - typeof(Notification) - }; - - public TestCaseNotificationOverlay() - { - progressingNotifications.Clear(); - - Content.Add(manager = new NotificationOverlay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }); - - SpriteText displayedCount = new SpriteText(); - - Content.Add(displayedCount); - - void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state); - void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected); - - manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; }; - - - setState(Visibility.Visible); - AddStep(@"simple #1", sendHelloNotification); - AddStep(@"simple #2", sendAmazingNotification); - AddStep(@"progress #1", sendUploadProgress); - AddStep(@"progress #2", sendDownloadProgress); - - checkProgressingCount(2); - - setState(Visibility.Hidden); - - AddRepeatStep(@"add many simple", sendManyNotifications, 3); - AddWaitStep(5); - - checkProgressingCount(0); - - AddStep(@"progress #3", sendUploadProgress); - - checkProgressingCount(1); - - AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33); - - AddWaitStep(10); - - checkProgressingCount(0); - - - setState(Visibility.Visible); - - //AddStep(@"barrage", () => sendBarrage()); - } - - private void sendBarrage(int remaining = 10) - { - switch (RNG.Next(0, 4)) - { - case 0: - sendHelloNotification(); - break; - case 1: - sendAmazingNotification(); - break; - case 2: - sendUploadProgress(); - break; - case 3: - sendDownloadProgress(); - break; - } - - if (remaining > 0) - Scheduler.AddDelayed(() => sendBarrage(remaining - 1), 80); - } - - protected override void Update() - { - base.Update(); - - progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed); - - if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) - { - var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued); - if (p != null) - p.State = ProgressNotificationState.Active; - } - - foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) - { - if (n.Progress < 1) - n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle(); - else - n.State = ProgressNotificationState.Completed; - } - } - - private void sendDownloadProgress() - { - var n = new ProgressNotification - { - Text = @"Downloading Haitai...", - CompletionText = "Downloaded Haitai!", - }; - manager.Post(n); - progressingNotifications.Add(n); - } - - private void sendUploadProgress() - { - var n = new ProgressNotification - { - Text = @"Uploading to BSS...", - CompletionText = "Uploaded to BSS!", - }; - manager.Post(n); - progressingNotifications.Add(n); - } - - private void sendAmazingNotification() - { - manager.Post(new SimpleNotification { Text = @"You are amazing" }); - } - - private void sendHelloNotification() - { - manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); - } - - private void sendManyNotifications() - { - for (int i = 0; i < 10; i++) - manager.Post(new SimpleNotification { Text = @"Spam incoming!!" }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.MathUtils; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseNotificationOverlay : OsuTestCase + { + private readonly NotificationOverlay manager; + private readonly List progressingNotifications = new List(); + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(NotificationSection), + typeof(SimpleNotification), + typeof(ProgressNotification), + typeof(ProgressCompletionNotification), + typeof(IHasCompletionTarget), + typeof(Notification) + }; + + public TestCaseNotificationOverlay() + { + progressingNotifications.Clear(); + + Content.Add(manager = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }); + + SpriteText displayedCount = new SpriteText(); + + Content.Add(displayedCount); + + void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state); + void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected); + + manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; }; + + + setState(Visibility.Visible); + AddStep(@"simple #1", sendHelloNotification); + AddStep(@"simple #2", sendAmazingNotification); + AddStep(@"progress #1", sendUploadProgress); + AddStep(@"progress #2", sendDownloadProgress); + + checkProgressingCount(2); + + setState(Visibility.Hidden); + + AddRepeatStep(@"add many simple", sendManyNotifications, 3); + AddWaitStep(5); + + checkProgressingCount(0); + + AddStep(@"progress #3", sendUploadProgress); + + checkProgressingCount(1); + + AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33); + + AddWaitStep(10); + + checkProgressingCount(0); + + + setState(Visibility.Visible); + + //AddStep(@"barrage", () => sendBarrage()); + } + + private void sendBarrage(int remaining = 10) + { + switch (RNG.Next(0, 4)) + { + case 0: + sendHelloNotification(); + break; + case 1: + sendAmazingNotification(); + break; + case 2: + sendUploadProgress(); + break; + case 3: + sendDownloadProgress(); + break; + } + + if (remaining > 0) + Scheduler.AddDelayed(() => sendBarrage(remaining - 1), 80); + } + + protected override void Update() + { + base.Update(); + + progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed); + + if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) + { + var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued); + if (p != null) + p.State = ProgressNotificationState.Active; + } + + foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) + { + if (n.Progress < 1) + n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle(); + else + n.State = ProgressNotificationState.Completed; + } + } + + private void sendDownloadProgress() + { + var n = new ProgressNotification + { + Text = @"Downloading Haitai...", + CompletionText = "Downloaded Haitai!", + }; + manager.Post(n); + progressingNotifications.Add(n); + } + + private void sendUploadProgress() + { + var n = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; + manager.Post(n); + progressingNotifications.Add(n); + } + + private void sendAmazingNotification() + { + manager.Post(new SimpleNotification { Text = @"You are amazing" }); + } + + private void sendHelloNotification() + { + manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); + } + + private void sendManyNotifications() + { + for (int i = 0; i < 10; i++) + manager.Post(new SimpleNotification { Text = @"Spam incoming!!" }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs index 6fe8bc5a8a..233c418d4a 100644 --- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseOnScreenDisplay : OsuTestCase - { - private FrameworkConfigManager config; - private Bindable frameSyncMode; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new OnScreenDisplay()); - - frameSyncMode = config.GetBindable(FrameworkSetting.FrameSync); - - FrameSync initial = frameSyncMode.Value; - - AddRepeatStep(@"Change frame limiter", setNextMode, 3); - - AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial); - } - - private void setNextMode() - { - var nextMode = frameSyncMode.Value + 1; - if (nextMode > FrameSync.Unlimited) - nextMode = FrameSync.VSync; - frameSyncMode.Value = nextMode; - } - - [BackgroundDependencyLoader] - private void load(FrameworkConfigManager config) - { - this.config = config; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseOnScreenDisplay : OsuTestCase + { + private FrameworkConfigManager config; + private Bindable frameSyncMode; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(new OnScreenDisplay()); + + frameSyncMode = config.GetBindable(FrameworkSetting.FrameSync); + + FrameSync initial = frameSyncMode.Value; + + AddRepeatStep(@"Change frame limiter", setNextMode, 3); + + AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial); + } + + private void setNextMode() + { + var nextMode = frameSyncMode.Value + 1; + if (nextMode > FrameSync.Unlimited) + nextMode = FrameSync.VSync; + frameSyncMode.Value = nextMode; + } + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager config) + { + this.config = config; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs index a802db6a10..f1a21a58d5 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs +++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Screens; -using osu.Game.Screens.Menu; -using OpenTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseOsuGame : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuLogo), - }; - - public TestCaseOsuGame() - { - var rateAdjustClock = new StopwatchClock(true); - var framedClock = new FramedClock(rateAdjustClock); - framedClock.ProcessFrame(); - - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }); - - Add(new Loader()); - - AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseOsuGame : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + public TestCaseOsuGame() + { + var rateAdjustClock = new StopwatchClock(true); + var framedClock = new FramedClock(rateAdjustClock); + framedClock.ProcessFrame(); + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }); + + Add(new Loader()); + + AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 5fd8fcc9c3..8c52360db8 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -1,173 +1,173 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Framework.MathUtils; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Rulesets; -using osu.Game.Screens.Select; -using osu.Game.Screens.Select.Carousel; -using osu.Game.Screens.Select.Filter; -using osu.Game.Tests.Platform; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCasePlaySongSelect : OsuTestCase - { - private BeatmapManager manager; - - private RulesetStore rulesets; - - private DependencyContainer dependencies; - private WorkingBeatmap defaultBeatmap; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(SongSelect), - typeof(BeatmapCarousel), - - typeof(CarouselItem), - typeof(CarouselGroup), - typeof(CarouselGroupEagerSelect), - typeof(CarouselBeatmap), - typeof(CarouselBeatmapSet), - - typeof(DrawableCarouselItem), - typeof(CarouselItemState), - - typeof(DrawableCarouselBeatmap), - typeof(DrawableCarouselBeatmapSet), - }; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - - private class TestSongSelect : PlaySongSelect - { - public WorkingBeatmap CurrentBeatmap => Beatmap.Value; - public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; - public new BeatmapCarousel Carousel => base.Carousel; - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - TestSongSelect songSelect = null; - - var storage = new TestStorage(@"TestCasePlaySongSelect"); - - // this is by no means clean. should be replacing inside of OsuGameBase somehow. - IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext()); - - dependencies.Cache(rulesets = new RulesetStore(factory)); - dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) - { - DefaultBeatmap = defaultBeatmap = game.Beatmap.Default - }); - - void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => - { - if (deleteMaps) - { - manager.Delete(manager.GetAllUsableBeatmapSets()); - game.Beatmap.SetDefault(); - } - - if (songSelect != null) - { - Remove(songSelect); - songSelect.Dispose(); - } - - Add(songSelect = new TestSongSelect()); - }); - - loadNewSongSelect(true); - - AddWaitStep(3); - - AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); - - AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); - - AddStep("import test maps", () => - { - for (int i = 0; i < 100; i += 10) - manager.Import(createTestBeatmapSet(i)); - }); - - AddWaitStep(3); - AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); - - loadNewSongSelect(); - AddWaitStep(3); - AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); - - AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); - AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); - AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); - AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); - } - - private BeatmapSetInfo createTestBeatmapSet(int i) - { - return new BeatmapSetInfo - { - OnlineBeatmapSetID = 1234 + i, - Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), - Metadata = new BeatmapMetadata - { - OnlineBeatmapSetID = 1234 + i, - // Create random metadata, then we can check if sorting works based on these - Artist = "MONACA " + RNG.Next(0, 9), - Title = "Black Song " + RNG.Next(0, 9), - AuthorString = "Some Guy " + RNG.Next(0, 9), - }, - Beatmaps = new List(new[] - { - new BeatmapInfo - { - OnlineBeatmapID = 1234 + i, - Ruleset = rulesets.AvailableRulesets.First(), - Path = "normal.osu", - Version = "Normal", - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = 1235 + i, - Ruleset = rulesets.AvailableRulesets.First(), - Path = "hard.osu", - Version = "Hard", - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 5, - } - }, - new BeatmapInfo - { - OnlineBeatmapID = 1236 + i, - Ruleset = rulesets.AvailableRulesets.First(), - Path = "insane.osu", - Version = "Insane", - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 7, - } - }, - }), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; +using osu.Game.Screens.Select.Filter; +using osu.Game.Tests.Platform; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCasePlaySongSelect : OsuTestCase + { + private BeatmapManager manager; + + private RulesetStore rulesets; + + private DependencyContainer dependencies; + private WorkingBeatmap defaultBeatmap; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SongSelect), + typeof(BeatmapCarousel), + + typeof(CarouselItem), + typeof(CarouselGroup), + typeof(CarouselGroupEagerSelect), + typeof(CarouselBeatmap), + typeof(CarouselBeatmapSet), + + typeof(DrawableCarouselItem), + typeof(CarouselItemState), + + typeof(DrawableCarouselBeatmap), + typeof(DrawableCarouselBeatmapSet), + }; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + + private class TestSongSelect : PlaySongSelect + { + public WorkingBeatmap CurrentBeatmap => Beatmap.Value; + public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; + public new BeatmapCarousel Carousel => base.Carousel; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + TestSongSelect songSelect = null; + + var storage = new TestStorage(@"TestCasePlaySongSelect"); + + // this is by no means clean. should be replacing inside of OsuGameBase somehow. + IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext()); + + dependencies.Cache(rulesets = new RulesetStore(factory)); + dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) + { + DefaultBeatmap = defaultBeatmap = game.Beatmap.Default + }); + + void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => + { + if (deleteMaps) + { + manager.Delete(manager.GetAllUsableBeatmapSets()); + game.Beatmap.SetDefault(); + } + + if (songSelect != null) + { + Remove(songSelect); + songSelect.Dispose(); + } + + Add(songSelect = new TestSongSelect()); + }); + + loadNewSongSelect(true); + + AddWaitStep(3); + + AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); + + AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); + + AddStep("import test maps", () => + { + for (int i = 0; i < 100; i += 10) + manager.Import(createTestBeatmapSet(i)); + }); + + AddWaitStep(3); + AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + + loadNewSongSelect(); + AddWaitStep(3); + AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + + AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); + AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); + AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); + AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); + } + + private BeatmapSetInfo createTestBeatmapSet(int i) + { + return new BeatmapSetInfo + { + OnlineBeatmapSetID = 1234 + i, + Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), + Metadata = new BeatmapMetadata + { + OnlineBeatmapSetID = 1234 + i, + // Create random metadata, then we can check if sorting works based on these + Artist = "MONACA " + RNG.Next(0, 9), + Title = "Black Song " + RNG.Next(0, 9), + AuthorString = "Some Guy " + RNG.Next(0, 9), + }, + Beatmaps = new List(new[] + { + new BeatmapInfo + { + OnlineBeatmapID = 1234 + i, + Ruleset = rulesets.AvailableRulesets.First(), + Path = "normal.osu", + Version = "Normal", + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = 1235 + i, + Ruleset = rulesets.AvailableRulesets.First(), + Path = "hard.osu", + Version = "Hard", + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 5, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = 1236 + i, + Ruleset = rulesets.AvailableRulesets.First(), + Path = "insane.osu", + Version = "Insane", + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 7, + } + }, + }), + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs index 9cdb3e36e3..24ebb534c1 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs @@ -1,42 +1,42 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Screens.Edit.Components; -using osu.Game.Tests.Beatmaps; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCasePlaybackControl : OsuTestCase - { - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - [BackgroundDependencyLoader] - private void load() - { - var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); - - var playback = new PlaybackControl - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200,100) - }; - - playback.Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); - - Child = playback; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Edit.Components; +using osu.Game.Tests.Beatmaps; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCasePlaybackControl : OsuTestCase + { + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + + [BackgroundDependencyLoader] + private void load() + { + var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); + + var playback = new PlaybackControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200,100) + }; + + playback.Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); + + Child = playback; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/TestCasePopupDialog.cs index 8d830672b7..d88be1e7c7 100644 --- a/osu.Game.Tests/Visual/TestCasePopupDialog.cs +++ b/osu.Game.Tests/Visual/TestCasePopupDialog.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Dialog; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCasePopupDialog : OsuTestCase - { - public TestCasePopupDialog() - { - var popup = new PopupDialog - { - RelativeSizeAxes = Axes.Both, - State = Framework.Graphics.Containers.Visibility.Visible, - Icon = FontAwesome.fa_assistive_listening_systems, - HeaderText = @"This is a test popup", - BodyText = "I can say lots of stuff and even wrap my words!", - Buttons = new PopupDialogButton[] - { - new PopupDialogCancelButton - { - Text = @"Yes. That you can.", - }, - new PopupDialogOkButton - { - Text = @"You're a fake!", - }, - } - }; - - Add(popup); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCasePopupDialog : OsuTestCase + { + public TestCasePopupDialog() + { + var popup = new PopupDialog + { + RelativeSizeAxes = Axes.Both, + State = Framework.Graphics.Containers.Visibility.Visible, + Icon = FontAwesome.fa_assistive_listening_systems, + HeaderText = @"This is a test popup", + BodyText = "I can say lots of stuff and even wrap my words!", + Buttons = new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = @"Yes. That you can.", + }, + new PopupDialogOkButton + { + Text = @"You're a fake!", + }, + } + }; + + Add(popup); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs index ad53238e76..45f6651537 100644 --- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs @@ -1,125 +1,125 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Overlays.Profile; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using System.Collections.Generic; -using System; -using NUnit.Framework; -using osu.Game.Graphics.UserInterface; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseRankGraph : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(RankGraph), - typeof(LineGraph) - }; - - public TestCaseRankGraph() - { - RankGraph graph; - - var data = new int[89]; - var dataWithZeros = new int[89]; - var smallData = new int[89]; - - for (int i = 0; i < 89; i++) - data[i] = dataWithZeros[i] = (i + 1) * 1000; - - for (int i = 20; i < 60; i++) - dataWithZeros[i] = 0; - - for (int i = 79; i < 89; i++) - smallData[i] = 100000 - i * 1000; - - Add(new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(300, 150), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, - graph = new RankGraph - { - RelativeSizeAxes = Axes.Both, - } - } - }); - - AddStep("null user", () => graph.User.Value = null); - AddStep("rank only", () => - { - graph.User.Value = new User - { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 123456 }, - PP = 12345, - } - }; - }); - - AddStep("with rank history", () => - { - graph.User.Value = new User - { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, - PP = 12345, - }, - RankHistory = new User.RankHistoryData - { - Data = data, - } - }; - }); - - AddStep("with zero values", () => - { - graph.User.Value = new User - { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, - PP = 12345, - }, - RankHistory = new User.RankHistoryData - { - Data = dataWithZeros, - } - }; - }); - - AddStep("small amount of data", () => - { - graph.User.Value = new User - { - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 12000 }, - PP = 12345, - }, - RankHistory = new User.RankHistoryData - { - Data = smallData, - } - }; - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.Profile; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using System.Collections.Generic; +using System; +using NUnit.Framework; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseRankGraph : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankGraph), + typeof(LineGraph) + }; + + public TestCaseRankGraph() + { + RankGraph graph; + + var data = new int[89]; + var dataWithZeros = new int[89]; + var smallData = new int[89]; + + for (int i = 0; i < 89; i++) + data[i] = dataWithZeros[i] = (i + 1) * 1000; + + for (int i = 20; i < 60; i++) + dataWithZeros[i] = 0; + + for (int i = 79; i < 89; i++) + smallData[i] = 100000 - i * 1000; + + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 150), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + graph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + } + } + }); + + AddStep("null user", () => graph.User.Value = null); + AddStep("rank only", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 123456 }, + PP = 12345, + } + }; + }); + + AddStep("with rank history", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 89000 }, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = data, + } + }; + }); + + AddStep("with zero values", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 89000 }, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = dataWithZeros, + } + }; + }); + + AddStep("small amount of data", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 12000 }, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = smallData, + } + }; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 115ac11919..6ba671c7fc 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [Description("Player instantiated with a replay.")] - public class TestCaseReplay : TestCasePlayer - { - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) - { - // We create a dummy RulesetContainer just to get the replay - we don't want to use mods here - // to simulate setting a replay rather than having the replay already set for us - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo)); - - // We have the replay - var replay = dummyRulesetContainer.Replay; - - // Reset the mods - beatmap.Mods.Value = beatmap.Mods.Value.Where(m => !(m is ModAutoplay)); - - return new ReplayPlayer(replay) - { - InitialBeatmap = beatmap - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [Description("Player instantiated with a replay.")] + public class TestCaseReplay : TestCasePlayer + { + protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + { + // We create a dummy RulesetContainer just to get the replay - we don't want to use mods here + // to simulate setting a replay rather than having the replay already set for us + beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo)); + + // We have the replay + var replay = dummyRulesetContainer.Replay; + + // Reset the mods + beatmap.Mods.Value = beatmap.Mods.Value.Where(m => !(m is ModAutoplay)); + + return new ReplayPlayer(replay) + { + InitialBeatmap = beatmap + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs index a1b683b64c..a57cc5b79d 100644 --- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs @@ -1,52 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.PlayerSettings; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseReplaySettingsOverlay : OsuTestCase - { - public TestCaseReplaySettingsOverlay() - { - ExampleContainer container; - - Add(new PlayerSettingsOverlay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }); - - Add(container = new ExampleContainer()); - - AddStep(@"Add button", () => container.Add(new TriangleButton - { - RelativeSizeAxes = Axes.X, - Text = @"Button", - })); - - AddStep(@"Add checkbox", () => container.Add(new PlayerCheckbox - { - LabelText = "Checkbox", - })); - - AddStep(@"Add textbox", () => container.Add(new FocusedTextBox - { - RelativeSizeAxes = Axes.X, - Height = 30, - PlaceholderText = "Textbox", - HoldFocus = false, - })); - } - - private class ExampleContainer : PlayerSettingsGroup - { - protected override string Title => @"example"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.PlayerSettings; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseReplaySettingsOverlay : OsuTestCase + { + public TestCaseReplaySettingsOverlay() + { + ExampleContainer container; + + Add(new PlayerSettingsOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }); + + Add(container = new ExampleContainer()); + + AddStep(@"Add button", () => container.Add(new TriangleButton + { + RelativeSizeAxes = Axes.X, + Text = @"Button", + })); + + AddStep(@"Add checkbox", () => container.Add(new PlayerCheckbox + { + LabelText = "Checkbox", + })); + + AddStep(@"Add textbox", () => container.Add(new FocusedTextBox + { + RelativeSizeAxes = Axes.X, + Height = 30, + PlaceholderText = "Textbox", + HoldFocus = false, + })); + } + + private class ExampleContainer : PlayerSettingsGroup + { + protected override string Title => @"example"; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index 06bdfdb7e1..35e1db7c9e 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -1,72 +1,72 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Ranking; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseResults : OsuTestCase - { - private BeatmapManager beatmaps; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(Score), - typeof(Results), - typeof(ResultsPage), - typeof(ResultsPageScore), - typeof(ResultsPageRanking) - }; - - [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) - { - this.beatmaps = beatmaps; - } - - private WorkingBeatmap beatmap; - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (beatmap == null) - { - var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); - if (beatmapInfo != null) - beatmap = beatmaps.GetWorkingBeatmap(beatmapInfo); - } - - Add(new Results(new Score - { - TotalScore = 2845370, - Accuracy = 0.98, - MaxCombo = 123, - Rank = ScoreRank.A, - Date = DateTimeOffset.Now, - Statistics = new Dictionary - { - { HitResult.Great, 50 }, - { HitResult.Good, 20 }, - { HitResult.Meh, 50 }, - { HitResult.Miss, 1 } - }, - User = new User - { - Username = "peppy", - } - }) - { - InitialBeatmap = beatmap - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseResults : OsuTestCase + { + private BeatmapManager beatmaps; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Score), + typeof(Results), + typeof(ResultsPage), + typeof(ResultsPageScore), + typeof(ResultsPageRanking) + }; + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + } + + private WorkingBeatmap beatmap; + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (beatmap == null) + { + var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); + if (beatmapInfo != null) + beatmap = beatmaps.GetWorkingBeatmap(beatmapInfo); + } + + Add(new Results(new Score + { + TotalScore = 2845370, + Accuracy = 0.98, + MaxCombo = 123, + Rank = ScoreRank.A, + Date = DateTimeOffset.Now, + Statistics = new Dictionary + { + { HitResult.Great, 50 }, + { HitResult.Good, 20 }, + { HitResult.Meh, 50 }, + { HitResult.Miss, 1 } + }, + User = new User + { + Username = "peppy", + } + }) + { + InitialBeatmap = beatmap + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs index c45312392f..88059d2dcf 100644 --- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs +++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs @@ -1,143 +1,143 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; -using osu.Game.Screens.Multiplayer; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseRoomInspector : OsuTestCase - { - private RulesetStore rulesets; - - protected override void LoadComplete() - { - base.LoadComplete(); - - var room = new Room - { - Name = { Value = @"My Awesome Room" }, - Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } }, - Status = { Value = new RoomStatusOpen() }, - Type = { Value = new GameTypeTeamVersus() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 3.7, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata - { - Title = @"Platina", - Artist = @"Maaya Sakamoto", - AuthorString = @"uwutm8", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/560573/covers/cover.jpg?1492722343", - }, - }, - }, - } - }, - MaxParticipants = { Value = 200 }, - Participants = - { - Value = new[] - { - new User { Username = @"flyte", Id = 3103765, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 142 } } }, - new User { Username = @"Cookiezi", Id = 124493, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 546 } } }, - new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 287 } } }, - new User { Username = @"Rafis", Id = 2558286, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 468 } } }, - new User { Username = @"hvick225", Id = 50265, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 325 } } }, - new User { Username = @"peppy", Id = 2, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 625 } } }, - } - } - }; - - RoomInspector inspector; - Add(inspector = new RoomInspector - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Room = room, - }); - - AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above"); - AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } }); - AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying()); - AddStep(@"change type", () => room.Type.Value = new GameTypeTag()); - AddStep(@"change beatmap", () => room.Beatmap.Value = null); - AddStep(@"change max participants", () => room.MaxParticipants.Value = null); - AddStep(@"change participants", () => room.Participants.Value = new[] - { - new User { Username = @"filsdelama", Id = 2831793, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 854 } } }, - new User { Username = @"_index", Id = 652457, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 150 } } } - }); - - AddStep(@"change room", () => - { - var newRoom = new Room - { - Name = { Value = @"My New, Better Than Ever Room" }, - Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } }, - Status = { Value = new RoomStatusOpen() }, - Type = { Value = new GameTypeTagTeam() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 7.07, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata - { - Title = @"FREEDOM DIVE", - Artist = @"xi", - AuthorString = @"Nakagawa-Kanon", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/39804/covers/cover.jpg?1456506845", - }, - }, - }, - }, - }, - MaxParticipants = { Value = 10 }, - Participants = - { - Value = new[] - { - new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 4 } } }, - new User { Username = @"HappyStick", Id = 256802, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 752 } } }, - new User { Username = @"-Konpaku-", Id = 2258797, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 571 } } } - } - } - }; - - inspector.Room = newRoom; - }); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Screens.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseRoomInspector : OsuTestCase + { + private RulesetStore rulesets; + + protected override void LoadComplete() + { + base.LoadComplete(); + + var room = new Room + { + Name = { Value = @"My Awesome Room" }, + Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } }, + Status = { Value = new RoomStatusOpen() }, + Type = { Value = new GameTypeTeamVersus() }, + Beatmap = + { + Value = new BeatmapInfo + { + StarDifficulty = 3.7, + Ruleset = rulesets.GetRuleset(3), + Metadata = new BeatmapMetadata + { + Title = @"Platina", + Artist = @"Maaya Sakamoto", + AuthorString = @"uwutm8", + }, + BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh/beatmaps/560573/covers/cover.jpg?1492722343", + }, + }, + }, + } + }, + MaxParticipants = { Value = 200 }, + Participants = + { + Value = new[] + { + new User { Username = @"flyte", Id = 3103765, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 142 } } }, + new User { Username = @"Cookiezi", Id = 124493, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 546 } } }, + new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 287 } } }, + new User { Username = @"Rafis", Id = 2558286, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 468 } } }, + new User { Username = @"hvick225", Id = 50265, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 325 } } }, + new User { Username = @"peppy", Id = 2, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 625 } } }, + } + } + }; + + RoomInspector inspector; + Add(inspector = new RoomInspector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Room = room, + }); + + AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above"); + AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } }); + AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying()); + AddStep(@"change type", () => room.Type.Value = new GameTypeTag()); + AddStep(@"change beatmap", () => room.Beatmap.Value = null); + AddStep(@"change max participants", () => room.MaxParticipants.Value = null); + AddStep(@"change participants", () => room.Participants.Value = new[] + { + new User { Username = @"filsdelama", Id = 2831793, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 854 } } }, + new User { Username = @"_index", Id = 652457, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 150 } } } + }); + + AddStep(@"change room", () => + { + var newRoom = new Room + { + Name = { Value = @"My New, Better Than Ever Room" }, + Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } }, + Status = { Value = new RoomStatusOpen() }, + Type = { Value = new GameTypeTagTeam() }, + Beatmap = + { + Value = new BeatmapInfo + { + StarDifficulty = 7.07, + Ruleset = rulesets.GetRuleset(0), + Metadata = new BeatmapMetadata + { + Title = @"FREEDOM DIVE", + Artist = @"xi", + AuthorString = @"Nakagawa-Kanon", + }, + BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh/beatmaps/39804/covers/cover.jpg?1456506845", + }, + }, + }, + }, + }, + MaxParticipants = { Value = 10 }, + Participants = + { + Value = new[] + { + new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 4 } } }, + new User { Username = @"HappyStick", Id = 256802, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 752 } } }, + new User { Username = @"-Konpaku-", Id = 2258797, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 571 } } } + } + } + }; + + inspector.Room = newRoom; + }); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs index e657035355..4dff7547d9 100644 --- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs @@ -1,106 +1,106 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.MathUtils; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play.HUD; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseScoreCounter : OsuTestCase - { - public TestCaseScoreCounter() - { - int numerator = 0, denominator = 0; - - ScoreCounter score = new ScoreCounter(7) - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - TextSize = 40, - Margin = new MarginPadding(20), - }; - Add(score); - - ComboCounter comboCounter = new StandardComboCounter - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Margin = new MarginPadding(10), - TextSize = 40, - }; - Add(comboCounter); - - PercentageCounter accuracyCounter = new PercentageCounter - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Position = new Vector2(-20, 60), - }; - Add(accuracyCounter); - - StarCounter stars = new StarCounter - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Position = new Vector2(20, -160), - CountStars = 5, - }; - Add(stars); - - SpriteText starsLabel = new SpriteText - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Position = new Vector2(20, -190), - Text = stars.CountStars.ToString("0.00"), - }; - Add(starsLabel); - - AddStep(@"Reset all", delegate - { - score.Current.Value = 0; - comboCounter.Current.Value = 0; - numerator = denominator = 0; - accuracyCounter.SetFraction(0, 0); - stars.CountStars = 0; - starsLabel.Text = stars.CountStars.ToString("0.00"); - }); - - AddStep(@"Hit! :D", delegate - { - score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current > 0 ? comboCounter.Current - 1 : 0) / 25.0); - comboCounter.Increment(); - numerator++; - denominator++; - accuracyCounter.SetFraction(numerator, denominator); - }); - - AddStep(@"miss...", delegate - { - comboCounter.Current.Value = 0; - denominator++; - accuracyCounter.SetFraction(numerator, denominator); - }); - - AddStep(@"Alter stars", delegate - { - stars.CountStars = RNG.NextSingle() * (stars.StarCount + 1); - starsLabel.Text = stars.CountStars.ToString("0.00"); - }); - - AddStep(@"Stop counters", delegate - { - score.StopRolling(); - comboCounter.StopRolling(); - accuracyCounter.StopRolling(); - stars.StopAnimation(); - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.MathUtils; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play.HUD; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseScoreCounter : OsuTestCase + { + public TestCaseScoreCounter() + { + int numerator = 0, denominator = 0; + + ScoreCounter score = new ScoreCounter(7) + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + TextSize = 40, + Margin = new MarginPadding(20), + }; + Add(score); + + ComboCounter comboCounter = new StandardComboCounter + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Margin = new MarginPadding(10), + TextSize = 40, + }; + Add(comboCounter); + + PercentageCounter accuracyCounter = new PercentageCounter + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Position = new Vector2(-20, 60), + }; + Add(accuracyCounter); + + StarCounter stars = new StarCounter + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Position = new Vector2(20, -160), + CountStars = 5, + }; + Add(stars); + + SpriteText starsLabel = new SpriteText + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Position = new Vector2(20, -190), + Text = stars.CountStars.ToString("0.00"), + }; + Add(starsLabel); + + AddStep(@"Reset all", delegate + { + score.Current.Value = 0; + comboCounter.Current.Value = 0; + numerator = denominator = 0; + accuracyCounter.SetFraction(0, 0); + stars.CountStars = 0; + starsLabel.Text = stars.CountStars.ToString("0.00"); + }); + + AddStep(@"Hit! :D", delegate + { + score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current > 0 ? comboCounter.Current - 1 : 0) / 25.0); + comboCounter.Increment(); + numerator++; + denominator++; + accuracyCounter.SetFraction(numerator, denominator); + }); + + AddStep(@"miss...", delegate + { + comboCounter.Current.Value = 0; + denominator++; + accuracyCounter.SetFraction(numerator, denominator); + }); + + AddStep(@"Alter stars", delegate + { + stars.CountStars = RNG.NextSingle() * (stars.StarCount + 1); + starsLabel.Text = stars.CountStars.ToString("0.00"); + }); + + AddStep(@"Stop counters", delegate + { + score.StopRolling(); + comboCounter.StopRolling(); + accuracyCounter.StopRolling(); + stars.StopAnimation(); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs index 0742dd68eb..8f90b6d87e 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -1,186 +1,186 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Timing; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseScrollingHitObjects : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; - - private readonly TestPlayfield[] playfields = new TestPlayfield[4]; - - public TestCaseScrollingHitObjects() - { - Add(new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - playfields[0] = new TestPlayfield(ScrollingDirection.Up), - playfields[1] = new TestPlayfield(ScrollingDirection.Down) - }, - new Drawable[] - { - playfields[2] = new TestPlayfield(ScrollingDirection.Left), - playfields[3] = new TestPlayfield(ScrollingDirection.Right) - } - } - }); - - AddSliderStep("Time range", 100, 10000, 5000, v => playfields.ForEach(p => p.VisibleTimeRange.Value = v)); - AddStep("Add control point", () => addControlPoint(Time.Current + 5000)); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - playfields.ForEach(p => p.HitObjects.AddControlPoint(new MultiplierControlPoint(0))); - - for (int i = 0; i <= 5000; i += 1000) - addHitObject(Time.Current + i); - - Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true); - } - - private void addHitObject(double time) - { - playfields.ForEach(p => - { - var hitObject = new TestDrawableHitObject(time); - setAnchor(hitObject, p); - - p.Add(hitObject); - }); - } - - private void addControlPoint(double time) - { - playfields.ForEach(p => - { - p.HitObjects.AddControlPoint(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } }); - p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } }); - p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } }); - - TestDrawableControlPoint createDrawablePoint(double t) - { - var obj = new TestDrawableControlPoint(p.Direction, t); - setAnchor(obj, p); - return obj; - } - - p.Add(createDrawablePoint(time)); - p.Add(createDrawablePoint(time + 2000)); - p.Add(createDrawablePoint(time + 3000)); - }); - } - - private void setAnchor(DrawableHitObject obj, TestPlayfield playfield) - { - switch (playfield.Direction) - { - case ScrollingDirection.Up: - obj.Anchor = Anchor.TopCentre; - break; - case ScrollingDirection.Down: - obj.Anchor = Anchor.BottomCentre; - break; - case ScrollingDirection.Left: - obj.Anchor = Anchor.CentreLeft; - break; - case ScrollingDirection.Right: - obj.Anchor = Anchor.CentreRight; - break; - } - } - - - private class TestPlayfield : ScrollingPlayfield - { - public readonly ScrollingDirection Direction; - - public TestPlayfield(ScrollingDirection direction) - : base(direction) - { - Direction = direction; - - Padding = new MarginPadding(2); - Content.Masking = true; - - AddInternal(new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.5f, - Depth = float.MaxValue - }); - } - } - - private class TestDrawableControlPoint : DrawableHitObject - { - public TestDrawableControlPoint(ScrollingDirection direction, double time) - : base(new HitObject { StartTime = time }) - { - Origin = Anchor.Centre; - - InternalChild = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both - }; - - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - RelativeSizeAxes = Axes.X; - Height = 2; - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - RelativeSizeAxes = Axes.Y; - Width = 2; - break; - } - } - - protected override void UpdateState(ArmedState state) - { - } - } - - private class TestDrawableHitObject : DrawableHitObject - { - public TestDrawableHitObject(double time) - : base(new HitObject { StartTime = time }) - { - Origin = Anchor.Centre; - AutoSizeAxes = Axes.Both; - - InternalChild = new Box { Size = new Vector2(75) }; - } - - protected override void UpdateState(ArmedState state) - { - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseScrollingHitObjects : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; + + private readonly TestPlayfield[] playfields = new TestPlayfield[4]; + + public TestCaseScrollingHitObjects() + { + Add(new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + playfields[0] = new TestPlayfield(ScrollingDirection.Up), + playfields[1] = new TestPlayfield(ScrollingDirection.Down) + }, + new Drawable[] + { + playfields[2] = new TestPlayfield(ScrollingDirection.Left), + playfields[3] = new TestPlayfield(ScrollingDirection.Right) + } + } + }); + + AddSliderStep("Time range", 100, 10000, 5000, v => playfields.ForEach(p => p.VisibleTimeRange.Value = v)); + AddStep("Add control point", () => addControlPoint(Time.Current + 5000)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + playfields.ForEach(p => p.HitObjects.AddControlPoint(new MultiplierControlPoint(0))); + + for (int i = 0; i <= 5000; i += 1000) + addHitObject(Time.Current + i); + + Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true); + } + + private void addHitObject(double time) + { + playfields.ForEach(p => + { + var hitObject = new TestDrawableHitObject(time); + setAnchor(hitObject, p); + + p.Add(hitObject); + }); + } + + private void addControlPoint(double time) + { + playfields.ForEach(p => + { + p.HitObjects.AddControlPoint(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } }); + p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } }); + p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } }); + + TestDrawableControlPoint createDrawablePoint(double t) + { + var obj = new TestDrawableControlPoint(p.Direction, t); + setAnchor(obj, p); + return obj; + } + + p.Add(createDrawablePoint(time)); + p.Add(createDrawablePoint(time + 2000)); + p.Add(createDrawablePoint(time + 3000)); + }); + } + + private void setAnchor(DrawableHitObject obj, TestPlayfield playfield) + { + switch (playfield.Direction) + { + case ScrollingDirection.Up: + obj.Anchor = Anchor.TopCentre; + break; + case ScrollingDirection.Down: + obj.Anchor = Anchor.BottomCentre; + break; + case ScrollingDirection.Left: + obj.Anchor = Anchor.CentreLeft; + break; + case ScrollingDirection.Right: + obj.Anchor = Anchor.CentreRight; + break; + } + } + + + private class TestPlayfield : ScrollingPlayfield + { + public readonly ScrollingDirection Direction; + + public TestPlayfield(ScrollingDirection direction) + : base(direction) + { + Direction = direction; + + Padding = new MarginPadding(2); + Content.Masking = true; + + AddInternal(new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + Depth = float.MaxValue + }); + } + } + + private class TestDrawableControlPoint : DrawableHitObject + { + public TestDrawableControlPoint(ScrollingDirection direction, double time) + : base(new HitObject { StartTime = time }) + { + Origin = Anchor.Centre; + + InternalChild = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }; + + switch (direction) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + RelativeSizeAxes = Axes.X; + Height = 2; + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + RelativeSizeAxes = Axes.Y; + Width = 2; + break; + } + } + + protected override void UpdateState(ArmedState state) + { + } + } + + private class TestDrawableHitObject : DrawableHitObject + { + public TestDrawableHitObject(double time) + : base(new HitObject { StartTime = time }) + { + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + InternalChild = new Box { Size = new Vector2(75) }; + } + + protected override void UpdateState(ArmedState state) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs index 3f42f2e863..5dad48c6d7 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/TestCaseSettings.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseSettings : OsuTestCase - { - private readonly SettingsOverlay settings; - private readonly DialogOverlay dialogOverlay; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - - public TestCaseSettings() - { - settings = new MainSettings - { - State = Visibility.Visible - }; - Add(dialogOverlay = new DialogOverlay - { - Depth = -1 - }); - } - - [BackgroundDependencyLoader] - private void load() - { - dependencies.Cache(dialogOverlay); - - Add(settings); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseSettings : OsuTestCase + { + private readonly SettingsOverlay settings; + private readonly DialogOverlay dialogOverlay; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + + public TestCaseSettings() + { + settings = new MainSettings + { + State = Visibility.Visible + }; + Add(dialogOverlay = new DialogOverlay + { + Depth = -1 + }); + } + + [BackgroundDependencyLoader] + private void load() + { + dependencies.Cache(dialogOverlay); + + Add(settings); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipButton.cs index df94d5147f..4f381fd7a8 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/TestCaseSkipButton.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseSkipButton : OsuTestCase - { - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new SkipOverlay(Clock.CurrentTime + 5000)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseSkipButton : OsuTestCase + { + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(new SkipOverlay(Clock.CurrentTime + 5000)); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseSocial.cs b/osu.Game.Tests/Visual/TestCaseSocial.cs index 4003d834d5..03f8fccae6 100644 --- a/osu.Game.Tests/Visual/TestCaseSocial.cs +++ b/osu.Game.Tests/Visual/TestCaseSocial.cs @@ -1,95 +1,95 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Game.Overlays; -using osu.Game.Overlays.Social; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseSocial : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(UserPanel), - typeof(SocialPanel), - typeof(FilterControl), - typeof(SocialOverlay), - typeof(SocialGridPanel), - typeof(SocialListPanel) - }; - - public TestCaseSocial() - { - SocialOverlay s = new SocialOverlay - { - Users = new[] - { - new User - { - Username = @"flyte", - Id = 3103765, - Country = new Country { FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - }, - new User - { - Username = @"Cookiezi", - Id = 124493, - Country = new Country { FlagName = @"KR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", - }, - new User - { - Username = @"Angelsim", - Id = 1777162, - Country = new Country { FlagName = @"KR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - new User - { - Username = @"Rafis", - Id = 2558286, - Country = new Country { FlagName = @"PL" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg", - }, - new User - { - Username = @"hvick225", - Id = 50265, - Country = new Country { FlagName = @"TW" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c5.jpg", - }, - new User - { - Username = @"peppy", - Id = 2, - Country = new Country { FlagName = @"AU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - }, - new User - { - Username = @"filsdelama", - Id = 2831793, - Country = new Country { FlagName = @"FR" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c7.jpg" - }, - new User - { - Username = @"_index", - Id = 652457, - Country = new Country { FlagName = @"RU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c8.jpg" - }, - }, - }; - Add(s); - - AddStep(@"toggle", s.ToggleVisibility); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Overlays; +using osu.Game.Overlays.Social; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseSocial : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UserPanel), + typeof(SocialPanel), + typeof(FilterControl), + typeof(SocialOverlay), + typeof(SocialGridPanel), + typeof(SocialListPanel) + }; + + public TestCaseSocial() + { + SocialOverlay s = new SocialOverlay + { + Users = new[] + { + new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + }, + new User + { + Username = @"Cookiezi", + Id = 124493, + Country = new Country { FlagName = @"KR" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + }, + new User + { + Username = @"Angelsim", + Id = 1777162, + Country = new Country { FlagName = @"KR" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + new User + { + Username = @"Rafis", + Id = 2558286, + Country = new Country { FlagName = @"PL" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg", + }, + new User + { + Username = @"hvick225", + Id = 50265, + Country = new Country { FlagName = @"TW" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c5.jpg", + }, + new User + { + Username = @"peppy", + Id = 2, + Country = new Country { FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }, + new User + { + Username = @"filsdelama", + Id = 2831793, + Country = new Country { FlagName = @"FR" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c7.jpg" + }, + new User + { + Username = @"_index", + Id = 652457, + Country = new Country { FlagName = @"RU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c8.jpg" + }, + }, + }; + Add(s); + + AddStep(@"toggle", s.ToggleVisibility); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs index 857fd6c902..1eb40a9486 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/TestCaseSongProgress.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.MathUtils; -using osu.Framework.Timing; -using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseSongProgress : OsuTestCase - { - private readonly SongProgress progress; - private readonly SongProgressGraph graph; - - private readonly StopwatchClock clock; - - public TestCaseSongProgress() - { - clock = new StopwatchClock(true); - - Add(progress = new SongProgress - { - RelativeSizeAxes = Axes.X, - AudioClock = new StopwatchClock(true), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }); - - Add(graph = new SongProgressGraph - { - RelativeSizeAxes = Axes.X, - Height = 200, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }); - - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(5); - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(2); - AddRepeatStep("New Values", displayNewValues, 5); - - displayNewValues(); - } - - private void displayNewValues() - { - List objects = new List(); - for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) - objects.Add(new HitObject { StartTime = i }); - - progress.Objects = objects; - graph.Objects = objects; - - progress.AudioClock = clock; - progress.OnSeek = pos => clock.Seek(pos); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseSongProgress : OsuTestCase + { + private readonly SongProgress progress; + private readonly SongProgressGraph graph; + + private readonly StopwatchClock clock; + + public TestCaseSongProgress() + { + clock = new StopwatchClock(true); + + Add(progress = new SongProgress + { + RelativeSizeAxes = Axes.X, + AudioClock = new StopwatchClock(true), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + + Add(graph = new SongProgressGraph + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + + AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); + AddWaitStep(5); + AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); + AddWaitStep(2); + AddRepeatStep("New Values", displayNewValues, 5); + + displayNewValues(); + } + + private void displayNewValues() + { + List objects = new List(); + for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) + objects.Add(new HitObject { StartTime = i }); + + progress.Objects = objects; + graph.Objects = objects; + + progress.AudioClock = clock; + progress.OnSeek = pos => clock.Seek(pos); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs index d34a0e0e5f..e721c5ced0 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs @@ -1,91 +1,91 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Game.Storyboards.Drawables; -using OpenTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseStoryboard : OsuTestCase - { - private readonly Bindable beatmapBacking = new Bindable(); - - private readonly Container storyboardContainer; - private DrawableStoryboard storyboard; - - public TestCaseStoryboard() - { - Clock = new FramedClock(); - - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - storyboardContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }, - }); - Add(new MusicController - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - State = Visibility.Visible, - }); - - AddStep("Restart", restart); - AddToggleStep("Passing", passing => { if (storyboard != null) storyboard.Passing = passing; }); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - beatmapBacking.BindTo(game.Beatmap); - beatmapBacking.ValueChanged += beatmapChanged; - } - - private void beatmapChanged(WorkingBeatmap working) - => loadStoryboard(working); - - private void restart() - { - var track = beatmapBacking.Value.Track; - - track.Reset(); - loadStoryboard(beatmapBacking.Value); - track.Start(); - } - - private void loadStoryboard(WorkingBeatmap working) - { - if (storyboard != null) - storyboardContainer.Remove(storyboard); - - var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; - storyboardContainer.Clock = decoupledClock; - - storyboard = working.Storyboard.CreateDrawable(beatmapBacking); - storyboard.Passing = false; - - storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(working.Track); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Storyboards.Drawables; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseStoryboard : OsuTestCase + { + private readonly Bindable beatmapBacking = new Bindable(); + + private readonly Container storyboardContainer; + private DrawableStoryboard storyboard; + + public TestCaseStoryboard() + { + Clock = new FramedClock(); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + storyboardContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + }, + }); + Add(new MusicController + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + State = Visibility.Visible, + }); + + AddStep("Restart", restart); + AddToggleStep("Passing", passing => { if (storyboard != null) storyboard.Passing = passing; }); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + beatmapBacking.BindTo(game.Beatmap); + beatmapBacking.ValueChanged += beatmapChanged; + } + + private void beatmapChanged(WorkingBeatmap working) + => loadStoryboard(working); + + private void restart() + { + var track = beatmapBacking.Value.Track; + + track.Reset(); + loadStoryboard(beatmapBacking.Value); + track.Start(); + } + + private void loadStoryboard(WorkingBeatmap working) + { + if (storyboard != null) + storyboardContainer.Remove(storyboard); + + var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; + storyboardContainer.Clock = decoupledClock; + + storyboard = working.Storyboard.CreateDrawable(beatmapBacking); + storyboard.Passing = false; + + storyboardContainer.Add(storyboard); + decoupledClock.ChangeSource(working.Track); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseTabControl.cs b/osu.Game.Tests/Visual/TestCaseTabControl.cs index 5fc774ff42..63708c5662 100644 --- a/osu.Game.Tests/Visual/TestCaseTabControl.cs +++ b/osu.Game.Tests/Visual/TestCaseTabControl.cs @@ -1,42 +1,42 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Select.Filter; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - [Description("SongSelect filter control")] - public class TestCaseTabControl : OsuTestCase - { - public TestCaseTabControl() - { - OsuSpriteText text; - OsuTabControl filter; - Add(filter = new OsuTabControl - { - Margin = new MarginPadding(4), - Size = new Vector2(229, 24), - AutoSort = true - }); - Add(text = new OsuSpriteText - { - Text = "None", - Margin = new MarginPadding(4), - Position = new Vector2(275, 5) - }); - - filter.PinItem(GroupMode.All); - filter.PinItem(GroupMode.RecentlyPlayed); - - filter.Current.ValueChanged += newFilter => - { - text.Text = "Currently Selected: " + newFilter.ToString(); - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Select.Filter; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + [Description("SongSelect filter control")] + public class TestCaseTabControl : OsuTestCase + { + public TestCaseTabControl() + { + OsuSpriteText text; + OsuTabControl filter; + Add(filter = new OsuTabControl + { + Margin = new MarginPadding(4), + Size = new Vector2(229, 24), + AutoSort = true + }); + Add(text = new OsuSpriteText + { + Text = "None", + Margin = new MarginPadding(4), + Position = new Vector2(275, 5) + }); + + filter.PinItem(GroupMode.All); + filter.PinItem(GroupMode.RecentlyPlayed); + + filter.Current.ValueChanged += newFilter => + { + text.Text = "Currently Selected: " + newFilter.ToString(); + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs index bf7609ff8d..75b149af29 100644 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs @@ -1,55 +1,55 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseTextAwesome : OsuTestCase - { - public TestCaseTextAwesome() - { - FillFlowContainer flow; - - Add(new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = flow = new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - }, - }); - - foreach (FontAwesome fa in Enum.GetValues(typeof(FontAwesome))) - flow.Add(new Icon(fa)); - } - - private class Icon : Container, IHasTooltip - { - public string TooltipText { get; } - - public Icon(FontAwesome fa) - { - TooltipText = fa.ToString(); - - AutoSizeAxes = Axes.Both; - Child = new SpriteIcon - { - Icon = fa, - Size = new Vector2(60), - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseTextAwesome : OsuTestCase + { + public TestCaseTextAwesome() + { + FillFlowContainer flow; + + Add(new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = flow = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + }, + }); + + foreach (FontAwesome fa in Enum.GetValues(typeof(FontAwesome))) + flow.Add(new Icon(fa)); + } + + private class Icon : Container, IHasTooltip + { + public string TooltipText { get; } + + public Icon(FontAwesome fa) + { + TooltipText = fa.ToString(); + + AutoSizeAxes = Axes.Both; + Child = new SpriteIcon + { + Icon = fa, + Size = new Vector2(60), + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/TestCaseToolbar.cs index 94e45fe0c2..fd218af054 100644 --- a/osu.Game.Tests/Visual/TestCaseToolbar.cs +++ b/osu.Game.Tests/Visual/TestCaseToolbar.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays.Toolbar; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseToolbar : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(ToolbarButton), - typeof(ToolbarModeSelector), - typeof(ToolbarModeButton), - typeof(ToolbarNotificationButton), - }; - - public TestCaseToolbar() - { - var toolbar = new Toolbar { State = Visibility.Visible }; - - Add(toolbar); - - var notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); - - void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); - - setNotifications(1); - setNotifications(2); - setNotifications(3); - setNotifications(0); - setNotifications(144); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Toolbar; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseToolbar : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ToolbarButton), + typeof(ToolbarModeSelector), + typeof(ToolbarModeButton), + typeof(ToolbarNotificationButton), + }; + + public TestCaseToolbar() + { + var toolbar = new Toolbar { State = Visibility.Visible }; + + Add(toolbar); + + var notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); + + void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); + + setNotifications(1); + setNotifications(2); + setNotifications(3); + setNotifications(0); + setNotifications(144); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs b/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs index cbf3dd150a..8079566947 100644 --- a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs +++ b/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Tests.Visual -{ - [Description("mostly back button")] - public class TestCaseTwoLayerButton : OsuTestCase - { - public TestCaseTwoLayerButton() - { - Add(new BackButton()); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual +{ + [Description("mostly back button")] + public class TestCaseTwoLayerButton : OsuTestCase + { + public TestCaseTwoLayerButton() + { + Add(new BackButton()); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/TestCaseUserPanel.cs index ed377dc160..8e2e708cc7 100644 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/TestCaseUserPanel.cs @@ -1,55 +1,55 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Users; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseUserPanel : OsuTestCase - { - public TestCaseUserPanel() - { - UserPanel flyte; - UserPanel peppy; - Add(new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10f), - Children = new[] - { - flyte = new UserPanel(new User - { - Username = @"flyte", - Id = 3103765, - Country = new Country { FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - }) { Width = 300 }, - peppy = new UserPanel(new User - { - Username = @"peppy", - Id = 2, - Country = new Country { FlagName = @"AU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - IsSupporter = true, - }) { Width = 300 }, - }, - }); - - flyte.Status.Value = new UserStatusOnline(); - peppy.Status.Value = new UserStatusSoloGame(); - - AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); }); - AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); }); - AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); }); - AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); }); - AddStep(@"null status", () => { flyte.Status.Value = null; }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Users; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseUserPanel : OsuTestCase + { + public TestCaseUserPanel() + { + UserPanel flyte; + UserPanel peppy; + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f), + Children = new[] + { + flyte = new UserPanel(new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }) { Width = 300 }, + peppy = new UserPanel(new User + { + Username = @"peppy", + Id = 2, + Country = new Country { FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + IsSupporter = true, + }) { Width = 300 }, + }, + }); + + flyte.Status.Value = new UserStatusOnline(); + peppy.Status.Value = new UserStatusSoloGame(); + + AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); }); + AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); }); + AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); }); + AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); }); + AddStep(@"null status", () => { flyte.Status.Value = null; }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 1fc6c6f224..b060b9f2f8 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -1,103 +1,103 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; -using osu.Game.Overlays.Profile; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseUserProfile : OsuTestCase - { - private readonly TestUserProfileOverlay profile; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(ProfileHeader), - typeof(UserProfileOverlay), - typeof(RankGraph), - typeof(LineGraph), - }; - - public TestCaseUserProfile() - { - Add(profile = new TestUserProfileOverlay()); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - AddStep("Show offline dummy", () => profile.ShowUser(new User - { - Username = @"Somebody", - Id = 1, - Country = new Country { FullName = @"Alien" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - JoinDate = DateTimeOffset.Now.AddDays(-1), - LastVisit = DateTimeOffset.Now, - Age = 1, - ProfileOrder = new[] { "me" }, - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, - PP = 4567.89m, - }, - RankHistory = new User.RankHistoryData - { - Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() - } - }, false)); - - checkSupporterTag(false); - - AddStep("Show null dummy", () => profile.ShowUser(new User - { - Username = @"Null", - Id = 1, - }, false)); - - AddStep("Show ppy", () => profile.ShowUser(new User - { - Username = @"peppy", - Id = 2, - Country = new Country { FullName = @"Australia", FlagName = @"AU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" - })); - - checkSupporterTag(true); - - AddStep("Show flyte", () => profile.ShowUser(new User - { - Username = @"flyte", - Id = 3103765, - Country = new Country { FullName = @"Japan", FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - })); - - AddStep("Hide", profile.Hide); - AddStep("Show without reload", profile.Show); - } - - private void checkSupporterTag(bool isSupporter) - { - AddUntilStep(() => profile.Header.User != null, "wait for load"); - if (isSupporter) - AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); - else - AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); - } - - private class TestUserProfileOverlay : UserProfileOverlay - { - public new ProfileHeader Header => base.Header; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseUserProfile : OsuTestCase + { + private readonly TestUserProfileOverlay profile; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(UserProfileOverlay), + typeof(RankGraph), + typeof(LineGraph), + }; + + public TestCaseUserProfile() + { + Add(profile = new TestUserProfileOverlay()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep("Show offline dummy", () => profile.ShowUser(new User + { + Username = @"Somebody", + Id = 1, + Country = new Country { FullName = @"Alien" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + JoinDate = DateTimeOffset.Now.AddDays(-1), + LastVisit = DateTimeOffset.Now, + Age = 1, + ProfileOrder = new[] { "me" }, + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, + PP = 4567.89m, + }, + RankHistory = new User.RankHistoryData + { + Mode = @"osu", + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + } + }, false)); + + checkSupporterTag(false); + + AddStep("Show null dummy", () => profile.ShowUser(new User + { + Username = @"Null", + Id = 1, + }, false)); + + AddStep("Show ppy", () => profile.ShowUser(new User + { + Username = @"peppy", + Id = 2, + Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" + })); + + checkSupporterTag(true); + + AddStep("Show flyte", () => profile.ShowUser(new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + })); + + AddStep("Hide", profile.Hide); + AddStep("Show without reload", profile.Show); + } + + private void checkSupporterTag(bool isSupporter) + { + AddUntilStep(() => profile.Header.User != null, "wait for load"); + if (isSupporter) + AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); + else + AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); + } + + private class TestUserProfileOverlay : UserProfileOverlay + { + public new ProfileHeader Header => base.Header; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs b/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs index 1f7a7e7165..f625bc0150 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs @@ -1,161 +1,161 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Profile.Sections; -using osu.Game.Overlays.Profile.Sections.Recent; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseUserProfileRecentSection : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(RecentSection), - typeof(DrawableRecentActivity), - typeof(PaginatedRecentActivityContainer), - typeof(MedalIcon) - }; - - public TestCaseUserProfileRecentSection() - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, - new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - ChildrenEnumerable = createDummyActivities().Select(a => new DrawableRecentActivity(a)) - }, - } - }; - } - - private IEnumerable createDummyActivities() - { - var dummyBeatmap = new RecentActivity.RecentActivityBeatmap - { - Title = @"Dummy beatmap", - Url = "/b/1337", - }; - - var dummyUser = new RecentActivity.RecentActivityUser - { - Username = "DummyReborn", - Url = "/u/666", - PreviousUsername = "Dummy", - }; - - return new[] - { - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.Achievement, - Achievement = new RecentActivity.RecentActivityAchievement - { - Name = @"Feelin' It", - Slug = @"all-secret-feelinit", - }, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.BeatmapPlaycount, - Count = 1337, - Beatmap = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.BeatmapsetApprove, - Approval = BeatmapApproval.Qualified, - Beatmapset = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.BeatmapsetDelete, - Beatmapset = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.BeatmapsetRevive, - Beatmapset = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.BeatmapsetRevive, - Beatmapset = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.BeatmapsetUpdate, - Beatmapset = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.BeatmapsetUpload, - Beatmapset = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.Rank, - Rank = 1, - Mode = "osu!", - Beatmap = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.RankLost, - Mode = "osu!", - Beatmap = dummyBeatmap, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.UsernameChange, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.UserSupportAgain, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.UserSupportFirst, - }, - new RecentActivity - { - User = dummyUser, - Type = RecentActivityType.UserSupportGift, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Overlays.Profile.Sections.Recent; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseUserProfileRecentSection : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RecentSection), + typeof(DrawableRecentActivity), + typeof(PaginatedRecentActivityContainer), + typeof(MedalIcon) + }; + + public TestCaseUserProfileRecentSection() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + ChildrenEnumerable = createDummyActivities().Select(a => new DrawableRecentActivity(a)) + }, + } + }; + } + + private IEnumerable createDummyActivities() + { + var dummyBeatmap = new RecentActivity.RecentActivityBeatmap + { + Title = @"Dummy beatmap", + Url = "/b/1337", + }; + + var dummyUser = new RecentActivity.RecentActivityUser + { + Username = "DummyReborn", + Url = "/u/666", + PreviousUsername = "Dummy", + }; + + return new[] + { + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.Achievement, + Achievement = new RecentActivity.RecentActivityAchievement + { + Name = @"Feelin' It", + Slug = @"all-secret-feelinit", + }, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.BeatmapPlaycount, + Count = 1337, + Beatmap = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.BeatmapsetApprove, + Approval = BeatmapApproval.Qualified, + Beatmapset = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.BeatmapsetDelete, + Beatmapset = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.BeatmapsetRevive, + Beatmapset = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.BeatmapsetRevive, + Beatmapset = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.BeatmapsetUpdate, + Beatmapset = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.BeatmapsetUpload, + Beatmapset = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.Rank, + Rank = 1, + Mode = "osu!", + Beatmap = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.RankLost, + Mode = "osu!", + Beatmap = dummyBeatmap, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.UsernameChange, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.UserSupportAgain, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.UserSupportFirst, + }, + new RecentActivity + { + User = dummyUser, + Type = RecentActivityType.UserSupportGift, + }, + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/TestCaseUserRanks.cs index effc98c381..8329ad705a 100644 --- a/osu.Game.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game.Tests/Visual/TestCaseUserRanks.cs @@ -1,47 +1,47 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Overlays.Profile.Sections; -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Users; -using System; -using System.Collections.Generic; -using NUnit.Framework; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseUserRanks : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; - - public TestCaseUserRanks() - { - RanksSection ranks; - - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, - new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = ranks = new RanksSection(), - }, - } - }); - - AddStep("Show cookiezi", () => ranks.User.Value = new User { Id = 124493 }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Users; +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseUserRanks : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; + + public TestCaseUserRanks() + { + RanksSection ranks; + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = ranks = new RanksSection(), + }, + } + }); + + AddStep("Show cookiezi", () => ranks.User.Value = new User { Id = 124493 }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs index cfbf7fdb4d..449f48b7d7 100644 --- a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs +++ b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs @@ -1,30 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Game.Overlays.Volume; -using OpenTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - public class TestCaseVolumePieces : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(VolumeMeter), typeof(MuteButton) }; - - protected override void LoadComplete() - { - VolumeMeter meter; - MuteButton mute; - Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue)); - Add(mute = new MuteButton - { - Margin = new MarginPadding { Top = 200 } - }); - - AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1); - AddToggleStep("mute", b => mute.Current.Value = b); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Overlays.Volume; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseVolumePieces : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(VolumeMeter), typeof(MuteButton) }; + + protected override void LoadComplete() + { + VolumeMeter meter; + MuteButton mute; + Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue)); + Add(mute = new MuteButton + { + Margin = new MarginPadding { Top = 200 } + }); + + AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1); + AddToggleStep("mute", b => mute.Current.Value = b); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs index 7d4a9d663b..776adab0d1 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs @@ -1,90 +1,90 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseWaveform : OsuTestCase - { - private readonly Bindable beatmapBacking = new Bindable(); - - public TestCaseWaveform() - { - FillFlowContainer flow; - Child = flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = new Drawable[] - { - new MusicController - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 100, - State = Visibility.Visible - }, - } - }; - - for (int i = 1; i <= 16; i *= 2) - { - var newDisplay = new BeatmapWaveformGraph - { - RelativeSizeAxes = Axes.Both, - Resolution = 1f / i - }; - - newDisplay.Beatmap.BindTo(beatmapBacking); - - flow.Add(new Container - { - RelativeSizeAxes = Axes.X, - Height = 100, - Children = new Drawable[] - { - newDisplay, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.75f - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = $"Resolution: {1f / i:0.00}" - } - } - } - } - }); - } - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Screens.Compose.Timeline; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseWaveform : OsuTestCase + { + private readonly Bindable beatmapBacking = new Bindable(); + + public TestCaseWaveform() + { + FillFlowContainer flow; + Child = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new MusicController + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 100, + State = Visibility.Visible + }, + } + }; + + for (int i = 1; i <= 16; i *= 2) + { + var newDisplay = new BeatmapWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Resolution = 1f / i + }; + + newDisplay.Beatmap.BindTo(beatmapBacking); + + flow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Children = new Drawable[] + { + newDisplay, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.75f + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = $"Resolution: {1f / i:0.00}" + } + } + } + } + }); + } + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap); + } +} diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 2014db6c61..f635b74030 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Audio -{ - [Serializable] - public class SampleInfo - { - public const string HIT_WHISTLE = @"hitwhistle"; - public const string HIT_FINISH = @"hitfinish"; - public const string HIT_NORMAL = @"hitnormal"; - public const string HIT_CLAP = @"hitclap"; - - /// - /// An optional ruleset namespace. - /// - public string Namespace; - - /// - /// The bank to load the sample from. - /// - public string Bank; - - /// - /// The name of the sample to load. - /// - public string Name; - - /// - /// The sample volume. - /// - public int Volume; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Audio +{ + [Serializable] + public class SampleInfo + { + public const string HIT_WHISTLE = @"hitwhistle"; + public const string HIT_FINISH = @"hitfinish"; + public const string HIT_NORMAL = @"hitnormal"; + public const string HIT_CLAP = @"hitclap"; + + /// + /// An optional ruleset namespace. + /// + public string Namespace; + + /// + /// The bank to load the sample from. + /// + public string Bank; + + /// + /// The name of the sample to load. + /// + public string Name; + + /// + /// The sample volume. + /// + public int Volume; + } +} diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 60cf93fd91..12a017f68c 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -1,88 +1,88 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Objects; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.IO.Serialization; -using Newtonsoft.Json; -using osu.Game.IO.Serialization.Converters; - -namespace osu.Game.Beatmaps -{ - /// - /// A Beatmap containing converted HitObjects. - /// - public class Beatmap : IJsonSerializable - where T : HitObject - { - public BeatmapInfo BeatmapInfo = new BeatmapInfo(); - public ControlPointInfo ControlPointInfo = new ControlPointInfo(); - public List Breaks = new List(); - - [JsonIgnore] - public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; - - /// - /// The HitObjects this Beatmap contains. - /// - [JsonConverter(typeof(TypedListConverter))] - public List HitObjects = new List(); - - /// - /// Total amount of break time in the beatmap. - /// - [JsonIgnore] - public double TotalBreakTime => Breaks.Sum(b => b.Duration); - - /// - /// Constructs a new beatmap. - /// - /// The original beatmap to use the parameters of. - public Beatmap(Beatmap original = null) - { - BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo; - ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; - Breaks = original?.Breaks ?? Breaks; - HitObjects = original?.HitObjects ?? HitObjects; - - if (original == null && Metadata == null) - { - // we may have no metadata in cases we weren't sourced from the database. - // let's fill it (and other related fields) so we don't need to null-check it in future usages. - BeatmapInfo = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Artist = @"Unknown", - Title = @"Unknown", - AuthorString = @"Unknown Creator", - }, - Version = @"Normal", - BaseDifficulty = new BeatmapDifficulty() - }; - } - } - } - - /// - /// A Beatmap containing un-converted HitObjects. - /// - public class Beatmap : Beatmap - { - /// - /// Constructs a new beatmap. - /// - /// The original beatmap to use the parameters of. - public Beatmap(Beatmap original) - : base(original) - { - } - - public Beatmap() - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.IO.Serialization; +using Newtonsoft.Json; +using osu.Game.IO.Serialization.Converters; + +namespace osu.Game.Beatmaps +{ + /// + /// A Beatmap containing converted HitObjects. + /// + public class Beatmap : IJsonSerializable + where T : HitObject + { + public BeatmapInfo BeatmapInfo = new BeatmapInfo(); + public ControlPointInfo ControlPointInfo = new ControlPointInfo(); + public List Breaks = new List(); + + [JsonIgnore] + public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; + + /// + /// The HitObjects this Beatmap contains. + /// + [JsonConverter(typeof(TypedListConverter))] + public List HitObjects = new List(); + + /// + /// Total amount of break time in the beatmap. + /// + [JsonIgnore] + public double TotalBreakTime => Breaks.Sum(b => b.Duration); + + /// + /// Constructs a new beatmap. + /// + /// The original beatmap to use the parameters of. + public Beatmap(Beatmap original = null) + { + BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo; + ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; + Breaks = original?.Breaks ?? Breaks; + HitObjects = original?.HitObjects ?? HitObjects; + + if (original == null && Metadata == null) + { + // we may have no metadata in cases we weren't sourced from the database. + // let's fill it (and other related fields) so we don't need to null-check it in future usages. + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = @"Unknown", + Title = @"Unknown", + AuthorString = @"Unknown Creator", + }, + Version = @"Normal", + BaseDifficulty = new BeatmapDifficulty() + }; + } + } + } + + /// + /// A Beatmap containing un-converted HitObjects. + /// + public class Beatmap : Beatmap + { + /// + /// Constructs a new beatmap. + /// + /// The original beatmap to use the parameters of. + public Beatmap(Beatmap original) + : base(original) + { + } + + public Beatmap() + { + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 2003b845d9..153cace187 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -1,112 +1,112 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Beatmaps -{ - /// - /// Converts a Beatmap for another mode. - /// - /// The type of HitObject stored in the Beatmap. - public abstract class BeatmapConverter : IBeatmapConverter - where T : HitObject - { - private event Action> ObjectConverted; - event Action> IBeatmapConverter.ObjectConverted - { - add => ObjectConverted += value; - remove => ObjectConverted -= value; - } - - /// - /// Checks if a Beatmap can be converted using this Beatmap Converter. - /// - /// The Beatmap to check. - /// Whether the Beatmap can be converted using this Beatmap Converter. - public bool CanConvert(Beatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType)); - - /// - /// Converts a Beatmap using this Beatmap Converter. - /// - /// The un-converted Beatmap. - /// The converted Beatmap. - public Beatmap Convert(Beatmap original) - { - // We always operate on a clone of the original beatmap, to not modify it game-wide - return ConvertBeatmap(new Beatmap(original)); - } - - void IBeatmapConverter.Convert(Beatmap original) => Convert(original); - - /// - /// Performs the conversion of a Beatmap using this Beatmap Converter. - /// - /// The un-converted Beatmap. - /// The converted Beatmap. - protected virtual Beatmap ConvertBeatmap(Beatmap original) - { - var beatmap = CreateBeatmap(); - - // todo: this *must* share logic (or directly use) Beatmap's constructor. - // right now this isn't easily possible due to generic entanglement. - beatmap.BeatmapInfo = original.BeatmapInfo; - beatmap.ControlPointInfo = original.ControlPointInfo; - beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList(); - beatmap.Breaks = original.Breaks; - - return beatmap; - } - - /// - /// Converts a hit object. - /// - /// The hit object to convert. - /// The un-converted Beatmap. - /// The converted hit object. - private IEnumerable convert(HitObject original, Beatmap beatmap) - { - // Check if the hitobject is already the converted type - T tObject = original as T; - if (tObject != null) - { - yield return tObject; - yield break; - } - - var converted = ConvertHitObject(original, beatmap).ToList(); - ObjectConverted?.Invoke(original, converted); - - // Convert the hit object - foreach (var obj in converted) - { - if (obj == null) - continue; - - yield return obj; - } - } - - /// - /// The types of HitObjects that can be converted to be used for this Beatmap. - /// - protected abstract IEnumerable ValidConversionTypes { get; } - - /// - /// Creates the that will be returned by this . - /// - protected virtual Beatmap CreateBeatmap() => new Beatmap(); - - /// - /// Performs the conversion of a hit object. - /// This method is generally executed sequentially for all objects in a beatmap. - /// - /// The hit object to convert. - /// The un-converted Beatmap. - /// The converted hit object. - protected abstract IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Beatmaps +{ + /// + /// Converts a Beatmap for another mode. + /// + /// The type of HitObject stored in the Beatmap. + public abstract class BeatmapConverter : IBeatmapConverter + where T : HitObject + { + private event Action> ObjectConverted; + event Action> IBeatmapConverter.ObjectConverted + { + add => ObjectConverted += value; + remove => ObjectConverted -= value; + } + + /// + /// Checks if a Beatmap can be converted using this Beatmap Converter. + /// + /// The Beatmap to check. + /// Whether the Beatmap can be converted using this Beatmap Converter. + public bool CanConvert(Beatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType)); + + /// + /// Converts a Beatmap using this Beatmap Converter. + /// + /// The un-converted Beatmap. + /// The converted Beatmap. + public Beatmap Convert(Beatmap original) + { + // We always operate on a clone of the original beatmap, to not modify it game-wide + return ConvertBeatmap(new Beatmap(original)); + } + + void IBeatmapConverter.Convert(Beatmap original) => Convert(original); + + /// + /// Performs the conversion of a Beatmap using this Beatmap Converter. + /// + /// The un-converted Beatmap. + /// The converted Beatmap. + protected virtual Beatmap ConvertBeatmap(Beatmap original) + { + var beatmap = CreateBeatmap(); + + // todo: this *must* share logic (or directly use) Beatmap's constructor. + // right now this isn't easily possible due to generic entanglement. + beatmap.BeatmapInfo = original.BeatmapInfo; + beatmap.ControlPointInfo = original.ControlPointInfo; + beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList(); + beatmap.Breaks = original.Breaks; + + return beatmap; + } + + /// + /// Converts a hit object. + /// + /// The hit object to convert. + /// The un-converted Beatmap. + /// The converted hit object. + private IEnumerable convert(HitObject original, Beatmap beatmap) + { + // Check if the hitobject is already the converted type + T tObject = original as T; + if (tObject != null) + { + yield return tObject; + yield break; + } + + var converted = ConvertHitObject(original, beatmap).ToList(); + ObjectConverted?.Invoke(original, converted); + + // Convert the hit object + foreach (var obj in converted) + { + if (obj == null) + continue; + + yield return obj; + } + } + + /// + /// The types of HitObjects that can be converted to be used for this Beatmap. + /// + protected abstract IEnumerable ValidConversionTypes { get; } + + /// + /// Creates the that will be returned by this . + /// + protected virtual Beatmap CreateBeatmap() => new Beatmap(); + + /// + /// Performs the conversion of a hit object. + /// This method is generally executed sequentially for all objects in a beatmap. + /// + /// The hit object to convert. + /// The un-converted Beatmap. + /// The converted hit object. + protected abstract IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap); + } +} diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 38b84b4b03..855e8fe881 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; - -namespace osu.Game.Beatmaps -{ - public class BeatmapDifficulty - { - /// - /// The default value used for all difficulty settings except and . - /// - public const float DEFAULT_DIFFICULTY = 5; - - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] - public int ID { get; set; } - - public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; - public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; - public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; - - private float? approachRate; - - public float ApproachRate - { - get => approachRate ?? OverallDifficulty; - set => approachRate = value; - } - - public double SliderMultiplier { get; set; } = 1; - public double SliderTickRate { get; set; } = 1; - - /// - /// Maps a difficulty value [0, 10] to a two-piece linear range of values. - /// - /// The difficulty value to be mapped. - /// Minimum of the resulting range which will be achieved by a difficulty value of 0. - /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. - /// Maximum of the resulting range which will be achieved by a difficulty value of 10. - /// Value to which the difficulty value maps in the specified range. - public static double DifficultyRange(double difficulty, double min, double mid, double max) - { - if (difficulty > 5) - return mid + (max - mid) * (difficulty - 5) / 5; - if (difficulty < 5) - return mid - (mid - min) * (5 - difficulty) / 5; - return mid; - } - - /// - /// Maps a difficulty value [0, 10] to a two-piece linear range of values. - /// - /// The difficulty value to be mapped. - /// The values that define the two linear ranges. - /// Minimum of the resulting range which will be achieved by a difficulty value of 0. - /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. - /// Maximum of the resulting range which will be achieved by a difficulty value of 10. - /// Value to which the difficulty value maps in the specified range. - public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) - => DifficultyRange(difficulty, range.od0, range.od5, range.od10); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace osu.Game.Beatmaps +{ + public class BeatmapDifficulty + { + /// + /// The default value used for all difficulty settings except and . + /// + public const float DEFAULT_DIFFICULTY = 5; + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] + public int ID { get; set; } + + public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; + public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; + public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; + + private float? approachRate; + + public float ApproachRate + { + get => approachRate ?? OverallDifficulty; + set => approachRate = value; + } + + public double SliderMultiplier { get; set; } = 1; + public double SliderTickRate { get; set; } = 1; + + /// + /// Maps a difficulty value [0, 10] to a two-piece linear range of values. + /// + /// The difficulty value to be mapped. + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// Value to which the difficulty value maps in the specified range. + public static double DifficultyRange(double difficulty, double min, double mid, double max) + { + if (difficulty > 5) + return mid + (max - mid) * (difficulty - 5) / 5; + if (difficulty < 5) + return mid - (mid - min) * (5 - difficulty) / 5; + return mid; + } + + /// + /// Maps a difficulty value [0, 10] to a two-piece linear range of values. + /// + /// The difficulty value to be mapped. + /// The values that define the two linear ranges. + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// Value to which the difficulty value maps in the specified range. + public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) + => DifficultyRange(difficulty, range.od0, range.od5, range.od10); + } +} diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index b151570806..a1b97afc6c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -1,147 +1,147 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using Newtonsoft.Json; -using osu.Game.Database; -using osu.Game.IO.Serialization; -using osu.Game.Rulesets; - -namespace osu.Game.Beatmaps -{ - [Serializable] - public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] - public int ID { get; set; } - - //TODO: should be in database - public int BeatmapVersion; - - private int? onlineBeatmapID; - private int? onlineBeatmapSetID; - - [JsonProperty("id")] - public int? OnlineBeatmapID - { - get { return onlineBeatmapID; } - set { onlineBeatmapID = value > 0 ? value : null; } - } - - [JsonProperty("beatmapset_id")] - [NotMapped] - public int? OnlineBeatmapSetID - { - get { return onlineBeatmapSetID; } - set { onlineBeatmapSetID = value > 0 ? value : null; } - } - - [JsonIgnore] - public int BeatmapSetInfoID { get; set; } - - [Required] - [JsonIgnore] - public BeatmapSetInfo BeatmapSet { get; set; } - - public BeatmapMetadata Metadata { get; set; } - - [JsonIgnore] - public int BaseDifficultyID { get; set; } - - public BeatmapDifficulty BaseDifficulty { get; set; } - - [NotMapped] - public BeatmapMetrics Metrics { get; set; } - - [NotMapped] - public BeatmapOnlineInfo OnlineInfo { get; set; } - - public string Path { get; set; } - - [JsonProperty("file_sha2")] - public string Hash { get; set; } - - [JsonIgnore] - public bool Hidden { get; set; } - - /// - /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). - /// - [JsonProperty("file_md5")] - public string MD5Hash { get; set; } - - // General - public int AudioLeadIn { get; set; } - public bool Countdown { get; set; } - public float StackLeniency { get; set; } - public bool SpecialStyle { get; set; } - - public int RulesetID { get; set; } - - public RulesetInfo Ruleset { get; set; } - - public bool LetterboxInBreaks { get; set; } - public bool WidescreenStoryboard { get; set; } - - // Editor - // This bookmarks stuff is necessary because DB doesn't know how to store int[] - [JsonIgnore] - public string StoredBookmarks - { - get { return string.Join(",", Bookmarks); } - set - { - if (string.IsNullOrEmpty(value)) - { - Bookmarks = new int[0]; - return; - } - - Bookmarks = value.Split(',').Select(v => - { - int val; - bool result = int.TryParse(v, out val); - return new { result, val }; - }).Where(p => p.result).Select(p => p.val).ToArray(); - } - } - - [NotMapped] - public int[] Bookmarks { get; set; } = new int[0]; - - public double DistanceSpacing { get; set; } - public int BeatDivisor { get; set; } - public int GridSize { get; set; } - public double TimelineZoom { get; set; } - - // Metadata - public string Version { get; set; } - - [JsonProperty("difficulty_rating")] - public double StarDifficulty { get; set; } - - public override string ToString() => $"{Metadata} [{Version}]"; - - public bool Equals(BeatmapInfo other) - { - if (ID == 0 || other?.ID == 0) - // one of the two BeatmapInfos we are comparing isn't sourced from a database. - // fall back to reference equality. - return ReferenceEquals(this, other); - - return ID == other?.ID; - } - - public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; - - public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Database; +using osu.Game.IO.Serialization; +using osu.Game.Rulesets; + +namespace osu.Game.Beatmaps +{ + [Serializable] + public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] + public int ID { get; set; } + + //TODO: should be in database + public int BeatmapVersion; + + private int? onlineBeatmapID; + private int? onlineBeatmapSetID; + + [JsonProperty("id")] + public int? OnlineBeatmapID + { + get { return onlineBeatmapID; } + set { onlineBeatmapID = value > 0 ? value : null; } + } + + [JsonProperty("beatmapset_id")] + [NotMapped] + public int? OnlineBeatmapSetID + { + get { return onlineBeatmapSetID; } + set { onlineBeatmapSetID = value > 0 ? value : null; } + } + + [JsonIgnore] + public int BeatmapSetInfoID { get; set; } + + [Required] + [JsonIgnore] + public BeatmapSetInfo BeatmapSet { get; set; } + + public BeatmapMetadata Metadata { get; set; } + + [JsonIgnore] + public int BaseDifficultyID { get; set; } + + public BeatmapDifficulty BaseDifficulty { get; set; } + + [NotMapped] + public BeatmapMetrics Metrics { get; set; } + + [NotMapped] + public BeatmapOnlineInfo OnlineInfo { get; set; } + + public string Path { get; set; } + + [JsonProperty("file_sha2")] + public string Hash { get; set; } + + [JsonIgnore] + public bool Hidden { get; set; } + + /// + /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). + /// + [JsonProperty("file_md5")] + public string MD5Hash { get; set; } + + // General + public int AudioLeadIn { get; set; } + public bool Countdown { get; set; } + public float StackLeniency { get; set; } + public bool SpecialStyle { get; set; } + + public int RulesetID { get; set; } + + public RulesetInfo Ruleset { get; set; } + + public bool LetterboxInBreaks { get; set; } + public bool WidescreenStoryboard { get; set; } + + // Editor + // This bookmarks stuff is necessary because DB doesn't know how to store int[] + [JsonIgnore] + public string StoredBookmarks + { + get { return string.Join(",", Bookmarks); } + set + { + if (string.IsNullOrEmpty(value)) + { + Bookmarks = new int[0]; + return; + } + + Bookmarks = value.Split(',').Select(v => + { + int val; + bool result = int.TryParse(v, out val); + return new { result, val }; + }).Where(p => p.result).Select(p => p.val).ToArray(); + } + } + + [NotMapped] + public int[] Bookmarks { get; set; } = new int[0]; + + public double DistanceSpacing { get; set; } + public int BeatDivisor { get; set; } + public int GridSize { get; set; } + public double TimelineZoom { get; set; } + + // Metadata + public string Version { get; set; } + + [JsonProperty("difficulty_rating")] + public double StarDifficulty { get; set; } + + public override string ToString() => $"{Metadata} [{Version}]"; + + public bool Equals(BeatmapInfo other) + { + if (ID == 0 || other?.ID == 0) + // one of the two BeatmapInfos we are comparing isn't sourced from a database. + // fall back to reference equality. + return ReferenceEquals(this, other); + + return ID == other?.ID; + } + + public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && + BeatmapSet.Hash == other.BeatmapSet.Hash && + (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; + + public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && + BeatmapSet.Hash == other.BeatmapSet.Hash && + (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; + } +} diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1113e38d7a..645e52a6c6 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -1,355 +1,355 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Audio; -using osu.Framework.Extensions; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Beatmaps.Formats; -using osu.Game.Database; -using osu.Game.Graphics; -using osu.Game.IO.Archives; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets; - -namespace osu.Game.Beatmaps -{ - /// - /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. - /// - public partial class BeatmapManager : ArchiveModelManager - { - /// - /// Fired when a single difficulty has been hidden. - /// - public event Action BeatmapHidden; - - /// - /// Fired when a single difficulty has been restored. - /// - public event Action BeatmapRestored; - - /// - /// Fired when a beatmap download begins. - /// - public event Action BeatmapDownloadBegan; - - /// - /// A default representation of a WorkingBeatmap to use when no beatmap is available. - /// - public WorkingBeatmap DefaultBeatmap { private get; set; } - - public override string[] HandledExtensions => new[] { ".osz" }; - - private readonly RulesetStore rulesets; - - private readonly BeatmapStore beatmaps; - - private readonly APIAccess api; - - private readonly AudioManager audioManager; - - private readonly List currentDownloads = new List(); - - /// - /// Set a storage with access to an osu-stable install for import purposes. - /// - public Func GetStableStorage { private get; set; } - - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null) - : base(storage, contextFactory, new BeatmapStore(contextFactory), importHost) - { - beatmaps = (BeatmapStore)ModelStore; - beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); - beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); - - this.rulesets = rulesets; - this.api = api; - this.audioManager = audioManager; - } - - protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) - { - model.Beatmaps = createBeatmapDifficulties(archive); - - // remove metadata from difficulties where it matches the set - foreach (BeatmapInfo b in model.Beatmaps) - if (model.Metadata.Equals(b.Metadata)) - b.Metadata = null; - } - - protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) - { - // check if this beatmap has already been imported and exit early if so - var existingHashMatch = beatmaps.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); - if (existingHashMatch != null) - { - Undelete(existingHashMatch); - return existingHashMatch; - } - - // check if a set already exists with the same online id - if (model.OnlineBeatmapSetID != null) - { - var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); - if (existingOnlineId != null) - { - Delete(existingOnlineId); - beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - } - } - - return null; - } - - /// - /// Downloads a beatmap. - /// This will post notifications tracking progress. - /// - /// The to be downloaded. - /// Whether the beatmap should be downloaded without video. Defaults to false. - public void Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) - { - var existing = GetExistingDownload(beatmapSetInfo); - - if (existing != null || api == null) return; - - if (!api.LocalUser.Value.IsSupporter) - { - PostNotification?.Invoke(new SimpleNotification - { - Icon = FontAwesome.fa_superpowers, - Text = "You gotta be a supporter to download for now 'yo" - }); - return; - } - - var downloadNotification = new ProgressNotification - { - CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", - Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", - }; - - var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo); - - request.DownloadProgressed += progress => - { - downloadNotification.State = ProgressNotificationState.Active; - downloadNotification.Progress = progress; - }; - - request.Success += data => - { - downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}"; - - Task.Factory.StartNew(() => - { - // This gets scheduled back to the update thread, but we want the import to run in the background. - using (var stream = new MemoryStream(data)) - using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) - Import(archive); - - downloadNotification.State = ProgressNotificationState.Completed; - currentDownloads.Remove(request); - }, TaskCreationOptions.LongRunning); - }; - - request.Failure += error => - { - if (error is OperationCanceledException) return; - - downloadNotification.State = ProgressNotificationState.Completed; - Logger.Error(error, "Beatmap download failed!"); - currentDownloads.Remove(request); - }; - - downloadNotification.CancelRequested += () => - { - request.Cancel(); - currentDownloads.Remove(request); - downloadNotification.State = ProgressNotificationState.Cancelled; - return true; - }; - - currentDownloads.Add(request); - PostNotification?.Invoke(downloadNotification); - - // don't run in the main api queue as this is a long-running task. - Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); - BeatmapDownloadBegan?.Invoke(request); - } - - /// - /// Get an existing download request if it exists. - /// - /// The whose download request is wanted. - /// The object if it exists, or null. - public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); - - /// - /// Delete a beatmap difficulty. - /// - /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); - - /// - /// Restore a beatmap difficulty. - /// - /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); - - /// - /// Retrieve a instance for the provided - /// - /// The beatmap to lookup. - /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. - /// A instance correlating to the provided . - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) - { - if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) - return DefaultBeatmap; - - if (beatmapInfo.Metadata == null) - beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; - - WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo, audioManager); - - previous?.TransferTo(working); - - return working; - } - - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// The first result for the provided query, or null if no results were found. - public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); - - /// - /// Returns a list of all usable s. - /// - /// A list of available . - public List GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList(); - - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// Results from the provided query. - public IEnumerable QueryBeatmapSets(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().Where(query); - - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// The first result for the provided query, or null if no results were found. - public BeatmapInfo QueryBeatmap(Expression> query) => beatmaps.Beatmaps.AsNoTracking().FirstOrDefault(query); - - /// - /// Perform a lookup query on available s. - /// - /// The query. - /// Results from the provided query. - public IEnumerable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); - - /// - /// Denotes whether an osu-stable installation is present to perform automated imports from. - /// - public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null; - - /// - /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. - /// - public async Task ImportFromStable() - { - var stable = GetStableStorage?.Invoke(); - - if (stable == null) - { - Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); - return; - } - - await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning); - } - - /// - /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content. - /// - private string computeBeatmapSetHash(ArchiveReader reader) - { - // for now, concatenate all .osu files in the set to create a unique hash. - MemoryStream hashable = new MemoryStream(); - foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - - return hashable.ComputeSHA2Hash(); - } - - protected override BeatmapSetInfo CreateModel(ArchiveReader reader) - { - // let's make sure there are actually .osu files to import. - string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); - if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder."); - - BeatmapMetadata metadata; - using (var stream = new StreamReader(reader.GetStream(mapName))) - metadata = Decoder.GetDecoder(stream).Decode(stream).Metadata; - - return new BeatmapSetInfo - { - OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, - Beatmaps = new List(), - Hash = computeBeatmapSetHash(reader), - Metadata = metadata - }; - } - - /// - /// Create all required s for the provided archive. - /// - private List createBeatmapDifficulties(ArchiveReader reader) - { - var beatmapInfos = new List(); - - foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu"))) - { - using (var raw = reader.GetStream(name)) - using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit - using (var sr = new StreamReader(ms)) - { - raw.CopyTo(ms); - ms.Position = 0; - - var decoder = Decoder.GetDecoder(sr); - Beatmap beatmap = decoder.Decode(sr); - - beatmap.BeatmapInfo.Path = name; - beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); - beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); - - RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); - - // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.Ruleset = ruleset; - beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0; - - beatmapInfos.Add(beatmap.BeatmapInfo); - } - } - - return beatmapInfos; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps.Formats; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.IO.Archives; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; + +namespace osu.Game.Beatmaps +{ + /// + /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. + /// + public partial class BeatmapManager : ArchiveModelManager + { + /// + /// Fired when a single difficulty has been hidden. + /// + public event Action BeatmapHidden; + + /// + /// Fired when a single difficulty has been restored. + /// + public event Action BeatmapRestored; + + /// + /// Fired when a beatmap download begins. + /// + public event Action BeatmapDownloadBegan; + + /// + /// A default representation of a WorkingBeatmap to use when no beatmap is available. + /// + public WorkingBeatmap DefaultBeatmap { private get; set; } + + public override string[] HandledExtensions => new[] { ".osz" }; + + private readonly RulesetStore rulesets; + + private readonly BeatmapStore beatmaps; + + private readonly APIAccess api; + + private readonly AudioManager audioManager; + + private readonly List currentDownloads = new List(); + + /// + /// Set a storage with access to an osu-stable install for import purposes. + /// + public Func GetStableStorage { private get; set; } + + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null) + : base(storage, contextFactory, new BeatmapStore(contextFactory), importHost) + { + beatmaps = (BeatmapStore)ModelStore; + beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); + beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); + + this.rulesets = rulesets; + this.api = api; + this.audioManager = audioManager; + } + + protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) + { + model.Beatmaps = createBeatmapDifficulties(archive); + + // remove metadata from difficulties where it matches the set + foreach (BeatmapInfo b in model.Beatmaps) + if (model.Metadata.Equals(b.Metadata)) + b.Metadata = null; + } + + protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) + { + // check if this beatmap has already been imported and exit early if so + var existingHashMatch = beatmaps.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + if (existingHashMatch != null) + { + Undelete(existingHashMatch); + return existingHashMatch; + } + + // check if a set already exists with the same online id + if (model.OnlineBeatmapSetID != null) + { + var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); + if (existingOnlineId != null) + { + Delete(existingOnlineId); + beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); + } + } + + return null; + } + + /// + /// Downloads a beatmap. + /// This will post notifications tracking progress. + /// + /// The to be downloaded. + /// Whether the beatmap should be downloaded without video. Defaults to false. + public void Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) + { + var existing = GetExistingDownload(beatmapSetInfo); + + if (existing != null || api == null) return; + + if (!api.LocalUser.Value.IsSupporter) + { + PostNotification?.Invoke(new SimpleNotification + { + Icon = FontAwesome.fa_superpowers, + Text = "You gotta be a supporter to download for now 'yo" + }); + return; + } + + var downloadNotification = new ProgressNotification + { + CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", + Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", + }; + + var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo); + + request.DownloadProgressed += progress => + { + downloadNotification.State = ProgressNotificationState.Active; + downloadNotification.Progress = progress; + }; + + request.Success += data => + { + downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}"; + + Task.Factory.StartNew(() => + { + // This gets scheduled back to the update thread, but we want the import to run in the background. + using (var stream = new MemoryStream(data)) + using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) + Import(archive); + + downloadNotification.State = ProgressNotificationState.Completed; + currentDownloads.Remove(request); + }, TaskCreationOptions.LongRunning); + }; + + request.Failure += error => + { + if (error is OperationCanceledException) return; + + downloadNotification.State = ProgressNotificationState.Completed; + Logger.Error(error, "Beatmap download failed!"); + currentDownloads.Remove(request); + }; + + downloadNotification.CancelRequested += () => + { + request.Cancel(); + currentDownloads.Remove(request); + downloadNotification.State = ProgressNotificationState.Cancelled; + return true; + }; + + currentDownloads.Add(request); + PostNotification?.Invoke(downloadNotification); + + // don't run in the main api queue as this is a long-running task. + Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); + BeatmapDownloadBegan?.Invoke(request); + } + + /// + /// Get an existing download request if it exists. + /// + /// The whose download request is wanted. + /// The object if it exists, or null. + public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); + + /// + /// Delete a beatmap difficulty. + /// + /// The beatmap difficulty to hide. + public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); + + /// + /// Restore a beatmap difficulty. + /// + /// The beatmap difficulty to restore. + public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); + + /// + /// Retrieve a instance for the provided + /// + /// The beatmap to lookup. + /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. + /// A instance correlating to the provided . + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) + { + if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) + return DefaultBeatmap; + + if (beatmapInfo.Metadata == null) + beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; + + WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo, audioManager); + + previous?.TransferTo(working); + + return working; + } + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The first result for the provided query, or null if no results were found. + public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + + /// + /// Returns a list of all usable s. + /// + /// A list of available . + public List GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList(); + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// Results from the provided query. + public IEnumerable QueryBeatmapSets(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().Where(query); + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The first result for the provided query, or null if no results were found. + public BeatmapInfo QueryBeatmap(Expression> query) => beatmaps.Beatmaps.AsNoTracking().FirstOrDefault(query); + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// Results from the provided query. + public IEnumerable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); + + /// + /// Denotes whether an osu-stable installation is present to perform automated imports from. + /// + public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null; + + /// + /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. + /// + public async Task ImportFromStable() + { + var stable = GetStableStorage?.Invoke(); + + if (stable == null) + { + Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); + return; + } + + await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning); + } + + /// + /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content. + /// + private string computeBeatmapSetHash(ArchiveReader reader) + { + // for now, concatenate all .osu files in the set to create a unique hash. + MemoryStream hashable = new MemoryStream(); + foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + + return hashable.ComputeSHA2Hash(); + } + + protected override BeatmapSetInfo CreateModel(ArchiveReader reader) + { + // let's make sure there are actually .osu files to import. + string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder."); + + BeatmapMetadata metadata; + using (var stream = new StreamReader(reader.GetStream(mapName))) + metadata = Decoder.GetDecoder(stream).Decode(stream).Metadata; + + return new BeatmapSetInfo + { + OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, + Beatmaps = new List(), + Hash = computeBeatmapSetHash(reader), + Metadata = metadata + }; + } + + /// + /// Create all required s for the provided archive. + /// + private List createBeatmapDifficulties(ArchiveReader reader) + { + var beatmapInfos = new List(); + + foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu"))) + { + using (var raw = reader.GetStream(name)) + using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit + using (var sr = new StreamReader(ms)) + { + raw.CopyTo(ms); + ms.Position = 0; + + var decoder = Decoder.GetDecoder(sr); + Beatmap beatmap = decoder.Decode(sr); + + beatmap.BeatmapInfo.Path = name; + beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); + beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); + + RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); + + // TODO: this should be done in a better place once we actually need to dynamically update it. + beatmap.BeatmapInfo.Ruleset = ruleset; + beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0; + + beatmapInfos.Add(beatmap.BeatmapInfo); + } + } + + return beatmapInfos; + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 5874314f75..8e09d66c42 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -1,125 +1,125 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using System.Linq; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; -using osu.Framework.Graphics.Textures; -using osu.Framework.IO.Stores; -using osu.Framework.Logging; -using osu.Game.Beatmaps.Formats; -using osu.Game.Graphics.Textures; -using osu.Game.Skinning; -using osu.Game.Storyboards; - -namespace osu.Game.Beatmaps -{ - public partial class BeatmapManager - { - protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap - { - private readonly IResourceStore store; - private readonly AudioManager audioManager; - - public BeatmapManagerWorkingBeatmap(IResourceStore store, BeatmapInfo beatmapInfo, AudioManager audioManager) - : base(beatmapInfo) - { - this.store = store; - this.audioManager = audioManager; - } - - protected override Beatmap GetBeatmap() - { - try - { - using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) - return Decoder.GetDecoder(stream).Decode(stream); - } - catch - { - return null; - } - } - - private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; - - protected override Texture GetBackground() - { - if (Metadata?.BackgroundFile == null) - return null; - - try - { - return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile)); - } - catch - { - return null; - } - } - - protected override Track GetTrack() - { - try - { - var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); - return trackData == null ? null : new TrackBass(trackData); - } - catch - { - return new TrackVirtual(); - } - } - - protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); - - protected override Storyboard GetStoryboard() - { - Storyboard storyboard; - try - { - using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) - { - var decoder = Decoder.GetDecoder(stream); - - // todo: support loading from both set-wide storyboard *and* beatmap specific. - if (BeatmapSetInfo?.StoryboardFile == null) - storyboard = decoder.Decode(stream); - else - { - using (var secondaryStream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) - storyboard = decoder.Decode(stream, secondaryStream); - } - } - } - catch (Exception e) - { - Logger.Error(e, "Storyboard failed to load"); - storyboard = new Storyboard(); - } - - storyboard.BeatmapInfo = BeatmapInfo; - - return storyboard; - } - - protected override Skin GetSkin() - { - Skin skin; - try - { - skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager); - } - catch (Exception e) - { - Logger.Error(e, "Skin failed to load"); - skin = new DefaultSkin(); - } - - return skin; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Linq; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Framework.Logging; +using osu.Game.Beatmaps.Formats; +using osu.Game.Graphics.Textures; +using osu.Game.Skinning; +using osu.Game.Storyboards; + +namespace osu.Game.Beatmaps +{ + public partial class BeatmapManager + { + protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap + { + private readonly IResourceStore store; + private readonly AudioManager audioManager; + + public BeatmapManagerWorkingBeatmap(IResourceStore store, BeatmapInfo beatmapInfo, AudioManager audioManager) + : base(beatmapInfo) + { + this.store = store; + this.audioManager = audioManager; + } + + protected override Beatmap GetBeatmap() + { + try + { + using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) + return Decoder.GetDecoder(stream).Decode(stream); + } + catch + { + return null; + } + } + + private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; + + protected override Texture GetBackground() + { + if (Metadata?.BackgroundFile == null) + return null; + + try + { + return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile)); + } + catch + { + return null; + } + } + + protected override Track GetTrack() + { + try + { + var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); + return trackData == null ? null : new TrackBass(trackData); + } + catch + { + return new TrackVirtual(); + } + } + + protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); + + protected override Storyboard GetStoryboard() + { + Storyboard storyboard; + try + { + using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) + { + var decoder = Decoder.GetDecoder(stream); + + // todo: support loading from both set-wide storyboard *and* beatmap specific. + if (BeatmapSetInfo?.StoryboardFile == null) + storyboard = decoder.Decode(stream); + else + { + using (var secondaryStream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) + storyboard = decoder.Decode(stream, secondaryStream); + } + } + } + catch (Exception e) + { + Logger.Error(e, "Storyboard failed to load"); + storyboard = new Storyboard(); + } + + storyboard.BeatmapInfo = BeatmapInfo; + + return storyboard; + } + + protected override Skin GetSkin() + { + Skin skin; + try + { + skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager); + } + catch (Exception e) + { + Logger.Error(e, "Skin failed to load"); + skin = new DefaultSkin(); + } + + return skin; + } + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index a28266dc62..34147c18d2 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -1,98 +1,98 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using Newtonsoft.Json; -using osu.Game.Users; - -namespace osu.Game.Beatmaps -{ - [Serializable] - public class BeatmapMetadata : IEquatable - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] - public int ID { get; set; } - - private int? onlineBeatmapSetID; - - [NotMapped] - [JsonProperty(@"id")] - public int? OnlineBeatmapSetID - { - get { return onlineBeatmapSetID; } - set { onlineBeatmapSetID = value > 0 ? value : null; } - } - - public string Title { get; set; } - public string TitleUnicode { get; set; } - public string Artist { get; set; } - public string ArtistUnicode { get; set; } - - [JsonIgnore] - public List Beatmaps { get; set; } - - [JsonIgnore] - public List BeatmapSets { get; set; } - - /// - /// Helper property to deserialize a username to . - /// - [JsonProperty(@"creator")] - [Column("Author")] - public string AuthorString - { - get { return Author?.Username; } - set { Author = new User { Username = value }; } - } - - /// - /// The author of the beatmaps in this set. - /// - [JsonIgnore] - public User Author; - - public string Source { get; set; } - - [JsonProperty(@"tags")] - public string Tags { get; set; } - public int PreviewTime { get; set; } - public string AudioFile { get; set; } - public string BackgroundFile { get; set; } - - public override string ToString() => $"{Artist} - {Title} ({Author})"; - - [JsonIgnore] - public string[] SearchableTerms => new[] - { - Author?.Username, - Artist, - ArtistUnicode, - Title, - TitleUnicode, - Source, - Tags - }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); - - public bool Equals(BeatmapMetadata other) - { - if (other == null) - return false; - - return onlineBeatmapSetID == other.onlineBeatmapSetID - && Title == other.Title - && TitleUnicode == other.TitleUnicode - && Artist == other.Artist - && ArtistUnicode == other.ArtistUnicode - && AuthorString == other.AuthorString - && Source == other.Source - && Tags == other.Tags - && PreviewTime == other.PreviewTime - && AudioFile == other.AudioFile - && BackgroundFile == other.BackgroundFile; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Users; + +namespace osu.Game.Beatmaps +{ + [Serializable] + public class BeatmapMetadata : IEquatable + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] + public int ID { get; set; } + + private int? onlineBeatmapSetID; + + [NotMapped] + [JsonProperty(@"id")] + public int? OnlineBeatmapSetID + { + get { return onlineBeatmapSetID; } + set { onlineBeatmapSetID = value > 0 ? value : null; } + } + + public string Title { get; set; } + public string TitleUnicode { get; set; } + public string Artist { get; set; } + public string ArtistUnicode { get; set; } + + [JsonIgnore] + public List Beatmaps { get; set; } + + [JsonIgnore] + public List BeatmapSets { get; set; } + + /// + /// Helper property to deserialize a username to . + /// + [JsonProperty(@"creator")] + [Column("Author")] + public string AuthorString + { + get { return Author?.Username; } + set { Author = new User { Username = value }; } + } + + /// + /// The author of the beatmaps in this set. + /// + [JsonIgnore] + public User Author; + + public string Source { get; set; } + + [JsonProperty(@"tags")] + public string Tags { get; set; } + public int PreviewTime { get; set; } + public string AudioFile { get; set; } + public string BackgroundFile { get; set; } + + public override string ToString() => $"{Artist} - {Title} ({Author})"; + + [JsonIgnore] + public string[] SearchableTerms => new[] + { + Author?.Username, + Artist, + ArtistUnicode, + Title, + TitleUnicode, + Source, + Tags + }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); + + public bool Equals(BeatmapMetadata other) + { + if (other == null) + return false; + + return onlineBeatmapSetID == other.onlineBeatmapSetID + && Title == other.Title + && TitleUnicode == other.TitleUnicode + && Artist == other.Artist + && ArtistUnicode == other.ArtistUnicode + && AuthorString == other.AuthorString + && Source == other.Source + && Tags == other.Tags + && PreviewTime == other.PreviewTime + && AudioFile == other.AudioFile + && BackgroundFile == other.BackgroundFile; + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapMetrics.cs b/osu.Game/Beatmaps/BeatmapMetrics.cs index 78527e7a02..427c2b50ac 100644 --- a/osu.Game/Beatmaps/BeatmapMetrics.cs +++ b/osu.Game/Beatmaps/BeatmapMetrics.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace osu.Game.Beatmaps -{ - /// - /// Beatmap metrics based on acculumated online data from community plays. - /// - public class BeatmapMetrics - { - /// - /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?). - /// - public IEnumerable Ratings { get; set; } - - /// - /// Points of failure on a relative time scale (usually 0..100). - /// - [JsonProperty(@"fail")] - public IEnumerable Fails { get; set; } - - /// - /// Points of retry on a relative time scale (usually 0..100). - /// - [JsonProperty(@"exit")] - public IEnumerable Retries { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace osu.Game.Beatmaps +{ + /// + /// Beatmap metrics based on acculumated online data from community plays. + /// + public class BeatmapMetrics + { + /// + /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?). + /// + public IEnumerable Ratings { get; set; } + + /// + /// Points of failure on a relative time scale (usually 0..100). + /// + [JsonProperty(@"fail")] + public IEnumerable Fails { get; set; } + + /// + /// Points of retry on a relative time scale (usually 0..100). + /// + [JsonProperty(@"exit")] + public IEnumerable Retries { get; set; } + } +} diff --git a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs index 58e4b2b5aa..3c1c2b10ce 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps -{ - /// - /// Beatmap info retrieved for previewing locally without having the beatmap downloaded. - /// - public class BeatmapOnlineInfo - { - /// - /// The length in milliseconds of this beatmap's song. - /// - public double Length { get; set; } - - /// - /// The amount of circles in this beatmap. - /// - public int CircleCount { get; set; } - - /// - /// The amount of sliders in this beatmap. - /// - public int SliderCount { get; set; } - - /// - /// The amount of plays this beatmap has. - /// - public int PlayCount { get; set; } - - /// - /// The amount of passes this beatmap has. - /// - public int PassCount { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps +{ + /// + /// Beatmap info retrieved for previewing locally without having the beatmap downloaded. + /// + public class BeatmapOnlineInfo + { + /// + /// The length in milliseconds of this beatmap's song. + /// + public double Length { get; set; } + + /// + /// The amount of circles in this beatmap. + /// + public int CircleCount { get; set; } + + /// + /// The amount of sliders in this beatmap. + /// + public int SliderCount { get; set; } + + /// + /// The amount of plays this beatmap has. + /// + public int PlayCount { get; set; } + + /// + /// The amount of passes this beatmap has. + /// + public int PassCount { get; set; } + } +} diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index f2cc419043..8f5a2a4cab 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -1,49 +1,49 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Beatmaps -{ - /// - /// Processes a post-converted Beatmap. - /// - /// The type of HitObject contained in the Beatmap. - public class BeatmapProcessor - where TObject : HitObject - { - /// - /// Post-processes a Beatmap to add mode-specific components that aren't added during conversion. - /// - /// An example of such a usage is for combo colours. - /// - /// - /// The Beatmap to process. - public virtual void PostProcess(Beatmap beatmap) - { - IHasComboInformation lastObj = null; - - foreach (var obj in beatmap.HitObjects.OfType()) - { - if (obj.NewCombo) - { - obj.IndexInCurrentCombo = 0; - if (lastObj != null) - { - lastObj.LastInCombo = true; - obj.ComboIndex = lastObj.ComboIndex + 1; - } - } - else if (lastObj != null) - { - obj.IndexInCurrentCombo = lastObj.IndexInCurrentCombo + 1; - obj.ComboIndex = lastObj.ComboIndex; - } - - lastObj = obj; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Beatmaps +{ + /// + /// Processes a post-converted Beatmap. + /// + /// The type of HitObject contained in the Beatmap. + public class BeatmapProcessor + where TObject : HitObject + { + /// + /// Post-processes a Beatmap to add mode-specific components that aren't added during conversion. + /// + /// An example of such a usage is for combo colours. + /// + /// + /// The Beatmap to process. + public virtual void PostProcess(Beatmap beatmap) + { + IHasComboInformation lastObj = null; + + foreach (var obj in beatmap.HitObjects.OfType()) + { + if (obj.NewCombo) + { + obj.IndexInCurrentCombo = 0; + if (lastObj != null) + { + lastObj.LastInCombo = true; + obj.ComboIndex = lastObj.ComboIndex + 1; + } + } + else if (lastObj != null) + { + obj.IndexInCurrentCombo = lastObj.IndexInCurrentCombo + 1; + obj.ComboIndex = lastObj.ComboIndex; + } + + lastObj = obj; + } + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index e88af6ed30..4fa286cbec 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using osu.Game.Database; -using osu.Game.IO; - -namespace osu.Game.Beatmaps -{ - public class BeatmapSetFileInfo : INamedFileInfo - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int ID { get; set; } - - public int BeatmapSetInfoID { get; set; } - - public int FileInfoID { get; set; } - - public FileInfo FileInfo { get; set; } - - [Required] - public string Filename { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; +using osu.Game.IO; + +namespace osu.Game.Beatmaps +{ + public class BeatmapSetFileInfo : INamedFileInfo + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public int BeatmapSetInfoID { get; set; } + + public int FileInfoID { get; set; } + + public FileInfo FileInfo { get; set; } + + [Required] + public string Filename { get; set; } + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 1736e3fa90..fa08c6cb68 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -1,40 +1,40 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using osu.Game.Database; - -namespace osu.Game.Beatmaps -{ - public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int ID { get; set; } - - public int? OnlineBeatmapSetID { get; set; } - - public BeatmapMetadata Metadata { get; set; } - - public List Beatmaps { get; set; } - - [NotMapped] - public BeatmapSetOnlineInfo OnlineInfo { get; set; } - - public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); - - [NotMapped] - public bool DeletePending { get; set; } - - public string Hash { get; set; } - - public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename; - - public List Files { get; set; } - - public override string ToString() => Metadata.ToString(); - - public bool Protected { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using osu.Game.Database; + +namespace osu.Game.Beatmaps +{ + public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public int? OnlineBeatmapSetID { get; set; } + + public BeatmapMetadata Metadata { get; set; } + + public List Beatmaps { get; set; } + + [NotMapped] + public BeatmapSetOnlineInfo OnlineInfo { get; set; } + + public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); + + [NotMapped] + public bool DeletePending { get; set; } + + public string Hash { get; set; } + + public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename; + + public List Files { get; set; } + + public override string ToString() => Metadata.ToString(); + + public bool Protected { get; set; } + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index f7221a6ac3..a70caf0207 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -1,82 +1,82 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using Newtonsoft.Json; - -namespace osu.Game.Beatmaps -{ - /// - /// Beatmap set info retrieved for previewing locally without having the set downloaded. - /// - public class BeatmapSetOnlineInfo - { - /// - /// The date this beatmap set was submitted to the online listing. - /// - public DateTimeOffset Submitted { get; set; } - - /// - /// The date this beatmap set was ranked. - /// - public DateTimeOffset? Ranked { get; set; } - - /// - /// The date this beatmap set was last updated. - /// - public DateTimeOffset? LastUpdated { get; set; } - - /// - /// The status of this beatmap set. - /// - public BeatmapSetOnlineStatus Status { get; set; } - - /// - /// Whether or not this beatmap set has a background video. - /// - public bool HasVideo { get; set; } - - /// - /// The different sizes of cover art for this beatmap set. - /// - public BeatmapSetOnlineCovers Covers { get; set; } - - /// - /// A small sample clip of this beatmap set's song. - /// - public string Preview { get; set; } - - /// - /// The beats per minute of this beatmap set's song. - /// - public double BPM { get; set; } - - /// - /// The amount of plays this beatmap set has. - /// - public int PlayCount { get; set; } - - /// - /// The amount of people who have favourited this beatmap set. - /// - public int FavouriteCount { get; set; } - } - - public class BeatmapSetOnlineCovers - { - public string CoverLowRes { get; set; } - - [JsonProperty(@"cover@2x")] - public string Cover { get; set; } - - public string CardLowRes { get; set; } - - [JsonProperty(@"card@2x")] - public string Card { get; set; } - - public string ListLowRes { get; set; } - - [JsonProperty(@"list@2x")] - public string List { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Newtonsoft.Json; + +namespace osu.Game.Beatmaps +{ + /// + /// Beatmap set info retrieved for previewing locally without having the set downloaded. + /// + public class BeatmapSetOnlineInfo + { + /// + /// The date this beatmap set was submitted to the online listing. + /// + public DateTimeOffset Submitted { get; set; } + + /// + /// The date this beatmap set was ranked. + /// + public DateTimeOffset? Ranked { get; set; } + + /// + /// The date this beatmap set was last updated. + /// + public DateTimeOffset? LastUpdated { get; set; } + + /// + /// The status of this beatmap set. + /// + public BeatmapSetOnlineStatus Status { get; set; } + + /// + /// Whether or not this beatmap set has a background video. + /// + public bool HasVideo { get; set; } + + /// + /// The different sizes of cover art for this beatmap set. + /// + public BeatmapSetOnlineCovers Covers { get; set; } + + /// + /// A small sample clip of this beatmap set's song. + /// + public string Preview { get; set; } + + /// + /// The beats per minute of this beatmap set's song. + /// + public double BPM { get; set; } + + /// + /// The amount of plays this beatmap set has. + /// + public int PlayCount { get; set; } + + /// + /// The amount of people who have favourited this beatmap set. + /// + public int FavouriteCount { get; set; } + } + + public class BeatmapSetOnlineCovers + { + public string CoverLowRes { get; set; } + + [JsonProperty(@"cover@2x")] + public string Cover { get; set; } + + public string CardLowRes { get; set; } + + [JsonProperty(@"card@2x")] + public string Card { get; set; } + + public string ListLowRes { get; set; } + + [JsonProperty(@"list@2x")] + public string List { get; set; } + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs index c7f767d3b2..4f97ee4133 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps -{ - public enum BeatmapSetOnlineStatus - { - None = -3, - Graveyard = -2, - WIP = -1, - Pending = 0, - Ranked = 1, - Approved = 2, - Qualified = 3, - Loved = 4, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps +{ + public enum BeatmapSetOnlineStatus + { + None = -3, + Graveyard = -2, + WIP = -1, + Pending = 0, + Ranked = 1, + Approved = 2, + Qualified = 3, + Loved = 4, + } +} diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index d061651e71..aebe2573d7 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; - -namespace osu.Game.Beatmaps -{ - public class BeatmapStatistic - { - public FontAwesome Icon; - public string Content; - public string Name; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Beatmaps +{ + public class BeatmapStatistic + { + public FontAwesome Icon; + public string Content; + public string Name; + } +} diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 93ad1badd2..5bdc42cdf3 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -1,97 +1,97 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.EntityFrameworkCore; -using osu.Game.Database; - -namespace osu.Game.Beatmaps -{ - /// - /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing - /// - public class BeatmapStore : MutableDatabaseBackedStore - { - public event Action BeatmapHidden; - public event Action BeatmapRestored; - - public BeatmapStore(IDatabaseContextFactory factory) - : base(factory) - { - } - - /// - /// Hide a in the database. - /// - /// The beatmap to hide. - /// Whether the beatmap's was changed. - public bool Hide(BeatmapInfo beatmap) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref beatmap, Beatmaps); - - if (beatmap.Hidden) return false; - beatmap.Hidden = true; - } - - BeatmapHidden?.Invoke(beatmap); - return true; - } - - /// - /// Restore a previously hidden . - /// - /// The beatmap to restore. - /// Whether the beatmap's was changed. - public bool Restore(BeatmapInfo beatmap) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref beatmap, Beatmaps); - - if (!beatmap.Hidden) return false; - beatmap.Hidden = false; - } - - BeatmapRestored?.Invoke(beatmap); - return true; - } - - protected override IQueryable AddIncludesForDeletion(IQueryable query) => - base.AddIncludesForDeletion(query) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Metadata); - - protected override IQueryable AddIncludesForConsumption(IQueryable query) => - base.AddIncludesForConsumption(query) - .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Files).ThenInclude(f => f.FileInfo); - - protected override void Purge(List items, OsuDbContext context) - { - // metadata is M-N so we can't rely on cascades - context.BeatmapMetadata.RemoveRange(items.Select(s => s.Metadata)); - context.BeatmapMetadata.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null))); - - // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly. - context.BeatmapDifficulty.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty))); - - base.Purge(items, context); - } - - public IQueryable Beatmaps => - ContextFactory.Get().BeatmapInfo - .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) - .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.BaseDifficulty); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Game.Database; + +namespace osu.Game.Beatmaps +{ + /// + /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing + /// + public class BeatmapStore : MutableDatabaseBackedStore + { + public event Action BeatmapHidden; + public event Action BeatmapRestored; + + public BeatmapStore(IDatabaseContextFactory factory) + : base(factory) + { + } + + /// + /// Hide a in the database. + /// + /// The beatmap to hide. + /// Whether the beatmap's was changed. + public bool Hide(BeatmapInfo beatmap) + { + using (ContextFactory.GetForWrite()) + { + Refresh(ref beatmap, Beatmaps); + + if (beatmap.Hidden) return false; + beatmap.Hidden = true; + } + + BeatmapHidden?.Invoke(beatmap); + return true; + } + + /// + /// Restore a previously hidden . + /// + /// The beatmap to restore. + /// Whether the beatmap's was changed. + public bool Restore(BeatmapInfo beatmap) + { + using (ContextFactory.GetForWrite()) + { + Refresh(ref beatmap, Beatmaps); + + if (!beatmap.Hidden) return false; + beatmap.Hidden = false; + } + + BeatmapRestored?.Invoke(beatmap); + return true; + } + + protected override IQueryable AddIncludesForDeletion(IQueryable query) => + base.AddIncludesForDeletion(query) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Metadata); + + protected override IQueryable AddIncludesForConsumption(IQueryable query) => + base.AddIncludesForConsumption(query) + .Include(s => s.Metadata) + .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Files).ThenInclude(f => f.FileInfo); + + protected override void Purge(List items, OsuDbContext context) + { + // metadata is M-N so we can't rely on cascades + context.BeatmapMetadata.RemoveRange(items.Select(s => s.Metadata)); + context.BeatmapMetadata.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null))); + + // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly. + context.BeatmapDifficulty.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty))); + + base.Purge(items, context); + } + + public IQueryable Beatmaps => + ContextFactory.Get().BeatmapInfo + .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) + .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(b => b.Metadata) + .Include(b => b.Ruleset) + .Include(b => b.BaseDifficulty); + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 2b49716853..db9e712d86 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Beatmaps.ControlPoints -{ - public class ControlPoint : IComparable, IEquatable - { - /// - /// The time at which the control point takes effect. - /// - public double Time; - - public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - - public bool Equals(ControlPoint other) => Time.Equals(other?.Time); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class ControlPoint : IComparable, IEquatable + { + /// + /// The time at which the control point takes effect. + /// + public double Time; + + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); + + public bool Equals(ControlPoint other) => Time.Equals(other?.Time); + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 69988b2a29..dad69321a5 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -1,120 +1,120 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.Lists; - -namespace osu.Game.Beatmaps.ControlPoints -{ - [Serializable] - public class ControlPointInfo - { - /// - /// All timing points. - /// - [JsonProperty] - public SortedList TimingPoints { get; private set; } = new SortedList(Comparer.Default); - - /// - /// All difficulty points. - /// - [JsonProperty] - public SortedList DifficultyPoints { get; private set; } = new SortedList(Comparer.Default); - - /// - /// All sound points. - /// - [JsonProperty] - public SortedList SamplePoints { get; private set; } = new SortedList(Comparer.Default); - - /// - /// All effect points. - /// - [JsonProperty] - public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); - - /// - /// Finds the difficulty control point that is active at . - /// - /// The time to find the difficulty control point at. - /// The difficulty control point. - public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time); - - /// - /// Finds the effect control point that is active at . - /// - /// The time to find the effect control point at. - /// The effect control point. - public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time); - - /// - /// Finds the sound control point that is active at . - /// - /// The time to find the sound control point at. - /// The sound control point. - public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault()); - - /// - /// Finds the timing control point that is active at . - /// - /// The time to find the timing control point at. - /// The timing control point. - public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault()); - - /// - /// Finds the maximum BPM represented by any timing control point. - /// - [JsonIgnore] - public double BPMMaximum => - 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; - - /// - /// Finds the minimum BPM represented by any timing control point. - /// - [JsonIgnore] - public double BPMMinimum => - 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; - - /// - /// Finds the mode BPM (most common BPM) represented by the control points. - /// - [JsonIgnore] - public double BPMMode => - 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; - - /// - /// Binary searches one of the control point lists to find the active control point at . - /// - /// The list to search. - /// The time to find the control point at. - /// The control point to use when is before any control points. If null, a new control point will be constructed. - /// The active control point at . - private T binarySearch(SortedList list, double time, T prePoint = null) - where T : ControlPoint, new() - { - if (list == null) - throw new ArgumentNullException(nameof(list)); - - if (list.Count == 0) - return new T(); - - if (time < list[0].Time) - return prePoint ?? new T(); - - int index = list.BinarySearch(new T { Time = time }); - - // Check if we've found an exact match (t == time) - if (index >= 0) - return list[index]; - - index = ~index; - - // BinarySearch will return the index of the first element _greater_ than the search - // This is the inactive point - the active point is the one before it (index - 1) - return list[index - 1]; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Lists; + +namespace osu.Game.Beatmaps.ControlPoints +{ + [Serializable] + public class ControlPointInfo + { + /// + /// All timing points. + /// + [JsonProperty] + public SortedList TimingPoints { get; private set; } = new SortedList(Comparer.Default); + + /// + /// All difficulty points. + /// + [JsonProperty] + public SortedList DifficultyPoints { get; private set; } = new SortedList(Comparer.Default); + + /// + /// All sound points. + /// + [JsonProperty] + public SortedList SamplePoints { get; private set; } = new SortedList(Comparer.Default); + + /// + /// All effect points. + /// + [JsonProperty] + public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); + + /// + /// Finds the difficulty control point that is active at . + /// + /// The time to find the difficulty control point at. + /// The difficulty control point. + public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time); + + /// + /// Finds the effect control point that is active at . + /// + /// The time to find the effect control point at. + /// The effect control point. + public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time); + + /// + /// Finds the sound control point that is active at . + /// + /// The time to find the sound control point at. + /// The sound control point. + public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault()); + + /// + /// Finds the timing control point that is active at . + /// + /// The time to find the timing control point at. + /// The timing control point. + public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault()); + + /// + /// Finds the maximum BPM represented by any timing control point. + /// + [JsonIgnore] + public double BPMMaximum => + 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + + /// + /// Finds the minimum BPM represented by any timing control point. + /// + [JsonIgnore] + public double BPMMinimum => + 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + + /// + /// Finds the mode BPM (most common BPM) represented by the control points. + /// + [JsonIgnore] + public double BPMMode => + 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + + /// + /// Binary searches one of the control point lists to find the active control point at . + /// + /// The list to search. + /// The time to find the control point at. + /// The control point to use when is before any control points. If null, a new control point will be constructed. + /// The active control point at . + private T binarySearch(SortedList list, double time, T prePoint = null) + where T : ControlPoint, new() + { + if (list == null) + throw new ArgumentNullException(nameof(list)); + + if (list.Count == 0) + return new T(); + + if (time < list[0].Time) + return prePoint ?? new T(); + + int index = list.BinarySearch(new T { Time = time }); + + // Check if we've found an exact match (t == time) + if (index >= 0) + return list[index]; + + index = ~index; + + // BinarySearch will return the index of the first element _greater_ than the search + // This is the inactive point - the active point is the one before it (index - 1) + return list[index - 1]; + } + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 2b42553891..9f717d21e3 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; - -namespace osu.Game.Beatmaps.ControlPoints -{ - public class DifficultyControlPoint : ControlPoint - { - /// - /// The speed multiplier at this control point. - /// - public double SpeedMultiplier - { - get => speedMultiplier; - set => speedMultiplier = MathHelper.Clamp(value, 0.1, 10); - } - - private double speedMultiplier = 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class DifficultyControlPoint : ControlPoint + { + /// + /// The speed multiplier at this control point. + /// + public double SpeedMultiplier + { + get => speedMultiplier; + set => speedMultiplier = MathHelper.Clamp(value, 0.1, 10); + } + + private double speedMultiplier = 1; + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 566d14a0be..73d5232f44 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.ControlPoints -{ - public class EffectControlPoint : ControlPoint - { - /// - /// Whether this control point enables Kiai mode. - /// - public bool KiaiMode; - - /// - /// Whether the first bar line of this control point is ignored. - /// - public bool OmitFirstBarLine; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class EffectControlPoint : ControlPoint + { + /// + /// Whether this control point enables Kiai mode. + /// + public bool KiaiMode; + + /// + /// Whether the first bar line of this control point is ignored. + /// + public bool OmitFirstBarLine; + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index ce06cc8a7b..5d801a1163 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Audio; - -namespace osu.Game.Beatmaps.ControlPoints -{ - public class SampleControlPoint : ControlPoint - { - public const string DEFAULT_BANK = "normal"; - - /// - /// The default sample bank at this control point. - /// - public string SampleBank = DEFAULT_BANK; - - /// - /// The default sample volume at this control point. - /// - public int SampleVolume = 100; - - /// - /// Create a SampleInfo based on the sample settings in this control point. - /// - /// The name of the same. - /// A populated . - public SampleInfo GetSampleInfo(string sampleName = SampleInfo.HIT_NORMAL) => new SampleInfo - { - Bank = SampleBank, - Name = sampleName, - Volume = SampleVolume, - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Audio; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class SampleControlPoint : ControlPoint + { + public const string DEFAULT_BANK = "normal"; + + /// + /// The default sample bank at this control point. + /// + public string SampleBank = DEFAULT_BANK; + + /// + /// The default sample volume at this control point. + /// + public int SampleVolume = 100; + + /// + /// Create a SampleInfo based on the sample settings in this control point. + /// + /// The name of the same. + /// A populated . + public SampleInfo GetSampleInfo(string sampleName = SampleInfo.HIT_NORMAL) => new SampleInfo + { + Bank = SampleBank, + Name = sampleName, + Volume = SampleVolume, + }; + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 0db1f08a90..d20b1b87a6 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Beatmaps.Timing; - -namespace osu.Game.Beatmaps.ControlPoints -{ - public class TimingControlPoint : ControlPoint - { - /// - /// The time signature at this control point. - /// - public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; - - /// - /// The beat length at this control point. - /// - public double BeatLength - { - get => beatLength; - set => beatLength = MathHelper.Clamp(value, 6, 60000); - } - - private double beatLength = 1000; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Beatmaps.Timing; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class TimingControlPoint : ControlPoint + { + /// + /// The time signature at this control point. + /// + public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + + /// + /// The beat length at this control point. + /// + public double BeatLength + { + get => beatLength; + set => beatLength = MathHelper.Clamp(value, 6, 60000); + } + + private double beatLength = 1000; + } +} diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index 2bea31c0d3..5e2d9afd23 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects; -using System.Collections.Generic; -using osu.Game.Rulesets.Mods; -using osu.Framework.Timing; -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; - -namespace osu.Game.Beatmaps -{ - public abstract class DifficultyCalculator - { - protected double TimeRate = 1; - - public abstract double Calculate(Dictionary categoryDifficulty = null); - } - - public abstract class DifficultyCalculator : DifficultyCalculator where T : HitObject - { - protected readonly Beatmap Beatmap; - protected readonly Mod[] Mods; - - protected DifficultyCalculator(Beatmap beatmap, Mod[] mods = null) - { - Mods = mods ?? new Mod[0]; - - var converter = CreateBeatmapConverter(beatmap); - - foreach (var mod in Mods.OfType>()) - mod.ApplyToBeatmapConverter(converter); - - Beatmap = converter.Convert(beatmap); - - ApplyMods(Mods); - - PreprocessHitObjects(); - } - - protected virtual void ApplyMods(Mod[] mods) - { - var clock = new StopwatchClock(); - mods.OfType().ForEach(m => m.ApplyToClock(clock)); - TimeRate = clock.Rate; - - foreach (var mod in Mods.OfType()) - mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); - - foreach (var h in Beatmap.HitObjects) - h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); - - foreach (var mod in mods.OfType>()) - foreach (var obj in Beatmap.HitObjects) - mod.ApplyToHitObject(obj); - } - - protected virtual void PreprocessHitObjects() - { - } - - protected abstract BeatmapConverter CreateBeatmapConverter(Beatmap beatmap); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; +using System.Collections.Generic; +using osu.Game.Rulesets.Mods; +using osu.Framework.Timing; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Beatmaps +{ + public abstract class DifficultyCalculator + { + protected double TimeRate = 1; + + public abstract double Calculate(Dictionary categoryDifficulty = null); + } + + public abstract class DifficultyCalculator : DifficultyCalculator where T : HitObject + { + protected readonly Beatmap Beatmap; + protected readonly Mod[] Mods; + + protected DifficultyCalculator(Beatmap beatmap, Mod[] mods = null) + { + Mods = mods ?? new Mod[0]; + + var converter = CreateBeatmapConverter(beatmap); + + foreach (var mod in Mods.OfType>()) + mod.ApplyToBeatmapConverter(converter); + + Beatmap = converter.Convert(beatmap); + + ApplyMods(Mods); + + PreprocessHitObjects(); + } + + protected virtual void ApplyMods(Mod[] mods) + { + var clock = new StopwatchClock(); + mods.OfType().ForEach(m => m.ApplyToClock(clock)); + TimeRate = clock.Rate; + + foreach (var mod in Mods.OfType()) + mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); + + foreach (var h in Beatmap.HitObjects) + h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); + + foreach (var mod in mods.OfType>()) + foreach (var obj in Beatmap.HitObjects) + mod.ApplyToHitObject(obj); + } + + protected virtual void PreprocessHitObjects() + { + } + + protected abstract BeatmapConverter CreateBeatmapConverter(Beatmap beatmap); + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs index 37af06e9f5..b747857e11 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; - -namespace osu.Game.Beatmaps.Drawables -{ - public class BeatmapBackgroundSprite : Sprite - { - private readonly WorkingBeatmap working; - - public BeatmapBackgroundSprite(WorkingBeatmap working) - { - if (working == null) - throw new ArgumentNullException(nameof(working)); - - this.working = working; - } - - [BackgroundDependencyLoader] - private void load() - { - if (working.Background != null) - Texture = working.Background; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Beatmaps.Drawables +{ + public class BeatmapBackgroundSprite : Sprite + { + private readonly WorkingBeatmap working; + + public BeatmapBackgroundSprite(WorkingBeatmap working) + { + if (working == null) + throw new ArgumentNullException(nameof(working)); + + this.working = working; + } + + [BackgroundDependencyLoader] + private void load() + { + if (working.Background != null) + Texture = working.Background; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index 90ccb27228..883c05f1e4 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -1,54 +1,54 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Beatmaps.Drawables -{ - public class BeatmapSetCover : Sprite - { - private readonly BeatmapSetInfo set; - private readonly BeatmapSetCoverType type; - - public BeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) - { - if (set == null) - throw new ArgumentNullException(nameof(set)); - - this.set = set; - this.type = type; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - string resource = null; - - switch (type) - { - case BeatmapSetCoverType.Cover: - resource = set.OnlineInfo.Covers.Cover; - break; - case BeatmapSetCoverType.Card: - resource = set.OnlineInfo.Covers.Card; - break; - case BeatmapSetCoverType.List: - resource = set.OnlineInfo.Covers.List; - break; - } - - if (resource != null) - Texture = textures.Get(resource); - } - } - - public enum BeatmapSetCoverType - { - Cover, - Card, - List, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Beatmaps.Drawables +{ + public class BeatmapSetCover : Sprite + { + private readonly BeatmapSetInfo set; + private readonly BeatmapSetCoverType type; + + public BeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) + { + if (set == null) + throw new ArgumentNullException(nameof(set)); + + this.set = set; + this.type = type; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + string resource = null; + + switch (type) + { + case BeatmapSetCoverType.Cover: + resource = set.OnlineInfo.Covers.Cover; + break; + case BeatmapSetCoverType.Card: + resource = set.OnlineInfo.Covers.Card; + break; + case BeatmapSetCoverType.List: + resource = set.OnlineInfo.Covers.List; + break; + } + + if (resource != null) + Texture = textures.Get(resource); + } + } + + public enum BeatmapSetCoverType + { + Cover, + Card, + List, + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs index 8ea7a538f9..24e6021421 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs @@ -1,54 +1,54 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; - -namespace osu.Game.Beatmaps.Drawables -{ - public class BeatmapSetOnlineStatusPill : CircularContainer - { - private readonly OsuSpriteText statusText; - - private BeatmapSetOnlineStatus status = BeatmapSetOnlineStatus.None; - public BeatmapSetOnlineStatus Status - { - get { return status; } - set - { - if (value == status) return; - status = value; - - statusText.Text = Enum.GetName(typeof(BeatmapSetOnlineStatus), Status)?.ToUpper(); - } - } - - public BeatmapSetOnlineStatusPill(float textSize, MarginPadding textPadding) - { - AutoSizeAxes = Axes.Both; - Masking = true; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, - }, - statusText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = @"Exo2.0-Bold", - TextSize = textSize, - Padding = textPadding, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Drawables +{ + public class BeatmapSetOnlineStatusPill : CircularContainer + { + private readonly OsuSpriteText statusText; + + private BeatmapSetOnlineStatus status = BeatmapSetOnlineStatus.None; + public BeatmapSetOnlineStatus Status + { + get { return status; } + set + { + if (value == status) return; + status = value; + + statusText.Text = Enum.GetName(typeof(BeatmapSetOnlineStatus), Status)?.ToUpper(); + } + } + + public BeatmapSetOnlineStatusPill(float textSize, MarginPadding textPadding) + { + AutoSizeAxes = Axes.Both; + Masking = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + statusText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = @"Exo2.0-Bold", + TextSize = textSize, + Padding = textPadding, + }, + }; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index 2fc39c71e6..025ab0037f 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -1,79 +1,79 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using OpenTK.Graphics; - -namespace osu.Game.Beatmaps.Drawables -{ - public class DifficultyColouredContainer : Container, IHasAccentColour - { - public Color4 AccentColour { get; set; } - - private readonly BeatmapInfo beatmap; - private OsuColour palette; - - public DifficultyColouredContainer(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load(OsuColour palette) - { - if (palette == null) - throw new ArgumentNullException(nameof(palette)); - - this.palette = palette; - AccentColour = getColour(beatmap); - } - - private enum DifficultyRating - { - Easy, - Normal, - Hard, - Insane, - Expert, - ExpertPlus - } - - private DifficultyRating getDifficultyRating(BeatmapInfo beatmap) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - var rating = beatmap.StarDifficulty; - - if (rating < 1.5) return DifficultyRating.Easy; - if (rating < 2.25) return DifficultyRating.Normal; - if (rating < 3.75) return DifficultyRating.Hard; - if (rating < 5.25) return DifficultyRating.Insane; - if (rating < 6.75) return DifficultyRating.Expert; - return DifficultyRating.ExpertPlus; - } - - private Color4 getColour(BeatmapInfo beatmap) - { - switch (getDifficultyRating(beatmap)) - { - case DifficultyRating.Easy: - return palette.Green; - default: - case DifficultyRating.Normal: - return palette.Blue; - case DifficultyRating.Hard: - return palette.Yellow; - case DifficultyRating.Insane: - return palette.Pink; - case DifficultyRating.Expert: - return palette.Purple; - case DifficultyRating.ExpertPlus: - return palette.Gray0; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Drawables +{ + public class DifficultyColouredContainer : Container, IHasAccentColour + { + public Color4 AccentColour { get; set; } + + private readonly BeatmapInfo beatmap; + private OsuColour palette; + + public DifficultyColouredContainer(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader] + private void load(OsuColour palette) + { + if (palette == null) + throw new ArgumentNullException(nameof(palette)); + + this.palette = palette; + AccentColour = getColour(beatmap); + } + + private enum DifficultyRating + { + Easy, + Normal, + Hard, + Insane, + Expert, + ExpertPlus + } + + private DifficultyRating getDifficultyRating(BeatmapInfo beatmap) + { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + + var rating = beatmap.StarDifficulty; + + if (rating < 1.5) return DifficultyRating.Easy; + if (rating < 2.25) return DifficultyRating.Normal; + if (rating < 3.75) return DifficultyRating.Hard; + if (rating < 5.25) return DifficultyRating.Insane; + if (rating < 6.75) return DifficultyRating.Expert; + return DifficultyRating.ExpertPlus; + } + + private Color4 getColour(BeatmapInfo beatmap) + { + switch (getDifficultyRating(beatmap)) + { + case DifficultyRating.Easy: + return palette.Green; + default: + case DifficultyRating.Normal: + return palette.Blue; + case DifficultyRating.Hard: + return palette.Yellow; + case DifficultyRating.Insane: + return palette.Pink; + case DifficultyRating.Expert: + return palette.Purple; + case DifficultyRating.ExpertPlus: + return palette.Gray0; + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index ace0541ea5..42c98aef24 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -1,48 +1,48 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Beatmaps.Drawables -{ - public class DifficultyIcon : DifficultyColouredContainer - { - private readonly BeatmapInfo beatmap; - - public DifficultyIcon(BeatmapInfo beatmap) : base(beatmap) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - this.beatmap = beatmap; - Size = new Vector2(20); - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = AccentColour, - Icon = FontAwesome.fa_circle - }, - new ConstrainedIconContainer - { - RelativeSizeAxes = Axes.Both, - // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = beatmap.Ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Beatmaps.Drawables +{ + public class DifficultyIcon : DifficultyColouredContainer + { + private readonly BeatmapInfo beatmap; + + public DifficultyIcon(BeatmapInfo beatmap) : base(beatmap) + { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + + this.beatmap = beatmap; + Size = new Vector2(20); + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = AccentColour, + Icon = FontAwesome.fa_circle + }, + new ConstrainedIconContainer + { + RelativeSizeAxes = Axes.Both, + // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) + Icon = beatmap.Ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } + } + }; + } + } +} diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index f4a3dacd53..0424ff84f1 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -1,74 +1,74 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Audio.Track; -using osu.Framework.Graphics.Textures; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Beatmaps -{ - public class DummyWorkingBeatmap : WorkingBeatmap - { - private readonly OsuGameBase game; - - public DummyWorkingBeatmap(OsuGameBase game) - : base(new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Artist = "please load a beatmap!", - Title = "no beatmaps available!" - }, - BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty - { - DrainRate = 0, - CircleSize = 0, - OverallDifficulty = 0, - ApproachRate = 0, - SliderMultiplier = 0, - SliderTickRate = 0, - }, - Ruleset = new DummyRulesetInfo() - }) - { - this.game = game; - } - - protected override Beatmap GetBeatmap() => new Beatmap(); - - protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4"); - - protected override Track GetTrack() => new TrackVirtual(); - - private class DummyRulesetInfo : RulesetInfo - { - public override Ruleset CreateInstance() => new DummyRuleset(this); - - private class DummyRuleset : Ruleset - { - public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) - { - throw new NotImplementedException(); - } - - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => null; - - public override string Description => "dummy"; - - public override string ShortName => "dummy"; - - public DummyRuleset(RulesetInfo rulesetInfo = null) - : base(rulesetInfo) - { - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Beatmaps +{ + public class DummyWorkingBeatmap : WorkingBeatmap + { + private readonly OsuGameBase game; + + public DummyWorkingBeatmap(OsuGameBase game) + : base(new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "please load a beatmap!", + Title = "no beatmaps available!" + }, + BeatmapSet = new BeatmapSetInfo(), + BaseDifficulty = new BeatmapDifficulty + { + DrainRate = 0, + CircleSize = 0, + OverallDifficulty = 0, + ApproachRate = 0, + SliderMultiplier = 0, + SliderTickRate = 0, + }, + Ruleset = new DummyRulesetInfo() + }) + { + this.game = game; + } + + protected override Beatmap GetBeatmap() => new Beatmap(); + + protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4"); + + protected override Track GetTrack() => new TrackVirtual(); + + private class DummyRulesetInfo : RulesetInfo + { + public override Ruleset CreateInstance() => new DummyRuleset(this); + + private class DummyRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; + + public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) + { + throw new NotImplementedException(); + } + + public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => null; + + public override string Description => "dummy"; + + public override string ShortName => "dummy"; + + public DummyRuleset(RulesetInfo rulesetInfo = null) + : base(rulesetInfo) + { + } + } + } + } +} diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index c07bedc8a6..bcd11f1fc8 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -1,80 +1,80 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace osu.Game.Beatmaps.Formats -{ - public abstract class Decoder : Decoder - where TOutput : new() - { - protected virtual TOutput CreateTemplateObject() => new TOutput(); - - public TOutput Decode(StreamReader primaryStream, params StreamReader[] otherStreams) - { - var output = CreateTemplateObject(); - foreach (StreamReader stream in otherStreams.Prepend(primaryStream)) - ParseStreamInto(stream, output); - return output; - } - - protected abstract void ParseStreamInto(StreamReader stream, TOutput beatmap); - } - - public abstract class Decoder - { - private static readonly Dictionary>> decoders = new Dictionary>>(); - - static Decoder() - { - LegacyBeatmapDecoder.Register(); - JsonBeatmapDecoder.Register(); - LegacyStoryboardDecoder.Register(); - } - - /// - /// Retrieves a to parse a . - /// - /// A stream pointing to the . - public static Decoder GetDecoder(StreamReader stream) - where T : new() - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) - throw new IOException(@"Unknown decoder type"); - - string line; - do - { - line = stream.ReadLine()?.Trim(); - } while (line != null && line.Length == 0); - - if (line == null) - throw new IOException(@"Unknown file format"); - - var decoder = typedDecoders.Select(d => line.StartsWith(d.Key) ? d.Value : null).FirstOrDefault(); - if (decoder == null) - throw new IOException(@"Unknown file format"); - - return (Decoder)decoder.Invoke(line); - } - - /// - /// Registers an instantiation function for a . - /// - /// A string in the file which triggers this decoder to be used. - /// A function which constructs the given . - protected static void AddDecoder(string magic, Func constructor) - { - if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) - decoders.Add(typeof(T), typedDecoders = new Dictionary>()); - - typedDecoders[magic] = constructor; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace osu.Game.Beatmaps.Formats +{ + public abstract class Decoder : Decoder + where TOutput : new() + { + protected virtual TOutput CreateTemplateObject() => new TOutput(); + + public TOutput Decode(StreamReader primaryStream, params StreamReader[] otherStreams) + { + var output = CreateTemplateObject(); + foreach (StreamReader stream in otherStreams.Prepend(primaryStream)) + ParseStreamInto(stream, output); + return output; + } + + protected abstract void ParseStreamInto(StreamReader stream, TOutput beatmap); + } + + public abstract class Decoder + { + private static readonly Dictionary>> decoders = new Dictionary>>(); + + static Decoder() + { + LegacyBeatmapDecoder.Register(); + JsonBeatmapDecoder.Register(); + LegacyStoryboardDecoder.Register(); + } + + /// + /// Retrieves a to parse a . + /// + /// A stream pointing to the . + public static Decoder GetDecoder(StreamReader stream) + where T : new() + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) + throw new IOException(@"Unknown decoder type"); + + string line; + do + { + line = stream.ReadLine()?.Trim(); + } while (line != null && line.Length == 0); + + if (line == null) + throw new IOException(@"Unknown file format"); + + var decoder = typedDecoders.Select(d => line.StartsWith(d.Key) ? d.Value : null).FirstOrDefault(); + if (decoder == null) + throw new IOException(@"Unknown file format"); + + return (Decoder)decoder.Invoke(line); + } + + /// + /// Registers an instantiation function for a . + /// + /// A string in the file which triggers this decoder to be used. + /// A function which constructs the given . + protected static void AddDecoder(string magic, Func constructor) + { + if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) + decoders.Add(typeof(T), typedDecoders = new Dictionary>()); + + typedDecoders[magic] = constructor; + } + } +} diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs index 93c6c18eec..d3377c2717 100644 --- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK.Graphics; - -namespace osu.Game.Beatmaps.Formats -{ - public interface IHasComboColours - { - List ComboColours { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Formats +{ + public interface IHasComboColours + { + List ComboColours { get; set; } + } +} diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs index 14614a6728..32390ac484 100644 --- a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK.Graphics; - -namespace osu.Game.Beatmaps.Formats -{ - public interface IHasCustomColours - { - Dictionary CustomColours { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Formats +{ + public interface IHasCustomColours + { + Dictionary CustomColours { get; set; } + } +} diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index add0f39280..e5574cd82e 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.IO; -using osu.Game.IO.Serialization; - -namespace osu.Game.Beatmaps.Formats -{ - public class JsonBeatmapDecoder : Decoder - { - public static void Register() - { - AddDecoder("{", m => new JsonBeatmapDecoder()); - } - - protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) - { - stream.BaseStream.Position = 0; - stream.DiscardBufferedData(); - - stream.ReadToEnd().DeserializeInto(beatmap); - - foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using osu.Game.IO.Serialization; + +namespace osu.Game.Beatmaps.Formats +{ + public class JsonBeatmapDecoder : Decoder + { + public static void Register() + { + AddDecoder("{", m => new JsonBeatmapDecoder()); + } + + protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) + { + stream.BaseStream.Position = 0; + stream.DiscardBufferedData(); + + stream.ReadToEnd().DeserializeInto(beatmap); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 52f1a01fcb..366b4f163f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -1,389 +1,389 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Globalization; -using System.IO; -using System.Linq; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Beatmaps.ControlPoints; -using osu.Framework; - -namespace osu.Game.Beatmaps.Formats -{ - public class LegacyBeatmapDecoder : LegacyDecoder - { - public const int LATEST_VERSION = 14; - - private Beatmap beatmap; - - private ConvertHitObjectParser parser; - - private LegacySampleBank defaultSampleBank; - private int defaultSampleVolume = 100; - - public static void Register() - { - AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); - } - - /// - /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited. - /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - /// - public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0; - - /// - /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. - /// - public bool ApplyOffsets = true; - - private readonly int offset = UniversalOffset; - - public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) - { - // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) - offset += FormatVersion < 5 ? 24 : 0; - } - - protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) - { - this.beatmap = beatmap; - this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; - - base.ParseStreamInto(stream, beatmap); - - // objects may be out of order *only* if a user has manually edited an .osu file. - // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). - this.beatmap.HitObjects.Sort((x, y) => x.StartTime.CompareTo(y.StartTime)); - - foreach (var hitObject in this.beatmap.HitObjects) - hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); - } - - protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"); - - protected override void ParseLine(Beatmap beatmap, Section section, string line) - { - switch (section) - { - case Section.General: - handleGeneral(line); - return; - case Section.Editor: - handleEditor(line); - return; - case Section.Metadata: - handleMetadata(line); - return; - case Section.Difficulty: - handleDifficulty(line); - return; - case Section.Events: - handleEvent(line); - return; - case Section.TimingPoints: - handleTimingPoint(line); - return; - case Section.HitObjects: - handleHitObject(line); - return; - } - - base.ParseLine(beatmap, section, line); - } - - private void handleGeneral(string line) - { - var pair = SplitKeyVal(line); - - var metadata = beatmap.BeatmapInfo.Metadata; - switch (pair.Key) - { - case @"AudioFilename": - metadata.AudioFile = pair.Value; - break; - case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); - break; - case @"PreviewTime": - metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value)); - break; - case @"Countdown": - beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; - break; - case @"SampleSet": - defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); - break; - case @"SampleVolume": - defaultSampleVolume = int.Parse(pair.Value); - break; - case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); - - switch (beatmap.BeatmapInfo.RulesetID) - { - case 0: - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); - break; - case 1: - parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(); - break; - case 2: - parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(); - break; - case 3: - parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(); - break; - } - break; - case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; - break; - case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; - break; - case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; - break; - } - } - - private void handleEditor(string line) - { - var pair = SplitKeyVal(line); - - switch (pair.Key) - { - case @"Bookmarks": - beatmap.BeatmapInfo.StoredBookmarks = pair.Value; - break; - case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); - break; - case @"GridSize": - beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); - break; - case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - } - } - - private void handleMetadata(string line) - { - var pair = SplitKeyVal(line); - - var metadata = beatmap.BeatmapInfo.Metadata; - switch (pair.Key) - { - case @"Title": - metadata.Title = pair.Value; - break; - case @"TitleUnicode": - metadata.TitleUnicode = pair.Value; - break; - case @"Artist": - metadata.Artist = pair.Value; - break; - case @"ArtistUnicode": - metadata.ArtistUnicode = pair.Value; - break; - case @"Creator": - metadata.AuthorString = pair.Value; - break; - case @"Version": - beatmap.BeatmapInfo.Version = pair.Value; - break; - case @"Source": - beatmap.BeatmapInfo.Metadata.Source = pair.Value; - break; - case @"Tags": - beatmap.BeatmapInfo.Metadata.Tags = pair.Value; - break; - case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); - break; - case @"BeatmapSetID": - beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); - metadata.OnlineBeatmapSetID = int.Parse(pair.Value); - break; - } - } - - private void handleDifficulty(string line) - { - var pair = SplitKeyVal(line); - - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; - switch (pair.Key) - { - case @"HPDrainRate": - difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"CircleSize": - difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"OverallDifficulty": - difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"ApproachRate": - difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"SliderMultiplier": - difficulty.SliderMultiplier = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - case @"SliderTickRate": - difficulty.SliderTickRate = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); - break; - } - } - - private void handleEvent(string line) - { - string[] split = line.Split(','); - - EventType type; - if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); - - switch (type) - { - case EventType.Background: - string filename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; - break; - case EventType.Break: - var breakEvent = new BreakPeriod - { - StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)), - EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo)) - }; - - if (!breakEvent.HasEffect) - return; - - beatmap.Breaks.Add(breakEvent); - break; - } - } - - private void handleTimingPoint(string line) - { - try - { - string[] split = line.Split(','); - - double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo)); - double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); - double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; - - TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; - if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); - - LegacySampleBank sampleSet = defaultSampleBank; - if (split.Length >= 4) - sampleSet = (LegacySampleBank)int.Parse(split[3]); - - //SampleBank sampleBank = SampleBank.Default; - //if (split.Length >= 5) - // sampleBank = (SampleBank)int.Parse(split[4]); - - int sampleVolume = defaultSampleVolume; - if (split.Length >= 6) - sampleVolume = int.Parse(split[5]); - - bool timingChange = true; - if (split.Length >= 7) - timingChange = split[6][0] == '1'; - - bool kiaiMode = false; - bool omitFirstBarSignature = false; - if (split.Length >= 8) - { - int effectFlags = int.Parse(split[7]); - kiaiMode = (effectFlags & 1) > 0; - omitFirstBarSignature = (effectFlags & 8) > 0; - } - - string stringSampleSet = sampleSet.ToString().ToLower(); - if (stringSampleSet == @"none") - stringSampleSet = @"normal"; - - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time); - SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time); - EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); - - if (timingChange) - { - beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint - { - Time = time, - BeatLength = beatLength, - TimeSignature = timeSignature - }); - } - - if (speedMultiplier != difficultyPoint.SpeedMultiplier) - { - beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); - beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint - { - Time = time, - SpeedMultiplier = speedMultiplier - }); - } - - if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume) - { - beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint - { - Time = time, - SampleBank = stringSampleSet, - SampleVolume = sampleVolume - }); - } - - if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) - { - beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint - { - Time = time, - KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature - }); - } - } - catch (FormatException e) - { - } - } - - private void handleHitObject(string line) - { - // If the ruleset wasn't specified, assume the osu!standard ruleset. - if (parser == null) - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); - - var obj = parser.Parse(line); - - if (obj != null) - { - obj.StartTime = getOffsetTime(obj.StartTime); - beatmap.HitObjects.Add(obj); - } - } - - private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); - - private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Beatmaps.ControlPoints; +using osu.Framework; + +namespace osu.Game.Beatmaps.Formats +{ + public class LegacyBeatmapDecoder : LegacyDecoder + { + public const int LATEST_VERSION = 14; + + private Beatmap beatmap; + + private ConvertHitObjectParser parser; + + private LegacySampleBank defaultSampleBank; + private int defaultSampleVolume = 100; + + public static void Register() + { + AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); + } + + /// + /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited. + /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + /// + public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0; + + /// + /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. + /// + public bool ApplyOffsets = true; + + private readonly int offset = UniversalOffset; + + public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) + { + // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) + offset += FormatVersion < 5 ? 24 : 0; + } + + protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) + { + this.beatmap = beatmap; + this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + + base.ParseStreamInto(stream, beatmap); + + // objects may be out of order *only* if a user has manually edited an .osu file. + // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). + this.beatmap.HitObjects.Sort((x, y) => x.StartTime.CompareTo(y.StartTime)); + + foreach (var hitObject in this.beatmap.HitObjects) + hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); + } + + protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"); + + protected override void ParseLine(Beatmap beatmap, Section section, string line) + { + switch (section) + { + case Section.General: + handleGeneral(line); + return; + case Section.Editor: + handleEditor(line); + return; + case Section.Metadata: + handleMetadata(line); + return; + case Section.Difficulty: + handleDifficulty(line); + return; + case Section.Events: + handleEvent(line); + return; + case Section.TimingPoints: + handleTimingPoint(line); + return; + case Section.HitObjects: + handleHitObject(line); + return; + } + + base.ParseLine(beatmap, section, line); + } + + private void handleGeneral(string line) + { + var pair = SplitKeyVal(line); + + var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) + { + case @"AudioFilename": + metadata.AudioFile = pair.Value; + break; + case @"AudioLeadIn": + beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); + break; + case @"PreviewTime": + metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value)); + break; + case @"Countdown": + beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; + break; + case @"SampleSet": + defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); + break; + case @"SampleVolume": + defaultSampleVolume = int.Parse(pair.Value); + break; + case @"StackLeniency": + beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"Mode": + beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); + + switch (beatmap.BeatmapInfo.RulesetID) + { + case 0: + parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); + break; + case 1: + parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(); + break; + case 2: + parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(); + break; + case 3: + parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(); + break; + } + break; + case @"LetterboxInBreaks": + beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; + break; + case @"SpecialStyle": + beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; + break; + case @"WidescreenStoryboard": + beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; + break; + } + } + + private void handleEditor(string line) + { + var pair = SplitKeyVal(line); + + switch (pair.Key) + { + case @"Bookmarks": + beatmap.BeatmapInfo.StoredBookmarks = pair.Value; + break; + case @"DistanceSpacing": + beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"BeatDivisor": + beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); + break; + case @"GridSize": + beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); + break; + case @"TimelineZoom": + beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + } + } + + private void handleMetadata(string line) + { + var pair = SplitKeyVal(line); + + var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) + { + case @"Title": + metadata.Title = pair.Value; + break; + case @"TitleUnicode": + metadata.TitleUnicode = pair.Value; + break; + case @"Artist": + metadata.Artist = pair.Value; + break; + case @"ArtistUnicode": + metadata.ArtistUnicode = pair.Value; + break; + case @"Creator": + metadata.AuthorString = pair.Value; + break; + case @"Version": + beatmap.BeatmapInfo.Version = pair.Value; + break; + case @"Source": + beatmap.BeatmapInfo.Metadata.Source = pair.Value; + break; + case @"Tags": + beatmap.BeatmapInfo.Metadata.Tags = pair.Value; + break; + case @"BeatmapID": + beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); + break; + case @"BeatmapSetID": + beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); + metadata.OnlineBeatmapSetID = int.Parse(pair.Value); + break; + } + } + + private void handleDifficulty(string line) + { + var pair = SplitKeyVal(line); + + var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + switch (pair.Key) + { + case @"HPDrainRate": + difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"CircleSize": + difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"OverallDifficulty": + difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"ApproachRate": + difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"SliderMultiplier": + difficulty.SliderMultiplier = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + case @"SliderTickRate": + difficulty.SliderTickRate = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + break; + } + } + + private void handleEvent(string line) + { + string[] split = line.Split(','); + + EventType type; + if (!Enum.TryParse(split[0], out type)) + throw new InvalidDataException($@"Unknown event type {split[0]}"); + + switch (type) + { + case EventType.Background: + string filename = split[2].Trim('"'); + beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + break; + case EventType.Break: + var breakEvent = new BreakPeriod + { + StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)), + EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo)) + }; + + if (!breakEvent.HasEffect) + return; + + beatmap.Breaks.Add(breakEvent); + break; + } + } + + private void handleTimingPoint(string line) + { + try + { + string[] split = line.Split(','); + + double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo)); + double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; + + TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + if (split.Length >= 3) + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); + + LegacySampleBank sampleSet = defaultSampleBank; + if (split.Length >= 4) + sampleSet = (LegacySampleBank)int.Parse(split[3]); + + //SampleBank sampleBank = SampleBank.Default; + //if (split.Length >= 5) + // sampleBank = (SampleBank)int.Parse(split[4]); + + int sampleVolume = defaultSampleVolume; + if (split.Length >= 6) + sampleVolume = int.Parse(split[5]); + + bool timingChange = true; + if (split.Length >= 7) + timingChange = split[6][0] == '1'; + + bool kiaiMode = false; + bool omitFirstBarSignature = false; + if (split.Length >= 8) + { + int effectFlags = int.Parse(split[7]); + kiaiMode = (effectFlags & 1) > 0; + omitFirstBarSignature = (effectFlags & 8) > 0; + } + + string stringSampleSet = sampleSet.ToString().ToLower(); + if (stringSampleSet == @"none") + stringSampleSet = @"normal"; + + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time); + SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time); + EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); + + if (timingChange) + { + beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint + { + Time = time, + BeatLength = beatLength, + TimeSignature = timeSignature + }); + } + + if (speedMultiplier != difficultyPoint.SpeedMultiplier) + { + beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); + beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint + { + Time = time, + SpeedMultiplier = speedMultiplier + }); + } + + if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume) + { + beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint + { + Time = time, + SampleBank = stringSampleSet, + SampleVolume = sampleVolume + }); + } + + if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) + { + beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint + { + Time = time, + KiaiMode = kiaiMode, + OmitFirstBarLine = omitFirstBarSignature + }); + } + } + catch (FormatException e) + { + } + } + + private void handleHitObject(string line) + { + // If the ruleset wasn't specified, assume the osu!standard ruleset. + if (parser == null) + parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); + + var obj = parser.Parse(line); + + if (obj != null) + { + obj.StartTime = getOffsetTime(obj.StartTime); + beatmap.HitObjects.Add(obj); + } + } + + private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); + + private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0); + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 131c010c5c..06160a87e0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -1,164 +1,164 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using osu.Framework.Logging; -using OpenTK.Graphics; - -namespace osu.Game.Beatmaps.Formats -{ - public abstract class LegacyDecoder : Decoder - where T : new() - { - protected readonly int FormatVersion; - - protected LegacyDecoder(int version) - { - FormatVersion = version; - } - - protected override void ParseStreamInto(StreamReader stream, T beatmap) - { - Section section = Section.None; - - string line; - while ((line = stream.ReadLine()) != null) - { - if (ShouldSkipLine(line)) - continue; - - if (line.StartsWith(@"[") && line.EndsWith(@"]")) - { - if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) - { - Logger.Log($"Unknown section \"{line}\" in {beatmap}"); - section = Section.None; - } - - continue; - } - - ParseLine(beatmap, section, line); - } - } - - protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//"); - - protected virtual void ParseLine(T output, Section section, string line) - { - switch (section) - { - case Section.Colours: - handleColours(output, line); - return; - } - } - - private bool hasComboColours; - - private void handleColours(T output, string line) - { - var pair = SplitKeyVal(line); - - bool isCombo = pair.Key.StartsWith(@"Combo"); - - string[] split = pair.Value.Split(','); - - if (split.Length != 3) - throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); - - if (!byte.TryParse(split[0], out var r) || !byte.TryParse(split[1], out var g) || !byte.TryParse(split[2], out var b)) - throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); - - Color4 colour = new Color4(r, g, b, 255); - - if (isCombo) - { - if (!(output is IHasComboColours tHasComboColours)) return; - - if (!hasComboColours) - { - // remove default colours. - tHasComboColours.ComboColours.Clear(); - hasComboColours = true; - } - - tHasComboColours.ComboColours.Add(colour); - } - else - { - if (!(output is IHasCustomColours tHasCustomColours)) return; - tHasCustomColours.CustomColours[pair.Key] = colour; - } - } - - protected KeyValuePair SplitKeyVal(string line, char separator = ':') - { - var split = line.Trim().Split(new[] { separator }, 2); - - return new KeyValuePair - ( - split[0].Trim(), - split.Length > 1 ? split[1].Trim() : string.Empty - ); - } - - protected enum Section - { - None, - General, - Editor, - Metadata, - Difficulty, - Events, - TimingPoints, - Colours, - HitObjects, - Variables, - Fonts - } - - internal enum LegacySampleBank - { - None = 0, - Normal = 1, - Soft = 2, - Drum = 3 - } - - internal enum EventType - { - Background = 0, - Video = 1, - Break = 2, - Colour = 3, - Sprite = 4, - Sample = 5, - Animation = 6 - } - - internal enum LegacyOrigins - { - TopLeft, - Centre, - CentreLeft, - TopRight, - BottomCentre, - TopCentre, - Custom, - CentreRight, - BottomLeft, - BottomRight - } - - internal enum StoryLayer - { - Background = 0, - Fail = 1, - Pass = 2, - Foreground = 3 - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using osu.Framework.Logging; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Formats +{ + public abstract class LegacyDecoder : Decoder + where T : new() + { + protected readonly int FormatVersion; + + protected LegacyDecoder(int version) + { + FormatVersion = version; + } + + protected override void ParseStreamInto(StreamReader stream, T beatmap) + { + Section section = Section.None; + + string line; + while ((line = stream.ReadLine()) != null) + { + if (ShouldSkipLine(line)) + continue; + + if (line.StartsWith(@"[") && line.EndsWith(@"]")) + { + if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) + { + Logger.Log($"Unknown section \"{line}\" in {beatmap}"); + section = Section.None; + } + + continue; + } + + ParseLine(beatmap, section, line); + } + } + + protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//"); + + protected virtual void ParseLine(T output, Section section, string line) + { + switch (section) + { + case Section.Colours: + handleColours(output, line); + return; + } + } + + private bool hasComboColours; + + private void handleColours(T output, string line) + { + var pair = SplitKeyVal(line); + + bool isCombo = pair.Key.StartsWith(@"Combo"); + + string[] split = pair.Value.Split(','); + + if (split.Length != 3) + throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); + + if (!byte.TryParse(split[0], out var r) || !byte.TryParse(split[1], out var g) || !byte.TryParse(split[2], out var b)) + throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); + + Color4 colour = new Color4(r, g, b, 255); + + if (isCombo) + { + if (!(output is IHasComboColours tHasComboColours)) return; + + if (!hasComboColours) + { + // remove default colours. + tHasComboColours.ComboColours.Clear(); + hasComboColours = true; + } + + tHasComboColours.ComboColours.Add(colour); + } + else + { + if (!(output is IHasCustomColours tHasCustomColours)) return; + tHasCustomColours.CustomColours[pair.Key] = colour; + } + } + + protected KeyValuePair SplitKeyVal(string line, char separator = ':') + { + var split = line.Trim().Split(new[] { separator }, 2); + + return new KeyValuePair + ( + split[0].Trim(), + split.Length > 1 ? split[1].Trim() : string.Empty + ); + } + + protected enum Section + { + None, + General, + Editor, + Metadata, + Difficulty, + Events, + TimingPoints, + Colours, + HitObjects, + Variables, + Fonts + } + + internal enum LegacySampleBank + { + None = 0, + Normal = 1, + Soft = 2, + Drum = 3 + } + + internal enum EventType + { + Background = 0, + Video = 1, + Break = 2, + Colour = 3, + Sprite = 4, + Sample = 5, + Animation = 6 + } + + internal enum LegacyOrigins + { + TopLeft, + Centre, + CentreLeft, + TopRight, + BottomCentre, + TopCentre, + Custom, + CentreRight, + BottomLeft, + BottomRight + } + + internal enum StoryLayer + { + Background = 0, + Fail = 1, + Pass = 2, + Foreground = 3 + } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 85b0f8d42e..868ae5206a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -1,306 +1,306 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.IO.File; -using osu.Game.Storyboards; - -namespace osu.Game.Beatmaps.Formats -{ - public class LegacyStoryboardDecoder : LegacyDecoder - { - private StoryboardSprite storyboardSprite; - private CommandTimelineGroup timelineGroup; - - private Storyboard storyboard; - - private readonly Dictionary variables = new Dictionary(); - - public LegacyStoryboardDecoder() - : base(0) - { - } - - public static void Register() - { - // note that this isn't completely correct - AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder()); - AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); - } - - protected override void ParseStreamInto(StreamReader stream, Storyboard storyboard) - { - this.storyboard = storyboard; - base.ParseStreamInto(stream, storyboard); - } - - protected override void ParseLine(Storyboard storyboard, Section section, string line) - { - switch (section) - { - case Section.Events: - handleEvents(line); - return; - case Section.Variables: - handleVariables(line); - return; - } - - base.ParseLine(storyboard, section, line); - } - - private void handleEvents(string line) - { - var depth = 0; - while (line.StartsWith(" ") || line.StartsWith("_")) - { - ++depth; - line = line.Substring(1); - } - - decodeVariables(ref line); - - string[] split = line.Split(','); - - if (depth == 0) - { - storyboardSprite = null; - - EventType type; - if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); - - switch (type) - { - case EventType.Sprite: - { - var layer = parseLayer(split[1]); - var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); - var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); - var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); - storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); - storyboard.GetLayer(layer).Add(storyboardSprite); - } - break; - case EventType.Animation: - { - var layer = parseLayer(split[1]); - var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); - var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); - var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); - var frameCount = int.Parse(split[6]); - var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo); - var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; - storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); - storyboard.GetLayer(layer).Add(storyboardSprite); - } - break; - case EventType.Sample: - { - var time = double.Parse(split[1], CultureInfo.InvariantCulture); - var layer = parseLayer(split[2]); - var path = cleanFilename(split[3]); - var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; - storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } - break; - } - } - else - { - if (depth < 2) - timelineGroup = storyboardSprite?.TimelineGroup; - - var commandType = split[0]; - switch (commandType) - { - case "T": - { - var triggerName = split[1]; - var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; - var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; - var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; - timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); - } - break; - case "L": - { - var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); - var loopCount = int.Parse(split[2]); - timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); - } - break; - default: - { - if (string.IsNullOrEmpty(split[3])) - split[3] = split[2]; - - var easing = (Easing)int.Parse(split[1]); - var startTime = double.Parse(split[2], CultureInfo.InvariantCulture); - var endTime = double.Parse(split[3], CultureInfo.InvariantCulture); - - switch (commandType) - { - case "F": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "S": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); - } - break; - case "V": - { - var startX = float.Parse(split[4], CultureInfo.InvariantCulture); - var startY = float.Parse(split[5], CultureInfo.InvariantCulture); - var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; - var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; - timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); - } - break; - case "R": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); - } - break; - case "M": - { - var startX = float.Parse(split[4], CultureInfo.InvariantCulture); - var startY = float.Parse(split[5], CultureInfo.InvariantCulture); - var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; - var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; - timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); - timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); - } - break; - case "MX": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "MY": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "C": - { - var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); - var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture); - var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture); - var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed; - var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen; - var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue; - timelineGroup?.Colour.Add(easing, startTime, endTime, - new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), - new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); - } - break; - case "P": - { - var type = split[4]; - switch (type) - { - case "A": - timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); - break; - case "H": - timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); - break; - case "V": - timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); - break; - } - } - break; - default: - throw new InvalidDataException($@"Unknown command type: {commandType}"); - } - } - break; - } - } - } - - private string parseLayer(string value) => Enum.Parse(typeof(StoryLayer), value).ToString(); - - private Anchor parseOrigin(string value) - { - var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); - switch (origin) - { - case LegacyOrigins.TopLeft: - return Anchor.TopLeft; - case LegacyOrigins.TopCentre: - return Anchor.TopCentre; - case LegacyOrigins.TopRight: - return Anchor.TopRight; - case LegacyOrigins.CentreLeft: - return Anchor.CentreLeft; - case LegacyOrigins.Centre: - return Anchor.Centre; - case LegacyOrigins.CentreRight: - return Anchor.CentreRight; - case LegacyOrigins.BottomLeft: - return Anchor.BottomLeft; - case LegacyOrigins.BottomCentre: - return Anchor.BottomCentre; - case LegacyOrigins.BottomRight: - return Anchor.BottomRight; - } - - throw new InvalidDataException($@"Unknown origin: {value}"); - } - - private void handleVariables(string line) - { - var pair = SplitKeyVal(line, '='); - variables[pair.Key] = pair.Value; - } - - /// - /// Decodes any beatmap variables present in a line into their real values. - /// - /// The line which may contains variables. - private void decodeVariables(ref string line) - { - while (line.IndexOf('$') >= 0) - { - string origLine = line; - string[] split = line.Split(','); - for (int i = 0; i < split.Length; i++) - { - var item = split[i]; - if (item.StartsWith("$") && variables.ContainsKey(item)) - split[i] = variables[item]; - } - - line = string.Join(",", split); - if (line == origLine) - break; - } - } - - private string cleanFilename(string path) => FileSafety.PathStandardise(FileSafety.PathSanitise(path.Trim('\"'))); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.IO.File; +using osu.Game.Storyboards; + +namespace osu.Game.Beatmaps.Formats +{ + public class LegacyStoryboardDecoder : LegacyDecoder + { + private StoryboardSprite storyboardSprite; + private CommandTimelineGroup timelineGroup; + + private Storyboard storyboard; + + private readonly Dictionary variables = new Dictionary(); + + public LegacyStoryboardDecoder() + : base(0) + { + } + + public static void Register() + { + // note that this isn't completely correct + AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder()); + AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); + } + + protected override void ParseStreamInto(StreamReader stream, Storyboard storyboard) + { + this.storyboard = storyboard; + base.ParseStreamInto(stream, storyboard); + } + + protected override void ParseLine(Storyboard storyboard, Section section, string line) + { + switch (section) + { + case Section.Events: + handleEvents(line); + return; + case Section.Variables: + handleVariables(line); + return; + } + + base.ParseLine(storyboard, section, line); + } + + private void handleEvents(string line) + { + var depth = 0; + while (line.StartsWith(" ") || line.StartsWith("_")) + { + ++depth; + line = line.Substring(1); + } + + decodeVariables(ref line); + + string[] split = line.Split(','); + + if (depth == 0) + { + storyboardSprite = null; + + EventType type; + if (!Enum.TryParse(split[0], out type)) + throw new InvalidDataException($@"Unknown event type {split[0]}"); + + switch (type) + { + case EventType.Sprite: + { + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); + var path = cleanFilename(split[3]); + var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); + var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); + storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); + storyboard.GetLayer(layer).Add(storyboardSprite); + } + break; + case EventType.Animation: + { + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); + var path = cleanFilename(split[3]); + var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); + var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); + var frameCount = int.Parse(split[6]); + var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo); + var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; + storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); + storyboard.GetLayer(layer).Add(storyboardSprite); + } + break; + case EventType.Sample: + { + var time = double.Parse(split[1], CultureInfo.InvariantCulture); + var layer = parseLayer(split[2]); + var path = cleanFilename(split[3]); + var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; + storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); + } + break; + } + } + else + { + if (depth < 2) + timelineGroup = storyboardSprite?.TimelineGroup; + + var commandType = split[0]; + switch (commandType) + { + case "T": + { + var triggerName = split[1]; + var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; + var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; + var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; + timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); + } + break; + case "L": + { + var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); + var loopCount = int.Parse(split[2]); + timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); + } + break; + default: + { + if (string.IsNullOrEmpty(split[3])) + split[3] = split[2]; + + var easing = (Easing)int.Parse(split[1]); + var startTime = double.Parse(split[2], CultureInfo.InvariantCulture); + var endTime = double.Parse(split[3], CultureInfo.InvariantCulture); + + switch (commandType) + { + case "F": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); + } + break; + case "S": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); + } + break; + case "V": + { + var startX = float.Parse(split[4], CultureInfo.InvariantCulture); + var startY = float.Parse(split[5], CultureInfo.InvariantCulture); + var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; + var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); + } + break; + case "R": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); + } + break; + case "M": + { + var startX = float.Parse(split[4], CultureInfo.InvariantCulture); + var startY = float.Parse(split[5], CultureInfo.InvariantCulture); + var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; + var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; + timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); + timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); + } + break; + case "MX": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); + } + break; + case "MY": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); + } + break; + case "C": + { + var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); + var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture); + var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture); + var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed; + var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen; + var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue; + timelineGroup?.Colour.Add(easing, startTime, endTime, + new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), + new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); + } + break; + case "P": + { + var type = split[4]; + switch (type) + { + case "A": + timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); + break; + case "H": + timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); + break; + case "V": + timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); + break; + } + } + break; + default: + throw new InvalidDataException($@"Unknown command type: {commandType}"); + } + } + break; + } + } + } + + private string parseLayer(string value) => Enum.Parse(typeof(StoryLayer), value).ToString(); + + private Anchor parseOrigin(string value) + { + var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + switch (origin) + { + case LegacyOrigins.TopLeft: + return Anchor.TopLeft; + case LegacyOrigins.TopCentre: + return Anchor.TopCentre; + case LegacyOrigins.TopRight: + return Anchor.TopRight; + case LegacyOrigins.CentreLeft: + return Anchor.CentreLeft; + case LegacyOrigins.Centre: + return Anchor.Centre; + case LegacyOrigins.CentreRight: + return Anchor.CentreRight; + case LegacyOrigins.BottomLeft: + return Anchor.BottomLeft; + case LegacyOrigins.BottomCentre: + return Anchor.BottomCentre; + case LegacyOrigins.BottomRight: + return Anchor.BottomRight; + } + + throw new InvalidDataException($@"Unknown origin: {value}"); + } + + private void handleVariables(string line) + { + var pair = SplitKeyVal(line, '='); + variables[pair.Key] = pair.Value; + } + + /// + /// Decodes any beatmap variables present in a line into their real values. + /// + /// The line which may contains variables. + private void decodeVariables(ref string line) + { + while (line.IndexOf('$') >= 0) + { + string origLine = line; + string[] split = line.Split(','); + for (int i = 0; i < split.Length; i++) + { + var item = split[i]; + if (item.StartsWith("$") && variables.ContainsKey(item)) + split[i] = variables[item]; + } + + line = string.Join(",", split); + if (line == origLine) + break; + } + } + + private string cleanFilename(string path) => FileSafety.PathStandardise(FileSafety.PathSanitise(path.Trim('\"'))); + } +} diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs index 096ba345a1..6c25395a56 100644 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ b/osu.Game/Beatmaps/IBeatmapConverter.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Beatmaps -{ - public interface IBeatmapConverter - { - /// - /// Invoked when a has been converted. - /// The first argument contains the that was converted. - /// The second argument contains the s that were output from the conversion process. - /// - event Action> ObjectConverted; - - /// - /// Converts a Beatmap using this Beatmap Converter. - /// - /// The un-converted Beatmap. - void Convert(Beatmap beatmap); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Beatmaps +{ + public interface IBeatmapConverter + { + /// + /// Invoked when a has been converted. + /// The first argument contains the that was converted. + /// The second argument contains the s that were output from the conversion process. + /// + event Action> ObjectConverted; + + /// + /// Converts a Beatmap using this Beatmap Converter. + /// + /// The un-converted Beatmap. + void Convert(Beatmap beatmap); + } +} diff --git a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs b/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs index 2a79c23e0b..eea82dac6d 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.Legacy -{ - /// - /// A type of Beatmap loaded from a legacy .osu beatmap file (version <=15). - /// - public class LegacyBeatmap : Beatmap - { - /// - /// Constructs a new beatmap. - /// - /// The original beatmap to use the parameters of. - internal LegacyBeatmap(Beatmap original = null) - : base(original) - { - HitObjects = original?.HitObjects; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps.Legacy +{ + /// + /// A type of Beatmap loaded from a legacy .osu beatmap file (version <=15). + /// + public class LegacyBeatmap : Beatmap + { + /// + /// Constructs a new beatmap. + /// + /// The original beatmap to use the parameters of. + internal LegacyBeatmap(Beatmap original = null) + : base(original) + { + HitObjects = original?.HitObjects; + } + } +} diff --git a/osu.Game/Beatmaps/RankStatus.cs b/osu.Game/Beatmaps/RankStatus.cs index 5ca358ddef..dce4f494f1 100644 --- a/osu.Game/Beatmaps/RankStatus.cs +++ b/osu.Game/Beatmaps/RankStatus.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Beatmaps -{ - public enum RankStatus - { - Any = 7, - [Description("Ranked & Approved")] - RankedApproved = 0, - Approved = 1, - Loved = 8, - Favourites = 2, - [Description("Mod Requests")] - ModRequests = 3, - Pending = 4, - Graveyard = 5, - [Description("My Maps")] - MyMaps = 6, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Beatmaps +{ + public enum RankStatus + { + Any = 7, + [Description("Ranked & Approved")] + RankedApproved = 0, + Approved = 1, + Loved = 8, + Favourites = 2, + [Description("Mod Requests")] + ModRequests = 3, + Pending = 4, + Graveyard = 5, + [Description("My Maps")] + MyMaps = 6, + } +} diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 7345f0df82..0524704b8c 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.Timing -{ - public class BreakPeriod - { - /// - /// The minimum duration required for a break to have any effect. - /// - public const double MIN_BREAK_DURATION = 650; - - /// - /// The break start time. - /// - public double StartTime; - - /// - /// The break end time. - /// - public double EndTime; - - /// - /// The break duration. - /// - public double Duration => EndTime - StartTime; - - /// - /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. - /// - public bool HasEffect => Duration >= MIN_BREAK_DURATION; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps.Timing +{ + public class BreakPeriod + { + /// + /// The minimum duration required for a break to have any effect. + /// + public const double MIN_BREAK_DURATION = 650; + + /// + /// The break start time. + /// + public double StartTime; + + /// + /// The break end time. + /// + public double EndTime; + + /// + /// The break duration. + /// + public double Duration => EndTime - StartTime; + + /// + /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. + /// + public bool HasEffect => Duration >= MIN_BREAK_DURATION; + } +} diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs index 6b7d9e7e5a..aa25c76fc5 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignatures.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.Timing -{ - public enum TimeSignatures - { - SimpleQuadruple = 4, - SimpleTriple = 3 - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps.Timing +{ + public enum TimeSignatures + { + SimpleQuadruple = 4, + SimpleTriple = 3 + } +} diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 5c0ad7685b..4080e34e81 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -1,216 +1,216 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Audio.Track; -using osu.Framework.Configuration; -using osu.Framework.Graphics.Textures; -using osu.Game.Rulesets.Mods; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using osu.Game.Storyboards; -using osu.Framework.IO.File; -using System.IO; -using osu.Game.IO.Serialization; -using System.Diagnostics; -using osu.Game.Skinning; - -namespace osu.Game.Beatmaps -{ - public abstract class WorkingBeatmap : IDisposable - { - public readonly BeatmapInfo BeatmapInfo; - - public readonly BeatmapSetInfo BeatmapSetInfo; - - public readonly BeatmapMetadata Metadata; - - public readonly Bindable> Mods = new Bindable>(new Mod[] { }); - - protected WorkingBeatmap(BeatmapInfo beatmapInfo) - { - BeatmapInfo = beatmapInfo; - BeatmapSetInfo = beatmapInfo.BeatmapSet; - Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - - Mods.ValueChanged += mods => applyRateAdjustments(); - - beatmap = new AsyncLazy(populateBeatmap); - background = new AsyncLazy(populateBackground, b => b == null || !b.IsDisposed); - track = new AsyncLazy(populateTrack); - waveform = new AsyncLazy(populateWaveform); - storyboard = new AsyncLazy(populateStoryboard); - skin = new AsyncLazy(populateSkin); - } - - /// - /// Saves the . - /// - public void Save() - { - var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json"); - using (var sw = new StreamWriter(path)) - sw.WriteLine(Beatmap.Serialize()); - Process.Start(path); - } - - protected abstract Beatmap GetBeatmap(); - protected abstract Texture GetBackground(); - protected abstract Track GetTrack(); - protected virtual Skin GetSkin() => new DefaultSkin(); - protected virtual Waveform GetWaveform() => new Waveform(); - protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo }; - - public bool BeatmapLoaded => beatmap.IsResultAvailable; - public Beatmap Beatmap => beatmap.Value.Result; - public async Task GetBeatmapAsync() => await beatmap.Value; - - private readonly AsyncLazy beatmap; - - private Beatmap populateBeatmap() - { - var b = GetBeatmap() ?? new Beatmap(); - - // use the database-backed info. - b.BeatmapInfo = BeatmapInfo; - - return b; - } - - public bool BackgroundLoaded => background.IsResultAvailable; - public Texture Background => background.Value.Result; - public async Task GetBackgroundAsync() => await background.Value; - private AsyncLazy background; - - private Texture populateBackground() => GetBackground(); - - public bool TrackLoaded => track.IsResultAvailable; - public Track Track => track.Value.Result; - public async Task GetTrackAsync() => await track.Value; - private AsyncLazy track; - - private Track populateTrack() - { - // we want to ensure that we always have a track, even if it's a fake one. - var t = GetTrack() ?? new TrackVirtual(); - applyRateAdjustments(t); - return t; - } - - public bool WaveformLoaded => waveform.IsResultAvailable; - public Waveform Waveform => waveform.Value.Result; - public async Task GetWaveformAsync() => await waveform.Value; - private readonly AsyncLazy waveform; - - private Waveform populateWaveform() => GetWaveform(); - - public bool StoryboardLoaded => storyboard.IsResultAvailable; - public Storyboard Storyboard => storyboard.Value.Result; - public async Task GetStoryboardAsync() => await storyboard.Value; - private readonly AsyncLazy storyboard; - - private Storyboard populateStoryboard() => GetStoryboard(); - - public bool SkinLoaded => skin.IsResultAvailable; - public Skin Skin => skin.Value.Result; - public async Task GetSkinAsync() => await skin.Value; - private readonly AsyncLazy skin; - - private Skin populateSkin() => GetSkin(); - - public void TransferTo(WorkingBeatmap other) - { - if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) - other.track = track; - - if (background.IsResultAvailable && Background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) - other.background = background; - } - - public virtual void Dispose() - { - if (BackgroundLoaded) Background?.Dispose(); - if (WaveformLoaded) Waveform?.Dispose(); - if (StoryboardLoaded) Storyboard?.Dispose(); - if (SkinLoaded) Skin?.Dispose(); - } - - /// - /// Eagerly dispose of the audio track associated with this (if any). - /// Accessing track again will load a fresh instance. - /// - public void RecycleTrack() => track.Recycle(); - - private void applyRateAdjustments(Track t = null) - { - if (t == null && track.IsResultAvailable) t = Track; - if (t == null) return; - - t.ResetSpeedAdjustments(); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToClock(t); - } - - public class AsyncLazy - { - private Lazy> lazy; - private readonly Func valueFactory; - private readonly Func stillValidFunction; - - private readonly object initLock = new object(); - - public AsyncLazy(Func valueFactory, Func stillValidFunction = null) - { - this.valueFactory = valueFactory; - this.stillValidFunction = stillValidFunction; - - recreate(); - } - - public void Recycle() - { - if (!IsResultAvailable) return; - - (lazy.Value.Result as IDisposable)?.Dispose(); - recreate(); - } - - public bool IsResultAvailable - { - get - { - recreateIfInvalid(); - return lazy.Value.IsCompleted; - } - } - - public Task Value - { - get - { - recreateIfInvalid(); - return lazy.Value; - } - } - - private void recreateIfInvalid() - { - lock (initLock) - { - if (!lazy.IsValueCreated || !lazy.Value.IsCompleted) - // we have not yet been initialised or haven't run the task. - return; - - if (stillValidFunction?.Invoke(lazy.Value.Result) ?? true) - // we are still in a valid state. - return; - - recreate(); - } - } - - private void recreate() => lazy = new Lazy>(() => Task.Run(valueFactory)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Mods; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using osu.Game.Storyboards; +using osu.Framework.IO.File; +using System.IO; +using osu.Game.IO.Serialization; +using System.Diagnostics; +using osu.Game.Skinning; + +namespace osu.Game.Beatmaps +{ + public abstract class WorkingBeatmap : IDisposable + { + public readonly BeatmapInfo BeatmapInfo; + + public readonly BeatmapSetInfo BeatmapSetInfo; + + public readonly BeatmapMetadata Metadata; + + public readonly Bindable> Mods = new Bindable>(new Mod[] { }); + + protected WorkingBeatmap(BeatmapInfo beatmapInfo) + { + BeatmapInfo = beatmapInfo; + BeatmapSetInfo = beatmapInfo.BeatmapSet; + Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + + Mods.ValueChanged += mods => applyRateAdjustments(); + + beatmap = new AsyncLazy(populateBeatmap); + background = new AsyncLazy(populateBackground, b => b == null || !b.IsDisposed); + track = new AsyncLazy(populateTrack); + waveform = new AsyncLazy(populateWaveform); + storyboard = new AsyncLazy(populateStoryboard); + skin = new AsyncLazy(populateSkin); + } + + /// + /// Saves the . + /// + public void Save() + { + var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json"); + using (var sw = new StreamWriter(path)) + sw.WriteLine(Beatmap.Serialize()); + Process.Start(path); + } + + protected abstract Beatmap GetBeatmap(); + protected abstract Texture GetBackground(); + protected abstract Track GetTrack(); + protected virtual Skin GetSkin() => new DefaultSkin(); + protected virtual Waveform GetWaveform() => new Waveform(); + protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo }; + + public bool BeatmapLoaded => beatmap.IsResultAvailable; + public Beatmap Beatmap => beatmap.Value.Result; + public async Task GetBeatmapAsync() => await beatmap.Value; + + private readonly AsyncLazy beatmap; + + private Beatmap populateBeatmap() + { + var b = GetBeatmap() ?? new Beatmap(); + + // use the database-backed info. + b.BeatmapInfo = BeatmapInfo; + + return b; + } + + public bool BackgroundLoaded => background.IsResultAvailable; + public Texture Background => background.Value.Result; + public async Task GetBackgroundAsync() => await background.Value; + private AsyncLazy background; + + private Texture populateBackground() => GetBackground(); + + public bool TrackLoaded => track.IsResultAvailable; + public Track Track => track.Value.Result; + public async Task GetTrackAsync() => await track.Value; + private AsyncLazy track; + + private Track populateTrack() + { + // we want to ensure that we always have a track, even if it's a fake one. + var t = GetTrack() ?? new TrackVirtual(); + applyRateAdjustments(t); + return t; + } + + public bool WaveformLoaded => waveform.IsResultAvailable; + public Waveform Waveform => waveform.Value.Result; + public async Task GetWaveformAsync() => await waveform.Value; + private readonly AsyncLazy waveform; + + private Waveform populateWaveform() => GetWaveform(); + + public bool StoryboardLoaded => storyboard.IsResultAvailable; + public Storyboard Storyboard => storyboard.Value.Result; + public async Task GetStoryboardAsync() => await storyboard.Value; + private readonly AsyncLazy storyboard; + + private Storyboard populateStoryboard() => GetStoryboard(); + + public bool SkinLoaded => skin.IsResultAvailable; + public Skin Skin => skin.Value.Result; + public async Task GetSkinAsync() => await skin.Value; + private readonly AsyncLazy skin; + + private Skin populateSkin() => GetSkin(); + + public void TransferTo(WorkingBeatmap other) + { + if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) + other.track = track; + + if (background.IsResultAvailable && Background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) + other.background = background; + } + + public virtual void Dispose() + { + if (BackgroundLoaded) Background?.Dispose(); + if (WaveformLoaded) Waveform?.Dispose(); + if (StoryboardLoaded) Storyboard?.Dispose(); + if (SkinLoaded) Skin?.Dispose(); + } + + /// + /// Eagerly dispose of the audio track associated with this (if any). + /// Accessing track again will load a fresh instance. + /// + public void RecycleTrack() => track.Recycle(); + + private void applyRateAdjustments(Track t = null) + { + if (t == null && track.IsResultAvailable) t = Track; + if (t == null) return; + + t.ResetSpeedAdjustments(); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToClock(t); + } + + public class AsyncLazy + { + private Lazy> lazy; + private readonly Func valueFactory; + private readonly Func stillValidFunction; + + private readonly object initLock = new object(); + + public AsyncLazy(Func valueFactory, Func stillValidFunction = null) + { + this.valueFactory = valueFactory; + this.stillValidFunction = stillValidFunction; + + recreate(); + } + + public void Recycle() + { + if (!IsResultAvailable) return; + + (lazy.Value.Result as IDisposable)?.Dispose(); + recreate(); + } + + public bool IsResultAvailable + { + get + { + recreateIfInvalid(); + return lazy.Value.IsCompleted; + } + } + + public Task Value + { + get + { + recreateIfInvalid(); + return lazy.Value; + } + } + + private void recreateIfInvalid() + { + lock (initLock) + { + if (!lazy.IsValueCreated || !lazy.Value.IsCompleted) + // we have not yet been initialised or haven't run the task. + return; + + if (stillValidFunction?.Invoke(lazy.Value.Result) ?? true) + // we are still in a valid state. + return; + + recreate(); + } + } + + private void recreate() => lazy = new Lazy>(() => Task.Run(valueFactory)); + } + } +} diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 1f7a84c6d3..0ef0589dff 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -1,71 +1,71 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Configuration; -using osu.Game.Rulesets; - -namespace osu.Game.Configuration -{ - public abstract class DatabasedConfigManager : ConfigManager - where T : struct - { - private readonly SettingsStore settings; - - private readonly int variant; - - private readonly List databasedSettings; - - private readonly RulesetInfo ruleset; - - protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0) - { - this.settings = settings; - this.ruleset = ruleset; - this.variant = variant; - - databasedSettings = settings.Query(ruleset?.ID, variant); - - InitialiseDefaults(); - } - - protected override void PerformLoad() - { - } - - protected override bool PerformSave() - { - return true; - } - - protected override void AddBindable(T lookup, Bindable bindable) - { - base.AddBindable(lookup, bindable); - - var setting = databasedSettings.FirstOrDefault(s => (int)s.Key == (int)(object)lookup); - if (setting != null) - { - bindable.Parse(setting.Value); - } - else - { - settings.Update(setting = new DatabasedSetting - { - Key = lookup, - Value = bindable.Value, - RulesetID = ruleset?.ID, - Variant = variant, - }); - - databasedSettings.Add(setting); - } - - bindable.ValueChanged += v => - { - setting.Value = v; - settings.Update(setting); - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Configuration; +using osu.Game.Rulesets; + +namespace osu.Game.Configuration +{ + public abstract class DatabasedConfigManager : ConfigManager + where T : struct + { + private readonly SettingsStore settings; + + private readonly int variant; + + private readonly List databasedSettings; + + private readonly RulesetInfo ruleset; + + protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0) + { + this.settings = settings; + this.ruleset = ruleset; + this.variant = variant; + + databasedSettings = settings.Query(ruleset?.ID, variant); + + InitialiseDefaults(); + } + + protected override void PerformLoad() + { + } + + protected override bool PerformSave() + { + return true; + } + + protected override void AddBindable(T lookup, Bindable bindable) + { + base.AddBindable(lookup, bindable); + + var setting = databasedSettings.FirstOrDefault(s => (int)s.Key == (int)(object)lookup); + if (setting != null) + { + bindable.Parse(setting.Value); + } + else + { + settings.Update(setting = new DatabasedSetting + { + Key = lookup, + Value = bindable.Value, + RulesetID = ruleset?.ID, + Variant = variant, + }); + + databasedSettings.Add(setting); + } + + bindable.ValueChanged += v => + { + setting.Value = v; + settings.Update(setting); + }; + } + } +} diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index 7c2f65c854..62990991b0 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -1,51 +1,51 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel.DataAnnotations.Schema; -using osu.Game.Database; - -namespace osu.Game.Configuration -{ - [Table("Settings")] - public class DatabasedSetting : IHasPrimaryKey - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int ID { get; set; } - - public int? RulesetID { get; set; } - - public int? Variant { get; set; } - - [Column("Key")] - public int IntKey - { - get => (int)Key; - private set => Key = value; - } - - [Column("Value")] - public string StringValue - { - get => Value.ToString(); - set => Value = value; - } - - public object Key; - public object Value; - - public DatabasedSetting(object key, object value) - { - Key = key; - Value = value; - } - - /// - /// Constructor for derived classes that may require serialisation. - /// - public DatabasedSetting() - { - } - - public override string ToString() => $"{Key}=>{Value}"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; + +namespace osu.Game.Configuration +{ + [Table("Settings")] + public class DatabasedSetting : IHasPrimaryKey + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public int? RulesetID { get; set; } + + public int? Variant { get; set; } + + [Column("Key")] + public int IntKey + { + get => (int)Key; + private set => Key = value; + } + + [Column("Value")] + public string StringValue + { + get => Value.ToString(); + set => Value = value; + } + + public object Key; + public object Value; + + public DatabasedSetting(object key, object value) + { + Key = key; + Value = value; + } + + /// + /// Constructor for derived classes that may require serialisation. + /// + public DatabasedSetting() + { + } + + public override string ToString() => $"{Key}=>{Value}"; + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 70260b349e..5ff3ddbe05 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -1,133 +1,133 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Platform; -using osu.Game.Overlays; -using osu.Game.Screens.Select; - -namespace osu.Game.Configuration -{ - public class OsuConfigManager : IniConfigManager - { - protected override void InitialiseDefaults() - { - // UI/selection defaults - Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); - Set(OsuSetting.Skin, 0, 0, int.MaxValue); - - Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); - - Set(OsuSetting.ShowConvertedBeatmaps, true); - Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); - Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); - - Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); - - Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); - - // Online settings - Set(OsuSetting.Username, string.Empty); - Set(OsuSetting.Token, string.Empty); - - Set(OsuSetting.SavePassword, false).ValueChanged += val => - { - if (val) Set(OsuSetting.SaveUsername, true); - }; - - Set(OsuSetting.SaveUsername, true).ValueChanged += val => - { - if (!val) Set(OsuSetting.SavePassword, false); - }; - - // Audio - Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); - - Set(OsuSetting.MenuVoice, true); - Set(OsuSetting.MenuMusic, true); - - Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); - - // Input - Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); - Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2, 0.01); - Set(OsuSetting.AutoCursorSize, false); - - Set(OsuSetting.MouseDisableButtons, false); - Set(OsuSetting.MouseDisableWheel, false); - - // Graphics - Set(OsuSetting.ShowFpsDisplay, false); - - Set(OsuSetting.ShowStoryboard, true); - Set(OsuSetting.CursorRotation, true); - - Set(OsuSetting.MenuParallax, true); - - Set(OsuSetting.SnakingInSliders, true); - Set(OsuSetting.SnakingOutSliders, true); - - // Gameplay - Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); - Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); - - Set(OsuSetting.ShowInterface, true); - Set(OsuSetting.KeyOverlay, false); - - Set(OsuSetting.FloatingComments, false); - - Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential); - - // Update - Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); - - Set(OsuSetting.Version, string.Empty); - - Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); - } - - public OsuConfigManager(Storage storage) : base(storage) - { - } - } - - public enum OsuSetting - { - Ruleset, - Token, - MenuCursorSize, - GameplayCursorSize, - AutoCursorSize, - DimLevel, - BlurLevel, - ShowStoryboard, - KeyOverlay, - FloatingComments, - ShowInterface, - MouseDisableButtons, - MouseDisableWheel, - AudioOffset, - VolumeInactive, - MenuMusic, - MenuVoice, - CursorRotation, - MenuParallax, - BeatmapDetailTab, - Username, - ReleaseStream, - SavePassword, - SaveUsername, - DisplayStarsMinimum, - DisplayStarsMaximum, - RandomSelectAlgorithm, - SnakingInSliders, - SnakingOutSliders, - ShowFpsDisplay, - ChatDisplayHeight, - Version, - ShowConvertedBeatmaps, - SpeedChangeVisualisation, - Skin, - ScreenshotFormat - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Platform; +using osu.Game.Overlays; +using osu.Game.Screens.Select; + +namespace osu.Game.Configuration +{ + public class OsuConfigManager : IniConfigManager + { + protected override void InitialiseDefaults() + { + // UI/selection defaults + Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); + Set(OsuSetting.Skin, 0, 0, int.MaxValue); + + Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); + + Set(OsuSetting.ShowConvertedBeatmaps, true); + Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); + Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); + + Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); + + Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); + + // Online settings + Set(OsuSetting.Username, string.Empty); + Set(OsuSetting.Token, string.Empty); + + Set(OsuSetting.SavePassword, false).ValueChanged += val => + { + if (val) Set(OsuSetting.SaveUsername, true); + }; + + Set(OsuSetting.SaveUsername, true).ValueChanged += val => + { + if (!val) Set(OsuSetting.SavePassword, false); + }; + + // Audio + Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); + + Set(OsuSetting.MenuVoice, true); + Set(OsuSetting.MenuMusic, true); + + Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); + + // Input + Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); + Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2, 0.01); + Set(OsuSetting.AutoCursorSize, false); + + Set(OsuSetting.MouseDisableButtons, false); + Set(OsuSetting.MouseDisableWheel, false); + + // Graphics + Set(OsuSetting.ShowFpsDisplay, false); + + Set(OsuSetting.ShowStoryboard, true); + Set(OsuSetting.CursorRotation, true); + + Set(OsuSetting.MenuParallax, true); + + Set(OsuSetting.SnakingInSliders, true); + Set(OsuSetting.SnakingOutSliders, true); + + // Gameplay + Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); + Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); + + Set(OsuSetting.ShowInterface, true); + Set(OsuSetting.KeyOverlay, false); + + Set(OsuSetting.FloatingComments, false); + + Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential); + + // Update + Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); + + Set(OsuSetting.Version, string.Empty); + + Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); + } + + public OsuConfigManager(Storage storage) : base(storage) + { + } + } + + public enum OsuSetting + { + Ruleset, + Token, + MenuCursorSize, + GameplayCursorSize, + AutoCursorSize, + DimLevel, + BlurLevel, + ShowStoryboard, + KeyOverlay, + FloatingComments, + ShowInterface, + MouseDisableButtons, + MouseDisableWheel, + AudioOffset, + VolumeInactive, + MenuMusic, + MenuVoice, + CursorRotation, + MenuParallax, + BeatmapDetailTab, + Username, + ReleaseStream, + SavePassword, + SaveUsername, + DisplayStarsMinimum, + DisplayStarsMaximum, + RandomSelectAlgorithm, + SnakingInSliders, + SnakingOutSliders, + ShowFpsDisplay, + ChatDisplayHeight, + Version, + ShowConvertedBeatmaps, + SpeedChangeVisualisation, + Skin, + ScreenshotFormat + } +} diff --git a/osu.Game/Configuration/RandomSelectAlgorithm.cs b/osu.Game/Configuration/RandomSelectAlgorithm.cs index 6f3d151b50..d994f59b34 100644 --- a/osu.Game/Configuration/RandomSelectAlgorithm.cs +++ b/osu.Game/Configuration/RandomSelectAlgorithm.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Configuration -{ - public enum RandomSelectAlgorithm - { - [Description("Never repeat")] - RandomPermutation, - [Description("Random")] - Random - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum RandomSelectAlgorithm + { + [Description("Never repeat")] + RandomPermutation, + [Description("Random")] + Random + } +} diff --git a/osu.Game/Configuration/RankingType.cs b/osu.Game/Configuration/RankingType.cs index 20483a9b73..32fe934100 100644 --- a/osu.Game/Configuration/RankingType.cs +++ b/osu.Game/Configuration/RankingType.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Configuration -{ - public enum RankingType - { - Local, - [Description("Global")] - Top, - [Description("Selected Mods")] - SelectedMod, - Friends, - Country - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum RankingType + { + Local, + [Description("Global")] + Top, + [Description("Selected Mods")] + SelectedMod, + Friends, + Country + } +} diff --git a/osu.Game/Configuration/ReleaseStream.cs b/osu.Game/Configuration/ReleaseStream.cs index 9deeb0bed8..ca13d0cac3 100644 --- a/osu.Game/Configuration/ReleaseStream.cs +++ b/osu.Game/Configuration/ReleaseStream.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Configuration -{ - public enum ReleaseStream - { - Lazer, - //Stable40, - //Beta40, - //Stable - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Configuration +{ + public enum ReleaseStream + { + Lazer, + //Stable40, + //Beta40, + //Stable + } +} diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index 6d4138c4c5..0333c3406f 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Configuration -{ - public enum ScoreMeterType - { - None, - Colour, - Error - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Configuration +{ + public enum ScoreMeterType + { + None, + Colour, + Error + } +} diff --git a/osu.Game/Configuration/ScreenshotFormat.cs b/osu.Game/Configuration/ScreenshotFormat.cs index b9309fae3a..43901281dc 100644 --- a/osu.Game/Configuration/ScreenshotFormat.cs +++ b/osu.Game/Configuration/ScreenshotFormat.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Configuration -{ - public enum ScreenshotFormat - { - [Description("JPG (web-friendly)")] - Jpg = 1, - [Description("PNG (lossless)")] - Png = 2 - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum ScreenshotFormat + { + [Description("JPG (web-friendly)")] + Jpg = 1, + [Description("PNG (lossless)")] + Png = 2 + } +} diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 7b66002a79..7914e34147 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Database; - -namespace osu.Game.Configuration -{ - public class SettingsStore : DatabaseBackedStore - { - public event Action SettingChanged; - - public SettingsStore(DatabaseContextFactory contextFactory) - : base(contextFactory) - { - } - - /// - /// Retrieve s for a specified ruleset/variant content. - /// - /// The ruleset's internal ID. - /// An optional variant. - /// - public List Query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); - - public void Update(DatabasedSetting setting) - { - using (ContextFactory.GetForWrite()) - { - var newValue = setting.Value; - Refresh(ref setting); - setting.Value = newValue; - } - - SettingChanged?.Invoke(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Database; + +namespace osu.Game.Configuration +{ + public class SettingsStore : DatabaseBackedStore + { + public event Action SettingChanged; + + public SettingsStore(DatabaseContextFactory contextFactory) + : base(contextFactory) + { + } + + /// + /// Retrieve s for a specified ruleset/variant content. + /// + /// The ruleset's internal ID. + /// An optional variant. + /// + public List Query(int? rulesetId = null, int? variant = null) => + ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + + public void Update(DatabasedSetting setting) + { + using (ContextFactory.GetForWrite()) + { + var newValue = setting.Value; + Refresh(ref setting); + setting.Value = newValue; + } + + SettingChanged?.Invoke(); + } + } +} diff --git a/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs b/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs index 644ae0a727..d38b1a89c5 100644 --- a/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs +++ b/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Configuration -{ - public enum SpeedChangeVisualisationMethod - { - [Description("Sequential")] - Sequential, - [Description("Overlapping")] - Overlapping - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum SpeedChangeVisualisationMethod + { + [Description("Sequential")] + Sequential, + [Description("Overlapping")] + Overlapping + } +} diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3afb22f0ad..e04559d547 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -1,348 +1,348 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.IO; -using osu.Game.IO.Archives; -using osu.Game.IPC; -using osu.Game.Overlays.Notifications; -using osu.Game.Utils; -using SharpCompress.Common; -using FileInfo = osu.Game.IO.FileInfo; - -namespace osu.Game.Database -{ - /// - /// Encapsulates a model store class to give it import functionality. - /// Adds cross-functionality with to give access to the central file store for the provided model. - /// - /// The model type. - /// The associated file join type. - public abstract class ArchiveModelManager : ICanAcceptFiles - where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete - where TFileModel : INamedFileInfo, new() - { - /// - /// Set an endpoint for notifications to be posted to. - /// - public Action PostNotification { protected get; set; } - - /// - /// Fired when a new becomes available in the database. - /// This is not guaranteed to run on the update thread. - /// - public event Action ItemAdded; - - /// - /// Fired when a is removed from the database. - /// This is not guaranteed to run on the update thread. - /// - public event Action ItemRemoved; - - public virtual string[] HandledExtensions => new[] { ".zip" }; - - protected readonly FileStore Files; - - protected readonly IDatabaseContextFactory ContextFactory; - - protected readonly MutableDatabaseBackedStore ModelStore; - - // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) - private ArchiveImportIPCChannel ipc; - - protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore modelStore, IIpcHost importHost = null) - { - ContextFactory = contextFactory; - - ModelStore = modelStore; - ModelStore.ItemAdded += s => ItemAdded?.Invoke(s); - ModelStore.ItemRemoved += s => ItemRemoved?.Invoke(s); - - Files = new FileStore(contextFactory, storage); - - if (importHost != null) - ipc = new ArchiveImportIPCChannel(importHost, this); - - ModelStore.Cleanup(); - } - - /// - /// Import one or more items from filesystem . - /// This will post notifications tracking progress. - /// - /// One or more archive locations on disk. - public void Import(params string[] paths) - { - var notification = new ProgressNotification - { - Text = "Import is initialising...", - Progress = 0, - State = ProgressNotificationState.Active, - }; - - PostNotification?.Invoke(notification); - - List imported = new List(); - - int current = 0; - int errors = 0; - foreach (string path in paths) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - try - { - notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}"; - using (ArchiveReader reader = getReaderFrom(path)) - imported.Add(Import(reader)); - - notification.Progress = (float)current / paths.Length; - - // We may or may not want to delete the file depending on where it is stored. - // e.g. reconstructing/repairing database with items from default storage. - // Also, not always a single file, i.e. for LegacyFilesystemReader - // TODO: Add a check to prevent files from storage to be deleted. - try - { - if (File.Exists(path)) - File.Delete(path); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); - } - } - catch (Exception e) - { - e = e.InnerException ?? e; - Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})"); - errors++; - } - } - - notification.Text = errors > 0 ? $"Import complete with {errors} errors" : "Import successful!"; - notification.State = ProgressNotificationState.Completed; - } - - /// - /// Import an item from an . - /// - /// The archive to be imported. - public TModel Import(ArchiveReader archive) - { - using (ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. - { - // create a new model (don't yet add to database) - var item = CreateModel(archive); - - var existing = CheckForExisting(item); - - if (existing != null) return existing; - - item.Files = createFileInfos(archive, Files); - - Populate(item, archive); - - // import to store - ModelStore.Add(item); - - return item; - } - } - - /// - /// Import an item from a . - /// - /// The model to be imported. - public void Import(TModel item) => ModelStore.Add(item); - - /// - /// Perform an update of the specified item. - /// TODO: Support file changes. - /// - /// The item to update. - public void Update(TModel item) => ModelStore.Update(item); - - /// - /// Delete an item from the manager. - /// Is a no-op for already deleted items. - /// - /// The item to delete. - public void Delete(TModel item) - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - context.ChangeTracker.AutoDetectChangesEnabled = false; - - // re-fetch the model on the import context. - var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == item.ID); - - if (foundModel.DeletePending) return; - - if (ModelStore.Delete(foundModel)) - Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); - - context.ChangeTracker.AutoDetectChangesEnabled = true; - } - } - - /// - /// Delete multiple items. - /// This will post notifications tracking progress. - /// - public void Delete(List items) - { - if (items.Count == 0) return; - - var notification = new ProgressNotification - { - Progress = 0, - CompletionText = "Deleted all beatmaps!", - State = ProgressNotificationState.Active, - }; - - PostNotification?.Invoke(notification); - - int i = 0; - - using (ContextFactory.GetForWrite()) - { - foreach (var b in items) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Deleting ({++i} of {items.Count})"; - - Delete(b); - - notification.Progress = (float)i / items.Count; - } - } - - notification.State = ProgressNotificationState.Completed; - } - - /// - /// Restore multiple items that were previously deleted. - /// This will post notifications tracking progress. - /// - public void Undelete(List items) - { - if (!items.Any()) return; - - var notification = new ProgressNotification - { - CompletionText = "Restored all deleted items!", - Progress = 0, - State = ProgressNotificationState.Active, - }; - - PostNotification?.Invoke(notification); - - int i = 0; - - using (ContextFactory.GetForWrite()) - { - foreach (var item in items) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Restoring ({++i} of {items.Count})"; - - Undelete(item); - - notification.Progress = (float)i / items.Count; - } - } - - notification.State = ProgressNotificationState.Completed; - } - - /// - /// Restore an item that was previously deleted. Is a no-op if the item is not in a deleted state, or has its protected flag set. - /// - /// The item to restore - public void Undelete(TModel item) - { - using (var usage = ContextFactory.GetForWrite()) - { - usage.Context.ChangeTracker.AutoDetectChangesEnabled = false; - - if (!ModelStore.Undelete(item)) return; - - Files.Reference(item.Files.Select(f => f.FileInfo).ToArray()); - - usage.Context.ChangeTracker.AutoDetectChangesEnabled = true; - } - } - - /// - /// Create all required s for the provided archive, adding them to the global file store. - /// - private List createFileInfos(ArchiveReader reader, FileStore files) - { - var fileInfos = new List(); - - // import files to manager - foreach (string file in reader.Filenames) - using (Stream s = reader.GetStream(file)) - fileInfos.Add(new TFileModel - { - Filename = file, - FileInfo = files.Add(s) - }); - - return fileInfos; - } - - /// - /// Create a barebones model from the provided archive. - /// Actual expensive population should be done in ; this should just prepare for duplicate checking. - /// - /// The archive to create the model for. - /// A model populated with minimal information. - protected abstract TModel CreateModel(ArchiveReader archive); - - /// - /// Populate the provided model completely from the given archive. - /// After this method, the model should be in a state ready to commit to a store. - /// - /// The model to populate. - /// The archive to use as a reference for population. - protected virtual void Populate(TModel model, ArchiveReader archive) - { - } - - protected virtual TModel CheckForExisting(TModel model) => null; - - private DbSet queryModel() => ContextFactory.Get().Set(); - - /// - /// Creates an from a valid storage path. - /// - /// A file or folder path resolving the archive content. - /// A reader giving access to the archive's content. - private ArchiveReader getReaderFrom(string path) - { - if (ZipUtils.IsZipArchive(path)) - return new ZipArchiveReader(Files.Storage.GetStream(path), Path.GetFileName(path)); - if (Directory.Exists(path)) - return new LegacyFilesystemReader(path); - throw new InvalidFormatException($"{path} is not a valid archive"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.IO; +using osu.Game.IO.Archives; +using osu.Game.IPC; +using osu.Game.Overlays.Notifications; +using osu.Game.Utils; +using SharpCompress.Common; +using FileInfo = osu.Game.IO.FileInfo; + +namespace osu.Game.Database +{ + /// + /// Encapsulates a model store class to give it import functionality. + /// Adds cross-functionality with to give access to the central file store for the provided model. + /// + /// The model type. + /// The associated file join type. + public abstract class ArchiveModelManager : ICanAcceptFiles + where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete + where TFileModel : INamedFileInfo, new() + { + /// + /// Set an endpoint for notifications to be posted to. + /// + public Action PostNotification { protected get; set; } + + /// + /// Fired when a new becomes available in the database. + /// This is not guaranteed to run on the update thread. + /// + public event Action ItemAdded; + + /// + /// Fired when a is removed from the database. + /// This is not guaranteed to run on the update thread. + /// + public event Action ItemRemoved; + + public virtual string[] HandledExtensions => new[] { ".zip" }; + + protected readonly FileStore Files; + + protected readonly IDatabaseContextFactory ContextFactory; + + protected readonly MutableDatabaseBackedStore ModelStore; + + // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) + private ArchiveImportIPCChannel ipc; + + protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore modelStore, IIpcHost importHost = null) + { + ContextFactory = contextFactory; + + ModelStore = modelStore; + ModelStore.ItemAdded += s => ItemAdded?.Invoke(s); + ModelStore.ItemRemoved += s => ItemRemoved?.Invoke(s); + + Files = new FileStore(contextFactory, storage); + + if (importHost != null) + ipc = new ArchiveImportIPCChannel(importHost, this); + + ModelStore.Cleanup(); + } + + /// + /// Import one or more items from filesystem . + /// This will post notifications tracking progress. + /// + /// One or more archive locations on disk. + public void Import(params string[] paths) + { + var notification = new ProgressNotification + { + Text = "Import is initialising...", + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + List imported = new List(); + + int current = 0; + int errors = 0; + foreach (string path in paths) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + try + { + notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}"; + using (ArchiveReader reader = getReaderFrom(path)) + imported.Add(Import(reader)); + + notification.Progress = (float)current / paths.Length; + + // We may or may not want to delete the file depending on where it is stored. + // e.g. reconstructing/repairing database with items from default storage. + // Also, not always a single file, i.e. for LegacyFilesystemReader + // TODO: Add a check to prevent files from storage to be deleted. + try + { + if (File.Exists(path)) + File.Delete(path); + } + catch (Exception e) + { + Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); + } + } + catch (Exception e) + { + e = e.InnerException ?? e; + Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})"); + errors++; + } + } + + notification.Text = errors > 0 ? $"Import complete with {errors} errors" : "Import successful!"; + notification.State = ProgressNotificationState.Completed; + } + + /// + /// Import an item from an . + /// + /// The archive to be imported. + public TModel Import(ArchiveReader archive) + { + using (ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. + { + // create a new model (don't yet add to database) + var item = CreateModel(archive); + + var existing = CheckForExisting(item); + + if (existing != null) return existing; + + item.Files = createFileInfos(archive, Files); + + Populate(item, archive); + + // import to store + ModelStore.Add(item); + + return item; + } + } + + /// + /// Import an item from a . + /// + /// The model to be imported. + public void Import(TModel item) => ModelStore.Add(item); + + /// + /// Perform an update of the specified item. + /// TODO: Support file changes. + /// + /// The item to update. + public void Update(TModel item) => ModelStore.Update(item); + + /// + /// Delete an item from the manager. + /// Is a no-op for already deleted items. + /// + /// The item to delete. + public void Delete(TModel item) + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + context.ChangeTracker.AutoDetectChangesEnabled = false; + + // re-fetch the model on the import context. + var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == item.ID); + + if (foundModel.DeletePending) return; + + if (ModelStore.Delete(foundModel)) + Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); + + context.ChangeTracker.AutoDetectChangesEnabled = true; + } + } + + /// + /// Delete multiple items. + /// This will post notifications tracking progress. + /// + public void Delete(List items) + { + if (items.Count == 0) return; + + var notification = new ProgressNotification + { + Progress = 0, + CompletionText = "Deleted all beatmaps!", + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 0; + + using (ContextFactory.GetForWrite()) + { + foreach (var b in items) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Deleting ({++i} of {items.Count})"; + + Delete(b); + + notification.Progress = (float)i / items.Count; + } + } + + notification.State = ProgressNotificationState.Completed; + } + + /// + /// Restore multiple items that were previously deleted. + /// This will post notifications tracking progress. + /// + public void Undelete(List items) + { + if (!items.Any()) return; + + var notification = new ProgressNotification + { + CompletionText = "Restored all deleted items!", + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 0; + + using (ContextFactory.GetForWrite()) + { + foreach (var item in items) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Restoring ({++i} of {items.Count})"; + + Undelete(item); + + notification.Progress = (float)i / items.Count; + } + } + + notification.State = ProgressNotificationState.Completed; + } + + /// + /// Restore an item that was previously deleted. Is a no-op if the item is not in a deleted state, or has its protected flag set. + /// + /// The item to restore + public void Undelete(TModel item) + { + using (var usage = ContextFactory.GetForWrite()) + { + usage.Context.ChangeTracker.AutoDetectChangesEnabled = false; + + if (!ModelStore.Undelete(item)) return; + + Files.Reference(item.Files.Select(f => f.FileInfo).ToArray()); + + usage.Context.ChangeTracker.AutoDetectChangesEnabled = true; + } + } + + /// + /// Create all required s for the provided archive, adding them to the global file store. + /// + private List createFileInfos(ArchiveReader reader, FileStore files) + { + var fileInfos = new List(); + + // import files to manager + foreach (string file in reader.Filenames) + using (Stream s = reader.GetStream(file)) + fileInfos.Add(new TFileModel + { + Filename = file, + FileInfo = files.Add(s) + }); + + return fileInfos; + } + + /// + /// Create a barebones model from the provided archive. + /// Actual expensive population should be done in ; this should just prepare for duplicate checking. + /// + /// The archive to create the model for. + /// A model populated with minimal information. + protected abstract TModel CreateModel(ArchiveReader archive); + + /// + /// Populate the provided model completely from the given archive. + /// After this method, the model should be in a state ready to commit to a store. + /// + /// The model to populate. + /// The archive to use as a reference for population. + protected virtual void Populate(TModel model, ArchiveReader archive) + { + } + + protected virtual TModel CheckForExisting(TModel model) => null; + + private DbSet queryModel() => ContextFactory.Get().Set(); + + /// + /// Creates an from a valid storage path. + /// + /// A file or folder path resolving the archive content. + /// A reader giving access to the archive's content. + private ArchiveReader getReaderFrom(string path) + { + if (ZipUtils.IsZipArchive(path)) + return new ZipArchiveReader(Files.Storage.GetStream(path), Path.GetFileName(path)); + if (Directory.Exists(path)) + return new LegacyFilesystemReader(path); + throw new InvalidFormatException($"{path} is not a valid archive"); + } + } +} diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 4b582bdfea..c6fb51c056 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -1,52 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using Microsoft.EntityFrameworkCore; -using osu.Framework.Platform; - -namespace osu.Game.Database -{ - public abstract class DatabaseBackedStore - { - protected readonly Storage Storage; - - protected readonly IDatabaseContextFactory ContextFactory; - - /// - /// Refresh an instance potentially from a different thread with a local context-tracked instance. - /// - /// The object to use as a reference when negotiating a local instance. - /// An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes. - /// A valid EF-stored type. - protected virtual void Refresh(ref T obj, IQueryable lookupSource = null) where T : class, IHasPrimaryKey - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - if (context.Entry(obj).State != EntityState.Detached) return; - - var id = obj.ID; - var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id); - if (foundObject != null) - obj = foundObject; - else - context.Add(obj); - } - } - - protected DatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) - { - ContextFactory = contextFactory; - Storage = storage; - } - - /// - /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. - /// - public virtual void Cleanup() - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Platform; + +namespace osu.Game.Database +{ + public abstract class DatabaseBackedStore + { + protected readonly Storage Storage; + + protected readonly IDatabaseContextFactory ContextFactory; + + /// + /// Refresh an instance potentially from a different thread with a local context-tracked instance. + /// + /// The object to use as a reference when negotiating a local instance. + /// An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes. + /// A valid EF-stored type. + protected virtual void Refresh(ref T obj, IQueryable lookupSource = null) where T : class, IHasPrimaryKey + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + if (context.Entry(obj).State != EntityState.Detached) return; + + var id = obj.ID; + var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id); + if (foundObject != null) + obj = foundObject; + else + context.Add(obj); + } + } + + protected DatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) + { + ContextFactory = contextFactory; + Storage = storage; + } + + /// + /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. + /// + public virtual void Cleanup() + { + } + } +} diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 06737e61eb..71960303b5 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,94 +1,94 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Threading; -using osu.Framework.Platform; - -namespace osu.Game.Database -{ - public class DatabaseContextFactory : IDatabaseContextFactory - { - private readonly GameHost host; - - private const string database_name = @"client"; - - private ThreadLocal threadContexts; - - private readonly object writeLock = new object(); - - private bool currentWriteDidWrite; - private int currentWriteUsages; - - public DatabaseContextFactory(GameHost host) - { - this.host = host; - recycleThreadContexts(); - } - - /// - /// Get a context for the current thread for read-only usage. - /// If a is in progress, the existing write-safe context will be returned. - /// - public OsuDbContext Get() => threadContexts.Value; - - /// - /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). - /// This method may block if a write is already active on a different thread. - /// - /// A usage containing a usable context. - public DatabaseWriteUsage GetForWrite() - { - Monitor.Enter(writeLock); - - Interlocked.Increment(ref currentWriteUsages); - - return new DatabaseWriteUsage(threadContexts.Value, usageCompleted); - } - - private void usageCompleted(DatabaseWriteUsage usage) - { - int usages = Interlocked.Decrement(ref currentWriteUsages); - - try - { - currentWriteDidWrite |= usage.PerformedWrite; - - if (usages > 0) return; - - if (currentWriteDidWrite) - { - // explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged). - usage.Context.Dispose(); - - currentWriteDidWrite = false; - - // once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches. - recycleThreadContexts(); - } - } - finally - { - Monitor.Exit(writeLock); - } - } - - private void recycleThreadContexts() => threadContexts = new ThreadLocal(CreateContext); - - protected virtual OsuDbContext CreateContext() - { - var ctx = new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)); - ctx.Database.AutoTransactionsEnabled = false; - - return ctx; - } - - public void ResetDatabase() - { - lock (writeLock) - { - recycleThreadContexts(); - host.Storage.DeleteDatabase(database_name); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Threading; +using osu.Framework.Platform; + +namespace osu.Game.Database +{ + public class DatabaseContextFactory : IDatabaseContextFactory + { + private readonly GameHost host; + + private const string database_name = @"client"; + + private ThreadLocal threadContexts; + + private readonly object writeLock = new object(); + + private bool currentWriteDidWrite; + private int currentWriteUsages; + + public DatabaseContextFactory(GameHost host) + { + this.host = host; + recycleThreadContexts(); + } + + /// + /// Get a context for the current thread for read-only usage. + /// If a is in progress, the existing write-safe context will be returned. + /// + public OsuDbContext Get() => threadContexts.Value; + + /// + /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). + /// This method may block if a write is already active on a different thread. + /// + /// A usage containing a usable context. + public DatabaseWriteUsage GetForWrite() + { + Monitor.Enter(writeLock); + + Interlocked.Increment(ref currentWriteUsages); + + return new DatabaseWriteUsage(threadContexts.Value, usageCompleted); + } + + private void usageCompleted(DatabaseWriteUsage usage) + { + int usages = Interlocked.Decrement(ref currentWriteUsages); + + try + { + currentWriteDidWrite |= usage.PerformedWrite; + + if (usages > 0) return; + + if (currentWriteDidWrite) + { + // explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged). + usage.Context.Dispose(); + + currentWriteDidWrite = false; + + // once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches. + recycleThreadContexts(); + } + } + finally + { + Monitor.Exit(writeLock); + } + } + + private void recycleThreadContexts() => threadContexts = new ThreadLocal(CreateContext); + + protected virtual OsuDbContext CreateContext() + { + var ctx = new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)); + ctx.Database.AutoTransactionsEnabled = false; + + return ctx; + } + + public void ResetDatabase() + { + lock (writeLock) + { + recycleThreadContexts(); + host.Storage.DeleteDatabase(database_name); + } + } + } +} diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs index 52dd0ee268..7858c1a0d1 100644 --- a/osu.Game/Database/DatabaseWriteUsage.cs +++ b/osu.Game/Database/DatabaseWriteUsage.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using Microsoft.EntityFrameworkCore.Storage; - -namespace osu.Game.Database -{ - public class DatabaseWriteUsage : IDisposable - { - public readonly OsuDbContext Context; - private readonly IDbContextTransaction transaction; - private readonly Action usageCompleted; - - public DatabaseWriteUsage(OsuDbContext context, Action onCompleted) - { - Context = context; - transaction = Context.BeginTransaction(); - usageCompleted = onCompleted; - } - - public bool PerformedWrite { get; private set; } - - private bool isDisposed; - - protected void Dispose(bool disposing) - { - if (isDisposed) return; - isDisposed = true; - - PerformedWrite |= Context.SaveChanges(transaction) > 0; - usageCompleted?.Invoke(this); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~DatabaseWriteUsage() - { - Dispose(false); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Microsoft.EntityFrameworkCore.Storage; + +namespace osu.Game.Database +{ + public class DatabaseWriteUsage : IDisposable + { + public readonly OsuDbContext Context; + private readonly IDbContextTransaction transaction; + private readonly Action usageCompleted; + + public DatabaseWriteUsage(OsuDbContext context, Action onCompleted) + { + Context = context; + transaction = Context.BeginTransaction(); + usageCompleted = onCompleted; + } + + public bool PerformedWrite { get; private set; } + + private bool isDisposed; + + protected void Dispose(bool disposing) + { + if (isDisposed) return; + isDisposed = true; + + PerformedWrite |= Context.SaveChanges(transaction) > 0; + usageCompleted?.Invoke(this); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~DatabaseWriteUsage() + { + Dispose(false); + } + } +} diff --git a/osu.Game/Database/ICanAcceptFiles.cs b/osu.Game/Database/ICanAcceptFiles.cs index ab26525619..a3766ef5aa 100644 --- a/osu.Game/Database/ICanAcceptFiles.cs +++ b/osu.Game/Database/ICanAcceptFiles.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Database -{ - /// - /// A class which can accept files for importing. - /// - public interface ICanAcceptFiles - { - /// - /// Import the specified paths. - /// - /// The files which should be imported. - void Import(params string[] paths); - - /// - /// An array of accepted file extensions (in the standard format of ".abc"). - /// - string[] HandledExtensions { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database +{ + /// + /// A class which can accept files for importing. + /// + public interface ICanAcceptFiles + { + /// + /// Import the specified paths. + /// + /// The files which should be imported. + void Import(params string[] paths); + + /// + /// An array of accepted file extensions (in the standard format of ".abc"). + /// + string[] HandledExtensions { get; } + } +} diff --git a/osu.Game/Database/IDatabaseContextFactory.cs b/osu.Game/Database/IDatabaseContextFactory.cs index bc1bc0349c..372e1770e4 100644 --- a/osu.Game/Database/IDatabaseContextFactory.cs +++ b/osu.Game/Database/IDatabaseContextFactory.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Database -{ - public interface IDatabaseContextFactory - { - /// - /// Get a context for read-only usage. - /// - OsuDbContext Get(); - - /// - /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). - /// This method may block if a write is already active on a different thread. - /// - /// A usage containing a usable context. - DatabaseWriteUsage GetForWrite(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database +{ + public interface IDatabaseContextFactory + { + /// + /// Get a context for read-only usage. + /// + OsuDbContext Get(); + + /// + /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). + /// This method may block if a write is already active on a different thread. + /// + /// A usage containing a usable context. + DatabaseWriteUsage GetForWrite(); + } +} diff --git a/osu.Game/Database/IHasFiles.cs b/osu.Game/Database/IHasFiles.cs index faf3f16dfe..3aaba37efc 100644 --- a/osu.Game/Database/IHasFiles.cs +++ b/osu.Game/Database/IHasFiles.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; - -namespace osu.Game.Database -{ - /// - /// A model that contains a list of files it is responsible for. - /// - /// The model representing a file. - public interface IHasFiles - where TFile : INamedFileInfo - - { - List Files { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Database +{ + /// + /// A model that contains a list of files it is responsible for. + /// + /// The model representing a file. + public interface IHasFiles + where TFile : INamedFileInfo + + { + List Files { get; set; } + } +} diff --git a/osu.Game/Database/IHasPrimaryKey.cs b/osu.Game/Database/IHasPrimaryKey.cs index c5ad5f22f9..2ee356baea 100644 --- a/osu.Game/Database/IHasPrimaryKey.cs +++ b/osu.Game/Database/IHasPrimaryKey.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Database -{ - public interface IHasPrimaryKey - { - int ID { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database +{ + public interface IHasPrimaryKey + { + int ID { get; set; } + } +} diff --git a/osu.Game/Database/INamedFileInfo.cs b/osu.Game/Database/INamedFileInfo.cs index 8de451dd78..751024d184 100644 --- a/osu.Game/Database/INamedFileInfo.cs +++ b/osu.Game/Database/INamedFileInfo.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.IO; - -namespace osu.Game.Database -{ - /// - /// Represent a join model which gives a filename and scope to a . - /// - public interface INamedFileInfo - { - FileInfo FileInfo { get; set; } - string Filename { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.IO; + +namespace osu.Game.Database +{ + /// + /// Represent a join model which gives a filename and scope to a . + /// + public interface INamedFileInfo + { + FileInfo FileInfo { get; set; } + string Filename { get; set; } + } +} diff --git a/osu.Game/Database/ISoftDelete.cs b/osu.Game/Database/ISoftDelete.cs index c884d7af00..38fba62421 100644 --- a/osu.Game/Database/ISoftDelete.cs +++ b/osu.Game/Database/ISoftDelete.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Database -{ - /// - /// A model that can be deleted from user's view without being instantly lost. - /// - public interface ISoftDelete - { - /// - /// Whether this model is marked for future deletion. - /// - bool DeletePending { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database +{ + /// + /// A model that can be deleted from user's view without being instantly lost. + /// + public interface ISoftDelete + { + /// + /// Whether this model is marked for future deletion. + /// + bool DeletePending { get; set; } + } +} diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 4ab55691f2..8569d81f01 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -1,149 +1,149 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using osu.Framework.Platform; - -namespace osu.Game.Database -{ - /// - /// A typed store which supports basic addition, deletion and updating for soft-deletable models. - /// - /// The databased model. - public abstract class MutableDatabaseBackedStore : DatabaseBackedStore - where T : class, IHasPrimaryKey, ISoftDelete - { - public event Action ItemAdded; - public event Action ItemRemoved; - - protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) - : base(contextFactory, storage) - { - } - - /// - /// Access items pre-populated with includes for consumption. - /// - public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set()); - - /// - /// Add a to the database. - /// - /// The item to add. - public void Add(T item) - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - context.Attach(item); - } - - ItemAdded?.Invoke(item); - } - - /// - /// Update a in the database. - /// - /// The item to update. - public void Update(T item) - { - ItemRemoved?.Invoke(item); - - using (var usage = ContextFactory.GetForWrite()) - usage.Context.Update(item); - - ItemAdded?.Invoke(item); - } - - /// - /// Delete a from the database. - /// - /// The item to delete. - public bool Delete(T item) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref item); - - if (item.DeletePending) return false; - item.DeletePending = true; - } - - ItemRemoved?.Invoke(item); - return true; - } - - /// - /// Restore a from a deleted state. - /// - /// The item to undelete. - public bool Undelete(T item) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref item, ConsumableItems); - - if (!item.DeletePending) return false; - item.DeletePending = false; - } - - ItemAdded?.Invoke(item); - return true; - } - - /// - /// Allow implementations to add database-side includes or constraints when querying for consumption of items. - /// - /// The input query. - /// A potentially modified output query. - protected virtual IQueryable AddIncludesForConsumption(IQueryable query) => query; - - /// - /// Allow implementations to add database-side includes or constraints when deleting items. - /// Included properties could then be subsequently deleted by overriding . - /// - /// The input query. - /// A potentially modified output query. - protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; - - /// - /// Called when removing an item completely from the database. - /// - /// The items to be purged. - /// The write context which can be used to perform subsequent deletions. - protected virtual void Purge(List items, OsuDbContext context) => context.RemoveRange(items); - - public override void Cleanup() - { - base.Cleanup(); - PurgeDeletable(); - } - - /// - /// Purge items in a pending delete state. - /// - /// An optional query limiting the scope of the purge. - public void PurgeDeletable(Expression> query = null) - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - var lookup = context.Set().Where(s => s.DeletePending); - - if (query != null) lookup = lookup.Where(query); - - lookup = AddIncludesForDeletion(lookup); - - var purgeable = lookup.ToList(); - - if (!purgeable.Any()) return; - - Purge(purgeable, context); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using osu.Framework.Platform; + +namespace osu.Game.Database +{ + /// + /// A typed store which supports basic addition, deletion and updating for soft-deletable models. + /// + /// The databased model. + public abstract class MutableDatabaseBackedStore : DatabaseBackedStore + where T : class, IHasPrimaryKey, ISoftDelete + { + public event Action ItemAdded; + public event Action ItemRemoved; + + protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) + : base(contextFactory, storage) + { + } + + /// + /// Access items pre-populated with includes for consumption. + /// + public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set()); + + /// + /// Add a to the database. + /// + /// The item to add. + public void Add(T item) + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + context.Attach(item); + } + + ItemAdded?.Invoke(item); + } + + /// + /// Update a in the database. + /// + /// The item to update. + public void Update(T item) + { + ItemRemoved?.Invoke(item); + + using (var usage = ContextFactory.GetForWrite()) + usage.Context.Update(item); + + ItemAdded?.Invoke(item); + } + + /// + /// Delete a from the database. + /// + /// The item to delete. + public bool Delete(T item) + { + using (ContextFactory.GetForWrite()) + { + Refresh(ref item); + + if (item.DeletePending) return false; + item.DeletePending = true; + } + + ItemRemoved?.Invoke(item); + return true; + } + + /// + /// Restore a from a deleted state. + /// + /// The item to undelete. + public bool Undelete(T item) + { + using (ContextFactory.GetForWrite()) + { + Refresh(ref item, ConsumableItems); + + if (!item.DeletePending) return false; + item.DeletePending = false; + } + + ItemAdded?.Invoke(item); + return true; + } + + /// + /// Allow implementations to add database-side includes or constraints when querying for consumption of items. + /// + /// The input query. + /// A potentially modified output query. + protected virtual IQueryable AddIncludesForConsumption(IQueryable query) => query; + + /// + /// Allow implementations to add database-side includes or constraints when deleting items. + /// Included properties could then be subsequently deleted by overriding . + /// + /// The input query. + /// A potentially modified output query. + protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; + + /// + /// Called when removing an item completely from the database. + /// + /// The items to be purged. + /// The write context which can be used to perform subsequent deletions. + protected virtual void Purge(List items, OsuDbContext context) => context.RemoveRange(items); + + public override void Cleanup() + { + base.Cleanup(); + PurgeDeletable(); + } + + /// + /// Purge items in a pending delete state. + /// + /// An optional query limiting the scope of the purge. + public void PurgeDeletable(Expression> query = null) + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + var lookup = context.Set().Where(s => s.DeletePending); + + if (query != null) lookup = lookup.Where(query); + + lookup = AddIncludesForDeletion(lookup); + + var purgeable = lookup.ToList(); + + if (!purgeable.Any()) return; + + Purge(purgeable, context); + } + } + } +} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index a4b0c30478..1979ce3648 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -1,209 +1,209 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Logging; -using osu.Framework.Logging; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.IO; -using osu.Game.Rulesets; -using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; -using osu.Game.Skinning; - -namespace osu.Game.Database -{ - public class OsuDbContext : DbContext - { - public DbSet BeatmapInfo { get; set; } - public DbSet BeatmapDifficulty { get; set; } - public DbSet BeatmapMetadata { get; set; } - public DbSet BeatmapSetInfo { get; set; } - public DbSet DatabasedKeyBinding { get; set; } - public DbSet DatabasedSetting { get; set; } - public DbSet FileInfo { get; set; } - public DbSet RulesetInfo { get; set; } - public DbSet SkinInfo { get; set; } - - private readonly string connectionString; - - private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory()); - - static OsuDbContext() - { - // required to initialise native SQLite libraries on some platforms. - SQLitePCL.Batteries_V2.Init(); - } - - /// - /// Create a new in-memory OsuDbContext instance. - /// - public OsuDbContext() - : this("DataSource=:memory:") - { - // required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0). - - Migrate(); - } - - /// - /// Create a new OsuDbContext instance. - /// - /// A valid SQLite connection string. - public OsuDbContext(string connectionString) - { - this.connectionString = connectionString; - - var connection = Database.GetDbConnection(); - connection.Open(); - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "PRAGMA journal_mode=WAL;"; - cmd.ExecuteNonQuery(); - } - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - optionsBuilder - // this is required for the time being due to the way we are querying in places like BeatmapStore. - // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. - .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) - .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) - .UseLoggerFactory(logger.Value); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapID).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.MD5Hash).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); - - modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapSetID).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.DeletePending); - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); - - modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); - modelBuilder.Entity().HasIndex(b => b.IntAction); - - modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); - - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.ReferenceCount); - - modelBuilder.Entity().HasIndex(b => b.Available); - modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); - - modelBuilder.Entity().HasOne(b => b.BaseDifficulty); - } - - public IDbContextTransaction BeginTransaction() - { - // return Database.BeginTransaction(); - return null; - } - - public int SaveChanges(IDbContextTransaction transaction = null) - { - var ret = base.SaveChanges(); - if (ret > 0) transaction?.Commit(); - return ret; - } - - private class OsuDbLoggerFactory : ILoggerFactory - { - #region Disposal - - public void Dispose() - { - } - - #endregion - - public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); - - public void AddProvider(ILoggerProvider provider) - { - // no-op. called by tooling. - } - - private class OsuDbLoggerProvider : ILoggerProvider - { - #region Disposal - - public void Dispose() - { - } - - #endregion - - public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); - } - - private class OsuDbLogger : ILogger - { - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - if (logLevel < LogLevel.Information) - return; - - Framework.Logging.LogLevel frameworkLogLevel; - - switch (logLevel) - { - default: - frameworkLogLevel = Framework.Logging.LogLevel.Debug; - break; - case LogLevel.Warning: - frameworkLogLevel = Framework.Logging.LogLevel.Important; - break; - case LogLevel.Error: - case LogLevel.Critical: - frameworkLogLevel = Framework.Logging.LogLevel.Error; - break; - } - - Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel); - } - - public bool IsEnabled(LogLevel logLevel) - { -#if DEBUG_DATABASE - return logLevel > LogLevel.Debug; -#else - return logLevel > LogLevel.Information; -#endif - } - - public IDisposable BeginScope(TState state) => null; - } - } - - public void Migrate() - { - try - { - Database.Migrate(); - } - catch (Exception e) - { - throw new MigrationFailedException(e); - } - } - } - - public class MigrationFailedException : Exception - { - public MigrationFailedException(Exception exception) - : base("sqlite-net migration failed", exception) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.IO; +using osu.Game.Rulesets; +using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; +using osu.Game.Skinning; + +namespace osu.Game.Database +{ + public class OsuDbContext : DbContext + { + public DbSet BeatmapInfo { get; set; } + public DbSet BeatmapDifficulty { get; set; } + public DbSet BeatmapMetadata { get; set; } + public DbSet BeatmapSetInfo { get; set; } + public DbSet DatabasedKeyBinding { get; set; } + public DbSet DatabasedSetting { get; set; } + public DbSet FileInfo { get; set; } + public DbSet RulesetInfo { get; set; } + public DbSet SkinInfo { get; set; } + + private readonly string connectionString; + + private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory()); + + static OsuDbContext() + { + // required to initialise native SQLite libraries on some platforms. + SQLitePCL.Batteries_V2.Init(); + } + + /// + /// Create a new in-memory OsuDbContext instance. + /// + public OsuDbContext() + : this("DataSource=:memory:") + { + // required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0). + + Migrate(); + } + + /// + /// Create a new OsuDbContext instance. + /// + /// A valid SQLite connection string. + public OsuDbContext(string connectionString) + { + this.connectionString = connectionString; + + var connection = Database.GetDbConnection(); + connection.Open(); + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "PRAGMA journal_mode=WAL;"; + cmd.ExecuteNonQuery(); + } + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + optionsBuilder + // this is required for the time being due to the way we are querying in places like BeatmapStore. + // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. + .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) + .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) + .UseLoggerFactory(logger.Value); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.MD5Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + + modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapSetID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.DeletePending); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + + modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); + modelBuilder.Entity().HasIndex(b => b.IntAction); + + modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); + + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.ReferenceCount); + + modelBuilder.Entity().HasIndex(b => b.Available); + modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); + + modelBuilder.Entity().HasOne(b => b.BaseDifficulty); + } + + public IDbContextTransaction BeginTransaction() + { + // return Database.BeginTransaction(); + return null; + } + + public int SaveChanges(IDbContextTransaction transaction = null) + { + var ret = base.SaveChanges(); + if (ret > 0) transaction?.Commit(); + return ret; + } + + private class OsuDbLoggerFactory : ILoggerFactory + { + #region Disposal + + public void Dispose() + { + } + + #endregion + + public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); + + public void AddProvider(ILoggerProvider provider) + { + // no-op. called by tooling. + } + + private class OsuDbLoggerProvider : ILoggerProvider + { + #region Disposal + + public void Dispose() + { + } + + #endregion + + public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); + } + + private class OsuDbLogger : ILogger + { + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (logLevel < LogLevel.Information) + return; + + Framework.Logging.LogLevel frameworkLogLevel; + + switch (logLevel) + { + default: + frameworkLogLevel = Framework.Logging.LogLevel.Debug; + break; + case LogLevel.Warning: + frameworkLogLevel = Framework.Logging.LogLevel.Important; + break; + case LogLevel.Error: + case LogLevel.Critical: + frameworkLogLevel = Framework.Logging.LogLevel.Error; + break; + } + + Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel); + } + + public bool IsEnabled(LogLevel logLevel) + { +#if DEBUG_DATABASE + return logLevel > LogLevel.Debug; +#else + return logLevel > LogLevel.Information; +#endif + } + + public IDisposable BeginScope(TState state) => null; + } + } + + public void Migrate() + { + try + { + Database.Migrate(); + } + catch (Exception e) + { + throw new MigrationFailedException(e); + } + } + } + + public class MigrationFailedException : Exception + { + public MigrationFailedException(Exception exception) + : base("sqlite-net migration failed", exception) + { + } + } +} diff --git a/osu.Game/Database/SingletonContextFactory.cs b/osu.Game/Database/SingletonContextFactory.cs index 067e4fd8eb..74951e8433 100644 --- a/osu.Game/Database/SingletonContextFactory.cs +++ b/osu.Game/Database/SingletonContextFactory.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Database -{ - public class SingletonContextFactory : IDatabaseContextFactory - { - private readonly OsuDbContext context; - - public SingletonContextFactory(OsuDbContext context) - { - this.context = context; - } - - public OsuDbContext Get() => context; - - public DatabaseWriteUsage GetForWrite() => new DatabaseWriteUsage(context, null); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database +{ + public class SingletonContextFactory : IDatabaseContextFactory + { + private readonly OsuDbContext context; + + public SingletonContextFactory(OsuDbContext context) + { + this.context = context; + } + + public OsuDbContext Get() => context; + + public DatabaseWriteUsage GetForWrite() => new DatabaseWriteUsage(context, null); + } +} diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index c5cc14c25f..d5825a8c42 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using OpenTK.Graphics; -using osu.Game.Graphics.Textures; - -namespace osu.Game.Graphics.Backgrounds -{ - public class Background : BufferedContainer - { - public Sprite Sprite; - - private readonly string textureName; - - public Background(string textureName = @"") - { - CacheDrawnFrameBuffer = true; - - this.textureName = textureName; - RelativeSizeAxes = Axes.Both; - - Add(Sprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.DarkGray, - FillMode = FillMode.Fill, - }); - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - if (!string.IsNullOrEmpty(textureName)) - Sprite.Texture = textures.Get(textureName); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using OpenTK.Graphics; +using osu.Game.Graphics.Textures; + +namespace osu.Game.Graphics.Backgrounds +{ + public class Background : BufferedContainer + { + public Sprite Sprite; + + private readonly string textureName; + + public Background(string textureName = @"") + { + CacheDrawnFrameBuffer = true; + + this.textureName = textureName; + RelativeSizeAxes = Axes.Both; + + Add(Sprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.DarkGray, + FillMode = FillMode.Fill, + }); + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + if (!string.IsNullOrEmpty(textureName)) + Sprite.Texture = textures.Get(textureName); + } + } +} diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 89ed8044e6..01b09c0a40 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -1,280 +1,280 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.MathUtils; -using OpenTK; -using OpenTK.Graphics; -using System; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Textures; -using OpenTK.Graphics.ES30; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Allocation; -using System.Collections.Generic; -using osu.Framework.Graphics.Batches; -using osu.Framework.Graphics.OpenGL.Vertices; -using osu.Framework.Lists; - -namespace osu.Game.Graphics.Backgrounds -{ - public class Triangles : Drawable - { - private const float triangle_size = 100; - private const float base_velocity = 50; - - /// - /// How many screen-space pixels are smoothed over. - /// Same behavior as Sprite's EdgeSmoothness. - /// - private const float edge_smoothness = 1; - - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - - - public Color4 ColourLight = Color4.White; - public Color4 ColourDark = Color4.Black; - - /// - /// Whether we want to expire triangles as they exit our draw area completely. - /// - protected virtual bool ExpireOffScreenTriangles => true; - - /// - /// Whether we should create new triangles as others expire. - /// - protected virtual bool CreateNewTriangles => true; - - /// - /// The amount of triangles we want compared to the default distribution. - /// - protected virtual float SpawnRatio => 1; - - private float triangleScale = 1; - - /// - /// Whether we should drop-off alpha values of triangles more quickly to improve - /// the visual appearance of fading. This defaults to on as it is generally more - /// aesthetically pleasing, but should be turned off in buffered containers. - /// - public bool HideAlphaDiscrepancies = true; - - /// - /// The relative velocity of the triangles. Default is 1. - /// - public float Velocity = 1; - - private readonly SortedList parts = new SortedList(Comparer.Default); - - private Shader shader; - private readonly Texture texture; - - public Triangles() - { - texture = Texture.WhitePixel; - } - - [BackgroundDependencyLoader] - private void load(ShaderManager shaders) - { - shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - addTriangles(true); - } - - public float TriangleScale - { - get { return triangleScale; } - set - { - float change = value / triangleScale; - triangleScale = value; - - for (int i = 0; i < parts.Count; i++) - { - TriangleParticle newParticle = parts[i]; - newParticle.Scale *= change; - parts[i] = newParticle; - } - } - } - - protected override void Update() - { - base.Update(); - - Invalidate(Invalidation.DrawNode, shallPropagate: false); - - if (CreateNewTriangles) - addTriangles(false); - - float adjustedAlpha = HideAlphaDiscrepancies ? - // Cubically scale alpha to make it drop off more sharply. - (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) : - 1; - - float elapsedSeconds = (float)Time.Elapsed / 1000; - // Since position is relative, the velocity needs to scale inversely with DrawHeight. - // Since we will later multiply by the scale of individual triangles we normalize by - // dividing by triangleScale. - float movedDistance = -elapsedSeconds * Velocity * base_velocity / (DrawHeight * triangleScale); - - for (int i = 0; i < parts.Count; i++) - { - TriangleParticle newParticle = parts[i]; - - // Scale moved distance by the size of the triangle. Smaller triangles should move more slowly. - newParticle.Position.Y += parts[i].Scale * movedDistance; - newParticle.Colour.A = adjustedAlpha; - - parts[i] = newParticle; - - float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight; - if (bottomPos < 0) - parts.RemoveAt(i); - } - } - - private void addTriangles(bool randomY) - { - int aimTriangleCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio); - - for (int i = 0; i < aimTriangleCount - parts.Count; i++) - parts.Add(createTriangle(randomY)); - } - - private TriangleParticle createTriangle(bool randomY) - { - TriangleParticle particle = CreateTriangle(); - - particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1); - particle.Colour = CreateTriangleShade(); - - return particle; - } - - /// - /// Creates a triangle particle with a random scale. - /// - /// The triangle particle. - protected virtual TriangleParticle CreateTriangle() - { - const float std_dev = 0.16f; - const float mean = 0.5f; - - float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats - float u2 = 1 - RNG.NextSingle(); - float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); //random normal(0,1) - var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); //random normal(mean,stdDev^2) - - return new TriangleParticle { Scale = scale }; - } - - /// - /// Creates a shade of colour for the triangles. - /// - /// The colour. - protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1); - - protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); - - private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData(); - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var trianglesNode = (TrianglesDrawNode)node; - - trianglesNode.Shader = shader; - trianglesNode.Texture = texture; - trianglesNode.Size = DrawSize; - trianglesNode.Shared = sharedData; - - trianglesNode.Parts.Clear(); - trianglesNode.Parts.AddRange(parts); - } - - private class TrianglesDrawNodeSharedData - { - public readonly LinearBatch VertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); - } - - private class TrianglesDrawNode : DrawNode - { - public Shader Shader; - public Texture Texture; - - public TrianglesDrawNodeSharedData Shared; - - public readonly List Parts = new List(); - public Vector2 Size; - - public override void Draw(Action vertexAction) - { - base.Draw(vertexAction); - - Shader.Bind(); - Texture.TextureGL.Bind(); - - Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; - - foreach (TriangleParticle particle in Parts) - { - var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); - var size = new Vector2(2 * offset.X, offset.Y); - - var triangle = new Triangle( - Vector2Extensions.Transform(particle.Position * Size, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + offset, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) - ); - - ColourInfo colourInfo = DrawInfo.Colour; - colourInfo.ApplyChild(particle.Colour); - - Texture.DrawTriangle( - triangle, - colourInfo, - null, - Shared.VertexBatch.AddAction, - Vector2.Divide(localInflationAmount, size)); - } - - Shader.Unbind(); - } - } - - protected struct TriangleParticle : IComparable - { - /// - /// The position of the top vertex of the triangle. - /// - public Vector2 Position; - - /// - /// The colour of the triangle. - /// - public Color4 Colour; - - /// - /// The scale of the triangle. - /// - public float Scale; - - /// - /// Compares two s. This is a reverse comparer because when the - /// triangles are added to the particles list, they should be drawn from largest to smallest - /// such that the smaller triangles appear on top. - /// - /// - /// - public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using OpenTK; +using OpenTK.Graphics; +using System; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using OpenTK.Graphics.ES30; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Allocation; +using System.Collections.Generic; +using osu.Framework.Graphics.Batches; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Lists; + +namespace osu.Game.Graphics.Backgrounds +{ + public class Triangles : Drawable + { + private const float triangle_size = 100; + private const float base_velocity = 50; + + /// + /// How many screen-space pixels are smoothed over. + /// Same behavior as Sprite's EdgeSmoothness. + /// + private const float edge_smoothness = 1; + + public override bool HandleKeyboardInput => false; + public override bool HandleMouseInput => false; + + + public Color4 ColourLight = Color4.White; + public Color4 ColourDark = Color4.Black; + + /// + /// Whether we want to expire triangles as they exit our draw area completely. + /// + protected virtual bool ExpireOffScreenTriangles => true; + + /// + /// Whether we should create new triangles as others expire. + /// + protected virtual bool CreateNewTriangles => true; + + /// + /// The amount of triangles we want compared to the default distribution. + /// + protected virtual float SpawnRatio => 1; + + private float triangleScale = 1; + + /// + /// Whether we should drop-off alpha values of triangles more quickly to improve + /// the visual appearance of fading. This defaults to on as it is generally more + /// aesthetically pleasing, but should be turned off in buffered containers. + /// + public bool HideAlphaDiscrepancies = true; + + /// + /// The relative velocity of the triangles. Default is 1. + /// + public float Velocity = 1; + + private readonly SortedList parts = new SortedList(Comparer.Default); + + private Shader shader; + private readonly Texture texture; + + public Triangles() + { + texture = Texture.WhitePixel; + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + addTriangles(true); + } + + public float TriangleScale + { + get { return triangleScale; } + set + { + float change = value / triangleScale; + triangleScale = value; + + for (int i = 0; i < parts.Count; i++) + { + TriangleParticle newParticle = parts[i]; + newParticle.Scale *= change; + parts[i] = newParticle; + } + } + } + + protected override void Update() + { + base.Update(); + + Invalidate(Invalidation.DrawNode, shallPropagate: false); + + if (CreateNewTriangles) + addTriangles(false); + + float adjustedAlpha = HideAlphaDiscrepancies ? + // Cubically scale alpha to make it drop off more sharply. + (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) : + 1; + + float elapsedSeconds = (float)Time.Elapsed / 1000; + // Since position is relative, the velocity needs to scale inversely with DrawHeight. + // Since we will later multiply by the scale of individual triangles we normalize by + // dividing by triangleScale. + float movedDistance = -elapsedSeconds * Velocity * base_velocity / (DrawHeight * triangleScale); + + for (int i = 0; i < parts.Count; i++) + { + TriangleParticle newParticle = parts[i]; + + // Scale moved distance by the size of the triangle. Smaller triangles should move more slowly. + newParticle.Position.Y += parts[i].Scale * movedDistance; + newParticle.Colour.A = adjustedAlpha; + + parts[i] = newParticle; + + float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight; + if (bottomPos < 0) + parts.RemoveAt(i); + } + } + + private void addTriangles(bool randomY) + { + int aimTriangleCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio); + + for (int i = 0; i < aimTriangleCount - parts.Count; i++) + parts.Add(createTriangle(randomY)); + } + + private TriangleParticle createTriangle(bool randomY) + { + TriangleParticle particle = CreateTriangle(); + + particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1); + particle.Colour = CreateTriangleShade(); + + return particle; + } + + /// + /// Creates a triangle particle with a random scale. + /// + /// The triangle particle. + protected virtual TriangleParticle CreateTriangle() + { + const float std_dev = 0.16f; + const float mean = 0.5f; + + float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats + float u2 = 1 - RNG.NextSingle(); + float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); //random normal(0,1) + var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); //random normal(mean,stdDev^2) + + return new TriangleParticle { Scale = scale }; + } + + /// + /// Creates a shade of colour for the triangles. + /// + /// The colour. + protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1); + + protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); + + private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData(); + protected override void ApplyDrawNode(DrawNode node) + { + base.ApplyDrawNode(node); + + var trianglesNode = (TrianglesDrawNode)node; + + trianglesNode.Shader = shader; + trianglesNode.Texture = texture; + trianglesNode.Size = DrawSize; + trianglesNode.Shared = sharedData; + + trianglesNode.Parts.Clear(); + trianglesNode.Parts.AddRange(parts); + } + + private class TrianglesDrawNodeSharedData + { + public readonly LinearBatch VertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); + } + + private class TrianglesDrawNode : DrawNode + { + public Shader Shader; + public Texture Texture; + + public TrianglesDrawNodeSharedData Shared; + + public readonly List Parts = new List(); + public Vector2 Size; + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + Shader.Bind(); + Texture.TextureGL.Bind(); + + Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; + + foreach (TriangleParticle particle in Parts) + { + var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); + var size = new Vector2(2 * offset.X, offset.Y); + + var triangle = new Triangle( + Vector2Extensions.Transform(particle.Position * Size, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * Size + offset, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) + ); + + ColourInfo colourInfo = DrawInfo.Colour; + colourInfo.ApplyChild(particle.Colour); + + Texture.DrawTriangle( + triangle, + colourInfo, + null, + Shared.VertexBatch.AddAction, + Vector2.Divide(localInflationAmount, size)); + } + + Shader.Unbind(); + } + } + + protected struct TriangleParticle : IComparable + { + /// + /// The position of the top vertex of the triangle. + /// + public Vector2 Position; + + /// + /// The colour of the triangle. + /// + public Color4 Colour; + + /// + /// The scale of the triangle. + /// + public float Scale; + + /// + /// Compares two s. This is a reverse comparer because when the + /// triangles are added to the particles list, they should be drawn from largest to smallest + /// such that the smaller triangles appear on top. + /// + /// + /// + public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale); + } + } +} diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 5a5f283fb9..bf16af4706 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -1,86 +1,86 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Configuration; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Graphics.Containers -{ - public class BeatSyncedContainer : Container - { - protected readonly Bindable Beatmap = new Bindable(); - - private int lastBeat; - private TimingControlPoint lastTimingPoint; - - /// - /// The amount of time before a beat we should fire . - /// This allows for adding easing to animations that may be synchronised to the beat. - /// - protected double EarlyActivationMilliseconds; - - /// - /// The time in milliseconds until the next beat. - /// - public double TimeUntilNextBeat { get; private set; } - - /// - /// The time in milliseconds since the last beat - /// - public double TimeSinceLastBeat { get; private set; } - - protected override void Update() - { - if (!Beatmap.Value.TrackLoaded || !Beatmap.Value.BeatmapLoaded) return; - - var track = Beatmap.Value.Track; - var beatmap = Beatmap.Value.Beatmap; - - if (track == null || beatmap == null) - return; - - double currentTrackTime = track.Length > 0 ? track.CurrentTime + EarlyActivationMilliseconds : Clock.CurrentTime; - - TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); - EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); - - if (timingPoint.BeatLength == 0) - return; - - int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength); - - // The beats before the start of the first control point are off by 1, this should do the trick - if (currentTrackTime < timingPoint.Time) - beatIndex--; - - TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength; - if (TimeUntilNextBeat < 0) - TimeUntilNextBeat += timingPoint.BeatLength; - - TimeSinceLastBeat = timingPoint.BeatLength - TimeUntilNextBeat; - - if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat) - return; - - using (BeginDelayedSequence(-TimeSinceLastBeat, true)) - OnNewBeat(beatIndex, timingPoint, effectPoint, track.CurrentAmplitudes); - - lastBeat = beatIndex; - lastTimingPoint = timingPoint; - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - Beatmap.BindTo(game.Beatmap); - } - - protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Graphics.Containers +{ + public class BeatSyncedContainer : Container + { + protected readonly Bindable Beatmap = new Bindable(); + + private int lastBeat; + private TimingControlPoint lastTimingPoint; + + /// + /// The amount of time before a beat we should fire . + /// This allows for adding easing to animations that may be synchronised to the beat. + /// + protected double EarlyActivationMilliseconds; + + /// + /// The time in milliseconds until the next beat. + /// + public double TimeUntilNextBeat { get; private set; } + + /// + /// The time in milliseconds since the last beat + /// + public double TimeSinceLastBeat { get; private set; } + + protected override void Update() + { + if (!Beatmap.Value.TrackLoaded || !Beatmap.Value.BeatmapLoaded) return; + + var track = Beatmap.Value.Track; + var beatmap = Beatmap.Value.Beatmap; + + if (track == null || beatmap == null) + return; + + double currentTrackTime = track.Length > 0 ? track.CurrentTime + EarlyActivationMilliseconds : Clock.CurrentTime; + + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); + EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); + + if (timingPoint.BeatLength == 0) + return; + + int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength); + + // The beats before the start of the first control point are off by 1, this should do the trick + if (currentTrackTime < timingPoint.Time) + beatIndex--; + + TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength; + if (TimeUntilNextBeat < 0) + TimeUntilNextBeat += timingPoint.BeatLength; + + TimeSinceLastBeat = timingPoint.BeatLength - TimeUntilNextBeat; + + if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat) + return; + + using (BeginDelayedSequence(-TimeSinceLastBeat, true)) + OnNewBeat(beatIndex, timingPoint, effectPoint, track.CurrentAmplitudes); + + lastBeat = beatIndex; + lastTimingPoint = timingPoint; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + Beatmap.BindTo(game.Beatmap); + } + + protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + } + } +} diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index ad0b815957..7289bfe52c 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -1,62 +1,62 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Graphics.Containers -{ - /// - /// Display an icon that is forced to scale to the size of this container. - /// - public class ConstrainedIconContainer : CompositeDrawable - { - public Drawable Icon - { - get - { - return InternalChild; - } - - set - { - InternalChild = value; - } - } - - /// - /// Determines an edge effect of this . - /// Edge effects are e.g. glow or a shadow. - /// Only has an effect when is true. - /// - public new EdgeEffectParameters EdgeEffect - { - get { return base.EdgeEffect; } - set { base.EdgeEffect = value; } - } - - protected override void Update() - { - base.Update(); - if (InternalChildren.Count > 0 && InternalChild.DrawSize.X > 0) - { - // We're modifying scale here for a few reasons - // - Guarantees correctness if BorderWidth is being used - // - If we were to use RelativeSize/FillMode, we'd need to set the Icon's RelativeSizeAxes directly. - // We can't do this because we would need access to AutoSizeAxes to set it to none. - // Other issues come up along the way too, so it's not a good solution. - var fitScale = Math.Min(DrawSize.X / InternalChild.DrawSize.X, DrawSize.Y / InternalChild.DrawSize.Y); - InternalChild.Scale = new Vector2(fitScale); - InternalChild.Anchor = Anchor.Centre; - InternalChild.Origin = Anchor.Centre; - } - } - - public ConstrainedIconContainer() - { - Masking = true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// Display an icon that is forced to scale to the size of this container. + /// + public class ConstrainedIconContainer : CompositeDrawable + { + public Drawable Icon + { + get + { + return InternalChild; + } + + set + { + InternalChild = value; + } + } + + /// + /// Determines an edge effect of this . + /// Edge effects are e.g. glow or a shadow. + /// Only has an effect when is true. + /// + public new EdgeEffectParameters EdgeEffect + { + get { return base.EdgeEffect; } + set { base.EdgeEffect = value; } + } + + protected override void Update() + { + base.Update(); + if (InternalChildren.Count > 0 && InternalChild.DrawSize.X > 0) + { + // We're modifying scale here for a few reasons + // - Guarantees correctness if BorderWidth is being used + // - If we were to use RelativeSize/FillMode, we'd need to set the Icon's RelativeSizeAxes directly. + // We can't do this because we would need access to AutoSizeAxes to set it to none. + // Other issues come up along the way too, so it's not a good solution. + var fitScale = Math.Min(DrawSize.X / InternalChild.DrawSize.X, DrawSize.Y / InternalChild.DrawSize.Y); + InternalChild.Scale = new Vector2(fitScale); + InternalChild.Anchor = Anchor.Centre; + InternalChild.Origin = Anchor.Centre; + } + } + + public ConstrainedIconContainer() + { + Masking = true; + } + } +} diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 45feab3076..6121cd487d 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -1,111 +1,111 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Online.Chat; -using System; -using System.Diagnostics; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; -using System.Collections.Generic; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; - -namespace osu.Game.Graphics.Containers -{ - public class LinkFlowContainer : OsuTextFlowContainer - { - public LinkFlowContainer(Action defaultCreationParameters = null) - : base(defaultCreationParameters) - { - } - - public override bool HandleMouseInput => true; - - private OsuGame game; - private ChannelManager channelManager; - private Action showNotImplementedError; - - [BackgroundDependencyLoader(true)] - private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) - { - // will be null in tests - this.game = game; - this.channelManager = channelManager; - showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.fa_life_saver, - }); - } - - public void AddLinks(string text, List links) - { - if (string.IsNullOrEmpty(text) || links == null) - return; - - if (links.Count == 0) - { - AddText(text); - return; - } - - int previousLinkEnd = 0; - foreach (var link in links) - { - AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); - AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); - previousLinkEnd = link.Index + link.Length; - } - - AddText(text.Substring(previousLinkEnd)); - } - - public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) - { - AddInternal(new DrawableLinkCompiler(AddText(text).ToList()) - { - TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = () => - { - switch (linkType) - { - case LinkAction.OpenBeatmap: - // todo: replace this with overlay.ShowBeatmap(id) once an appropriate API call is implemented. - if (int.TryParse(linkArgument, out int beatmapId)) - Process.Start($"https://osu.ppy.sh/b/{beatmapId}"); - break; - case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) - game?.ShowBeatmapSet(setId); - break; - case LinkAction.OpenChannel: - try - { - channelManager.OpenChannel(linkArgument); - } - catch (ArgumentException) - { - //channel was not found - } - break; - case LinkAction.OpenEditorTimestamp: - case LinkAction.JoinMultiplayerMatch: - case LinkAction.Spectate: - showNotImplementedError?.Invoke(); - break; - case LinkAction.External: - Process.Start(url); - break; - case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) - game?.ShowUser(userId); - break; - default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); - } - }, - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Online.Chat; +using System; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using System.Collections.Generic; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Graphics.Containers +{ + public class LinkFlowContainer : OsuTextFlowContainer + { + public LinkFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) + { + } + + public override bool HandleMouseInput => true; + + private OsuGame game; + private ChannelManager channelManager; + private Action showNotImplementedError; + + [BackgroundDependencyLoader(true)] + private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) + { + // will be null in tests + this.game = game; + this.channelManager = channelManager; + showNotImplementedError = () => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.fa_life_saver, + }); + } + + public void AddLinks(string text, List links) + { + if (string.IsNullOrEmpty(text) || links == null) + return; + + if (links.Count == 0) + { + AddText(text); + return; + } + + int previousLinkEnd = 0; + foreach (var link in links) + { + AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); + AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); + previousLinkEnd = link.Index + link.Length; + } + + AddText(text.Substring(previousLinkEnd)); + } + + public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + { + AddInternal(new DrawableLinkCompiler(AddText(text).ToList()) + { + TooltipText = tooltipText ?? (url != text ? url : string.Empty), + Action = () => + { + switch (linkType) + { + case LinkAction.OpenBeatmap: + // todo: replace this with overlay.ShowBeatmap(id) once an appropriate API call is implemented. + if (int.TryParse(linkArgument, out int beatmapId)) + Process.Start($"https://osu.ppy.sh/b/{beatmapId}"); + break; + case LinkAction.OpenBeatmapSet: + if (int.TryParse(linkArgument, out int setId)) + game?.ShowBeatmapSet(setId); + break; + case LinkAction.OpenChannel: + try + { + channelManager.OpenChannel(linkArgument); + } + catch (ArgumentException) + { + //channel was not found + } + break; + case LinkAction.OpenEditorTimestamp: + case LinkAction.JoinMultiplayerMatch: + case LinkAction.Spectate: + showNotImplementedError?.Invoke(); + break; + case LinkAction.External: + Process.Start(url); + break; + case LinkAction.OpenUserProfile: + if (long.TryParse(linkArgument, out long userId)) + game?.ShowUser(userId); + break; + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + } + }, + }); + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index b9ee1f4463..cf80a549a4 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -1,42 +1,42 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Graphics.Containers -{ - public class OsuClickableContainer : ClickableContainer - { - private readonly HoverSampleSet sampleSet; - - private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; - - protected override Container Content => content; - - protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); - - public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal) - { - this.sampleSet = sampleSet; - } - - [BackgroundDependencyLoader] - private void load() - { - if (AutoSizeAxes != Axes.None) - { - content.RelativeSizeAxes = RelativeSizeAxes; - content.AutoSizeAxes = AutoSizeAxes; - } - - InternalChildren = new Drawable[] - { - content, - CreateHoverClickSounds(sampleSet) - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.Containers +{ + public class OsuClickableContainer : ClickableContainer + { + private readonly HoverSampleSet sampleSet; + + private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; + + protected override Container Content => content; + + protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); + + public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal) + { + this.sampleSet = sampleSet; + } + + [BackgroundDependencyLoader] + private void load() + { + if (AutoSizeAxes != Axes.None) + { + content.RelativeSizeAxes = RelativeSizeAxes; + content.AutoSizeAxes = AutoSizeAxes; + } + + InternalChildren = new Drawable[] + { + content, + CreateHoverClickSounds(sampleSet) + }; + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index ec461b86fd..2a30e0d032 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -1,73 +1,73 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using OpenTK; - -namespace osu.Game.Graphics.Containers -{ - public class OsuFocusedOverlayContainer : FocusedOverlayContainer - { - private SampleChannel samplePopIn; - private SampleChannel samplePopOut; - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in"); - samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out"); - - StateChanged += onStateChanged; - } - - /// - /// Whether mouse input should be blocked screen-wide while this overlay is visible. - /// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through. - /// - public virtual bool BlockScreenWideMouse => BlockPassThroughMouse; - - // receive input outside our bounds so we can trigger a close event on ourselves. - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceiveMouseInputAt(screenSpacePos); - - protected override bool OnClick(InputState state) - { - if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) - { - State = Visibility.Hidden; - return true; - } - - return base.OnClick(state); - } - - protected override bool OnDragStart(InputState state) - { - if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) - { - State = Visibility.Hidden; - return true; - } - - return base.OnDragStart(state); - } - - protected override bool OnDrag(InputState state) => State == Visibility.Hidden; - - private void onStateChanged(Visibility visibility) - { - switch (visibility) - { - case Visibility.Visible: - samplePopIn?.Play(); - break; - case Visibility.Hidden: - samplePopOut?.Play(); - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using OpenTK; + +namespace osu.Game.Graphics.Containers +{ + public class OsuFocusedOverlayContainer : FocusedOverlayContainer + { + private SampleChannel samplePopIn; + private SampleChannel samplePopOut; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in"); + samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out"); + + StateChanged += onStateChanged; + } + + /// + /// Whether mouse input should be blocked screen-wide while this overlay is visible. + /// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through. + /// + public virtual bool BlockScreenWideMouse => BlockPassThroughMouse; + + // receive input outside our bounds so we can trigger a close event on ourselves. + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceiveMouseInputAt(screenSpacePos); + + protected override bool OnClick(InputState state) + { + if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) + { + State = Visibility.Hidden; + return true; + } + + return base.OnClick(state); + } + + protected override bool OnDragStart(InputState state) + { + if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) + { + State = Visibility.Hidden; + return true; + } + + return base.OnDragStart(state); + } + + protected override bool OnDrag(InputState state) => State == Visibility.Hidden; + + private void onStateChanged(Visibility visibility) + { + switch (visibility) + { + case Visibility.Visible: + samplePopIn?.Play(); + break; + case Visibility.Hidden: + samplePopOut?.Play(); + break; + } + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index fd1742871b..e88dad93ef 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -1,45 +1,45 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Input; - -namespace osu.Game.Graphics.Containers -{ - public class OsuHoverContainer : OsuClickableContainer - { - protected Color4 HoverColour; - - protected Color4 IdleColour = Color4.White; - - protected virtual IEnumerable EffectTargets => new[] { Content }; - - protected override bool OnHover(InputState state) - { - EffectTargets.ForEach(d => d.FadeColour(HoverColour, 500, Easing.OutQuint)); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - EffectTargets.ForEach(d => d.FadeColour(IdleColour, 500, Easing.OutQuint)); - base.OnHoverLost(state); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - HoverColour = colours.Yellow; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - EffectTargets.ForEach(d => d.FadeColour(IdleColour)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Input; + +namespace osu.Game.Graphics.Containers +{ + public class OsuHoverContainer : OsuClickableContainer + { + protected Color4 HoverColour; + + protected Color4 IdleColour = Color4.White; + + protected virtual IEnumerable EffectTargets => new[] { Content }; + + protected override bool OnHover(InputState state) + { + EffectTargets.ForEach(d => d.FadeColour(HoverColour, 500, Easing.OutQuint)); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + EffectTargets.ForEach(d => d.FadeColour(IdleColour, 500, Easing.OutQuint)); + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + HoverColour = colours.Yellow; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + EffectTargets.ForEach(d => d.FadeColour(IdleColour)); + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 1d5a2af899..ebea9c49de 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -1,75 +1,75 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using OpenTK.Input; - -namespace osu.Game.Graphics.Containers -{ - public class OsuScrollContainer : ScrollContainer - { - /// - /// Allows controlling the scroll bar from any position in the container using the right mouse button. - /// Uses the value of to smoothly scroll to the dragged location. - /// - public bool RightMouseScrollbar = false; - - /// - /// Controls the rate with which the target position is approached when performing a relative drag. Default is 0.02. - /// - public double DistanceDecayOnRightMouseScrollbar = 0.02; - - private bool shouldPerformRightMouseScroll(InputState state) => RightMouseScrollbar && state.Mouse.IsPressed(MouseButton.Right); - - private void scrollToRelative(float value) => ScrollTo(Clamp((value - Scrollbar.DrawSize[ScrollDim] / 2) / Scrollbar.Size[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar); - - private bool mouseScrollBarDragging; - - protected override bool IsDragging => base.IsDragging || mouseScrollBarDragging; - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - if (shouldPerformRightMouseScroll(state)) - { - scrollToRelative(state.Mouse.Position[ScrollDim]); - return true; - } - - return base.OnMouseDown(state, args); - } - - protected override bool OnDrag(InputState state) - { - if (mouseScrollBarDragging) - { - scrollToRelative(state.Mouse.Position[ScrollDim]); - return true; - } - - return base.OnDrag(state); - } - - protected override bool OnDragStart(InputState state) - { - if (shouldPerformRightMouseScroll(state)) - { - mouseScrollBarDragging = true; - return true; - } - - return base.OnDragStart(state); - } - - protected override bool OnDragEnd(InputState state) - { - if (mouseScrollBarDragging) - { - mouseScrollBarDragging = false; - return true; - } - - return base.OnDragEnd(state); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using OpenTK.Input; + +namespace osu.Game.Graphics.Containers +{ + public class OsuScrollContainer : ScrollContainer + { + /// + /// Allows controlling the scroll bar from any position in the container using the right mouse button. + /// Uses the value of to smoothly scroll to the dragged location. + /// + public bool RightMouseScrollbar = false; + + /// + /// Controls the rate with which the target position is approached when performing a relative drag. Default is 0.02. + /// + public double DistanceDecayOnRightMouseScrollbar = 0.02; + + private bool shouldPerformRightMouseScroll(InputState state) => RightMouseScrollbar && state.Mouse.IsPressed(MouseButton.Right); + + private void scrollToRelative(float value) => ScrollTo(Clamp((value - Scrollbar.DrawSize[ScrollDim] / 2) / Scrollbar.Size[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar); + + private bool mouseScrollBarDragging; + + protected override bool IsDragging => base.IsDragging || mouseScrollBarDragging; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + if (shouldPerformRightMouseScroll(state)) + { + scrollToRelative(state.Mouse.Position[ScrollDim]); + return true; + } + + return base.OnMouseDown(state, args); + } + + protected override bool OnDrag(InputState state) + { + if (mouseScrollBarDragging) + { + scrollToRelative(state.Mouse.Position[ScrollDim]); + return true; + } + + return base.OnDrag(state); + } + + protected override bool OnDragStart(InputState state) + { + if (shouldPerformRightMouseScroll(state)) + { + mouseScrollBarDragging = true; + return true; + } + + return base.OnDragStart(state); + } + + protected override bool OnDragEnd(InputState state) + { + if (mouseScrollBarDragging) + { + mouseScrollBarDragging = false; + return true; + } + + return base.OnDragEnd(state); + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index 51adf5f232..119af4d762 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Graphics.Containers -{ - public class OsuTextFlowContainer : TextFlowContainer - { - public OsuTextFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) - { - } - - protected override SpriteText CreateSpriteText() => new OsuSpriteText(); - - public void AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.Containers +{ + public class OsuTextFlowContainer : TextFlowContainer + { + public OsuTextFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) + { + } + + protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + + public void AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); + } +} diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 97d6225534..dc635ce7e7 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -1,78 +1,78 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Framework.Input; -using OpenTK; -using osu.Framework.Allocation; -using osu.Game.Configuration; -using osu.Framework.Configuration; -using osu.Framework.MathUtils; - -namespace osu.Game.Graphics.Containers -{ - public class ParallaxContainer : Container, IRequireHighFrequencyMousePosition - { - public const float DEFAULT_PARALLAX_AMOUNT = 0.02f; - - public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT; - - private Bindable parallaxEnabled; - - public ParallaxContainer() - { - RelativeSizeAxes = Axes.Both; - AddInternal(content = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }); - } - - private readonly Container content; - private InputManager input; - - protected override Container Content => content; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - parallaxEnabled = config.GetBindable(OsuSetting.MenuParallax); - parallaxEnabled.ValueChanged += delegate - { - if (!parallaxEnabled) - { - content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint); - content.Scale = new Vector2(1 + ParallaxAmount); - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - input = GetContainingInputManager(); - } - - private bool firstUpdate = true; - - protected override void Update() - { - base.Update(); - - if (parallaxEnabled) - { - Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount; - - double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); - - content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint); - content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + ParallaxAmount), 0, 1000, Easing.OutQuint); - } - - firstUpdate = false; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Input; +using OpenTK; +using osu.Framework.Allocation; +using osu.Game.Configuration; +using osu.Framework.Configuration; +using osu.Framework.MathUtils; + +namespace osu.Game.Graphics.Containers +{ + public class ParallaxContainer : Container, IRequireHighFrequencyMousePosition + { + public const float DEFAULT_PARALLAX_AMOUNT = 0.02f; + + public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT; + + private Bindable parallaxEnabled; + + public ParallaxContainer() + { + RelativeSizeAxes = Axes.Both; + AddInternal(content = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + } + + private readonly Container content; + private InputManager input; + + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + parallaxEnabled = config.GetBindable(OsuSetting.MenuParallax); + parallaxEnabled.ValueChanged += delegate + { + if (!parallaxEnabled) + { + content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint); + content.Scale = new Vector2(1 + ParallaxAmount); + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + input = GetContainingInputManager(); + } + + private bool firstUpdate = true; + + protected override void Update() + { + base.Update(); + + if (parallaxEnabled) + { + Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount; + + double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); + + content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint); + content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + ParallaxAmount), 0, 1000, Easing.OutQuint); + } + + firstUpdate = false; + } + } +} diff --git a/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs b/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs index 5803c8a5db..e6dcb336e7 100644 --- a/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Graphics.Containers -{ - public class ReverseChildIDFillFlowContainer : FillFlowContainer where T : Drawable - { - protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Graphics.Containers +{ + public class ReverseChildIDFillFlowContainer : FillFlowContainer where T : Drawable + { + protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y); + } +} diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index acb74588fd..36fdbe6e94 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -1,194 +1,194 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Graphics.Containers -{ - /// - /// A container that can scroll to each section inside it. - /// - public class SectionsContainer : Container - where T : Drawable - { - private Drawable expandableHeader, fixedHeader, footer, headerBackground; - private readonly ScrollContainer scrollContainer; - private readonly Container headerBackgroundContainer; - private readonly FlowContainer scrollContentContainer; - - protected override Container Content => scrollContentContainer; - - public Drawable ExpandableHeader - { - get { return expandableHeader; } - set - { - if (value == expandableHeader) return; - - expandableHeader?.Expire(); - expandableHeader = value; - if (value == null) return; - - AddInternal(expandableHeader); - lastKnownScroll = float.NaN; - } - } - - public Drawable FixedHeader - { - get { return fixedHeader; } - set - { - if (value == fixedHeader) return; - - fixedHeader?.Expire(); - fixedHeader = value; - if (value == null) return; - - AddInternal(fixedHeader); - lastKnownScroll = float.NaN; - } - } - - public Drawable Footer - { - get { return footer; } - set - { - if (value == footer) return; - - if (footer != null) - scrollContainer.Remove(footer); - footer = value; - if (value == null) return; - - footer.Anchor |= Anchor.y2; - footer.Origin |= Anchor.y2; - scrollContainer.Add(footer); - lastKnownScroll = float.NaN; - } - } - - public Drawable HeaderBackground - { - get { return headerBackground; } - set - { - if (value == headerBackground) return; - - headerBackgroundContainer.Clear(); - headerBackground = value; - if (value == null) return; - - headerBackgroundContainer.Add(headerBackground); - - lastKnownScroll = float.NaN; - } - } - - public Bindable SelectedSection { get; } = new Bindable(); - - protected virtual FlowContainer CreateScrollContentContainer() - => new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }; - - public override void Add(T drawable) - { - base.Add(drawable); - lastKnownScroll = float.NaN; - headerHeight = float.NaN; - footerHeight = float.NaN; - } - - private float headerHeight, footerHeight; - private readonly MarginPadding originalSectionsMargin; - private void updateSectionsMargin() - { - if (!Children.Any()) return; - - var newMargin = originalSectionsMargin; - newMargin.Top += headerHeight; - newMargin.Bottom += footerHeight; - - scrollContentContainer.Margin = newMargin; - } - - public SectionsContainer() - { - AddInternal(scrollContainer = new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - ScrollbarVisible = false, - Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() } - }); - AddInternal(headerBackgroundContainer = new Container - { - RelativeSizeAxes = Axes.X - }); - originalSectionsMargin = scrollContentContainer.Margin; - } - - public void ScrollTo(Drawable section) => scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - (FixedHeader?.BoundingBox.Height ?? 0)); - - public void ScrollToTop() => scrollContainer.ScrollTo(0); - - private float lastKnownScroll; - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); - float footerH = Footer?.LayoutSize.Y ?? 0; - if (headerH != headerHeight || footerH != footerHeight) - { - headerHeight = headerH; - footerHeight = footerH; - updateSectionsMargin(); - } - - float currentScroll = scrollContainer.Current; - - if (currentScroll != lastKnownScroll) - { - lastKnownScroll = currentScroll; - - if (ExpandableHeader != null && FixedHeader != null) - { - float offset = Math.Min(ExpandableHeader.LayoutSize.Y, currentScroll); - - ExpandableHeader.Y = -offset; - FixedHeader.Y = -offset + ExpandableHeader.LayoutSize.Y; - } - - headerBackgroundContainer.Height = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); - headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0; - - T bestMatch = null; - float minDiff = float.MaxValue; - float scrollOffset = FixedHeader?.LayoutSize.Y ?? 0; - - foreach (var section in Children) - { - float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset); - if (diff < minDiff) - { - minDiff = diff; - bestMatch = section; - } - } - - if (bestMatch != null) - SelectedSection.Value = bestMatch; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that can scroll to each section inside it. + /// + public class SectionsContainer : Container + where T : Drawable + { + private Drawable expandableHeader, fixedHeader, footer, headerBackground; + private readonly ScrollContainer scrollContainer; + private readonly Container headerBackgroundContainer; + private readonly FlowContainer scrollContentContainer; + + protected override Container Content => scrollContentContainer; + + public Drawable ExpandableHeader + { + get { return expandableHeader; } + set + { + if (value == expandableHeader) return; + + expandableHeader?.Expire(); + expandableHeader = value; + if (value == null) return; + + AddInternal(expandableHeader); + lastKnownScroll = float.NaN; + } + } + + public Drawable FixedHeader + { + get { return fixedHeader; } + set + { + if (value == fixedHeader) return; + + fixedHeader?.Expire(); + fixedHeader = value; + if (value == null) return; + + AddInternal(fixedHeader); + lastKnownScroll = float.NaN; + } + } + + public Drawable Footer + { + get { return footer; } + set + { + if (value == footer) return; + + if (footer != null) + scrollContainer.Remove(footer); + footer = value; + if (value == null) return; + + footer.Anchor |= Anchor.y2; + footer.Origin |= Anchor.y2; + scrollContainer.Add(footer); + lastKnownScroll = float.NaN; + } + } + + public Drawable HeaderBackground + { + get { return headerBackground; } + set + { + if (value == headerBackground) return; + + headerBackgroundContainer.Clear(); + headerBackground = value; + if (value == null) return; + + headerBackgroundContainer.Add(headerBackground); + + lastKnownScroll = float.NaN; + } + } + + public Bindable SelectedSection { get; } = new Bindable(); + + protected virtual FlowContainer CreateScrollContentContainer() + => new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + + public override void Add(T drawable) + { + base.Add(drawable); + lastKnownScroll = float.NaN; + headerHeight = float.NaN; + footerHeight = float.NaN; + } + + private float headerHeight, footerHeight; + private readonly MarginPadding originalSectionsMargin; + private void updateSectionsMargin() + { + if (!Children.Any()) return; + + var newMargin = originalSectionsMargin; + newMargin.Top += headerHeight; + newMargin.Bottom += footerHeight; + + scrollContentContainer.Margin = newMargin; + } + + public SectionsContainer() + { + AddInternal(scrollContainer = new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + ScrollbarVisible = false, + Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() } + }); + AddInternal(headerBackgroundContainer = new Container + { + RelativeSizeAxes = Axes.X + }); + originalSectionsMargin = scrollContentContainer.Margin; + } + + public void ScrollTo(Drawable section) => scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - (FixedHeader?.BoundingBox.Height ?? 0)); + + public void ScrollToTop() => scrollContainer.ScrollTo(0); + + private float lastKnownScroll; + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); + float footerH = Footer?.LayoutSize.Y ?? 0; + if (headerH != headerHeight || footerH != footerHeight) + { + headerHeight = headerH; + footerHeight = footerH; + updateSectionsMargin(); + } + + float currentScroll = scrollContainer.Current; + + if (currentScroll != lastKnownScroll) + { + lastKnownScroll = currentScroll; + + if (ExpandableHeader != null && FixedHeader != null) + { + float offset = Math.Min(ExpandableHeader.LayoutSize.Y, currentScroll); + + ExpandableHeader.Y = -offset; + FixedHeader.Y = -offset + ExpandableHeader.LayoutSize.Y; + } + + headerBackgroundContainer.Height = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); + headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0; + + T bestMatch = null; + float minDiff = float.MaxValue; + float scrollOffset = FixedHeader?.LayoutSize.Y ?? 0; + + foreach (var section in Children) + { + float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset); + if (diff < minDiff) + { + minDiff = diff; + bestMatch = section; + } + } + + if (bestMatch != null) + SelectedSection.Value = bestMatch; + } + } + } +} diff --git a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs b/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs index 0fae4579fa..81ae3198c7 100644 --- a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs +++ b/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs @@ -1,67 +1,67 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Input; - -namespace osu.Game.Graphics.Cursor -{ - /// - /// A container which provides a which can be overridden by hovered s. - /// - public class CursorOverrideContainer : Container, IProvideCursor - { - protected override Container Content => content; - private readonly Container content; - - /// - /// Whether any cursors can be displayed. - /// - public bool CanShowCursor = true; - - public CursorContainer Cursor { get; } - public bool ProvidingUserCursor => true; - - public CursorOverrideContainer() - { - AddRangeInternal(new Drawable[] - { - Cursor = new MenuCursor { State = Visibility.Hidden }, - content = new Container { RelativeSizeAxes = Axes.Both } - }); - } - - private InputManager inputManager; - - protected override void LoadComplete() - { - base.LoadComplete(); - inputManager = GetContainingInputManager(); - } - - private IProvideCursor currentTarget; - protected override void Update() - { - base.Update(); - - if (!CanShowCursor) - { - currentTarget?.Cursor?.Hide(); - return; - } - - var newTarget = inputManager.HoveredDrawables.OfType().FirstOrDefault(t => t.ProvidingUserCursor) ?? this; - - if (currentTarget == newTarget) - return; - - currentTarget?.Cursor?.Hide(); - newTarget.Cursor?.Show(); - - currentTarget = newTarget; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; + +namespace osu.Game.Graphics.Cursor +{ + /// + /// A container which provides a which can be overridden by hovered s. + /// + public class CursorOverrideContainer : Container, IProvideCursor + { + protected override Container Content => content; + private readonly Container content; + + /// + /// Whether any cursors can be displayed. + /// + public bool CanShowCursor = true; + + public CursorContainer Cursor { get; } + public bool ProvidingUserCursor => true; + + public CursorOverrideContainer() + { + AddRangeInternal(new Drawable[] + { + Cursor = new MenuCursor { State = Visibility.Hidden }, + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + private InputManager inputManager; + + protected override void LoadComplete() + { + base.LoadComplete(); + inputManager = GetContainingInputManager(); + } + + private IProvideCursor currentTarget; + protected override void Update() + { + base.Update(); + + if (!CanShowCursor) + { + currentTarget?.Cursor?.Hide(); + return; + } + + var newTarget = inputManager.HoveredDrawables.OfType().FirstOrDefault(t => t.ProvidingUserCursor) ?? this; + + if (currentTarget == newTarget) + return; + + currentTarget?.Cursor?.Hide(); + newTarget.Cursor?.Show(); + + currentTarget = newTarget; + } + } +} diff --git a/osu.Game/Graphics/Cursor/IProvideCursor.cs b/osu.Game/Graphics/Cursor/IProvideCursor.cs index 91b44234fb..c7b23bbc00 100644 --- a/osu.Game/Graphics/Cursor/IProvideCursor.cs +++ b/osu.Game/Graphics/Cursor/IProvideCursor.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; - -namespace osu.Game.Graphics.Cursor -{ - /// - /// Interface for s that display cursors which can replace the user's cursor. - /// - public interface IProvideCursor : IDrawable - { - /// - /// The cursor provided by this . - /// May be null if no cursor should be visible. - /// - CursorContainer Cursor { get; } - - /// - /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. - /// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays). - /// - bool ProvidingUserCursor { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Graphics.Cursor +{ + /// + /// Interface for s that display cursors which can replace the user's cursor. + /// + public interface IProvideCursor : IDrawable + { + /// + /// The cursor provided by this . + /// May be null if no cursor should be visible. + /// + CursorContainer Cursor { get; } + + /// + /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. + /// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays). + /// + bool ProvidingUserCursor { get; } + } +} diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index bdee7d289d..34d815ec14 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -1,157 +1,157 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Configuration; -using System; -using System.Diagnostics; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Graphics.Cursor -{ - public class MenuCursor : CursorContainer - { - protected override Drawable CreateCursor() => new Cursor(); - - private Bindable cursorRotate; - private bool dragging; - - private bool startRotation; - - protected override bool OnMouseMove(InputState state) - { - if (cursorRotate && dragging) - { - Debug.Assert(state.Mouse.PositionMouseDown != null); - - // don't start rotating until we're moved a minimum distance away from the mouse down location, - // else it can have an annoying effect. - // ReSharper disable once PossibleInvalidOperationException - startRotation |= Vector2Extensions.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30; - - if (startRotation) - { - Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value; - float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f; - - // Always rotate in the direction of least distance - float diff = (degrees - ActiveCursor.Rotation) % 360; - if (diff < -180) diff += 360; - if (diff > 180) diff -= 360; - degrees = ActiveCursor.Rotation + diff; - - ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint); - } - } - - return base.OnMouseMove(state); - } - - protected override bool OnDragStart(InputState state) - { - dragging = true; - return base.OnDragStart(state); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - ActiveCursor.Scale = new Vector2(1); - ActiveCursor.ScaleTo(0.90f, 800, Easing.OutQuint); - - ((Cursor)ActiveCursor).AdditiveLayer.Alpha = 0; - ((Cursor)ActiveCursor).AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - if (!state.Mouse.HasMainButtonPressed) - { - dragging = false; - startRotation = false; - - ((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint); - ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf); - ActiveCursor.ScaleTo(1, 500, Easing.OutElastic); - } - - return base.OnMouseUp(state, args); - } - - protected override bool OnClick(InputState state) - { - ((Cursor)ActiveCursor).AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); - - return base.OnClick(state); - } - - protected override void PopIn() - { - ActiveCursor.FadeTo(1, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); - } - - protected override void PopOut() - { - ActiveCursor.FadeTo(0, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(0.6f, 250, Easing.In); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - cursorRotate = config.GetBindable(OsuSetting.CursorRotation); - } - - public class Cursor : Container - { - private Container cursorContainer; - private Bindable cursorScale; - private const float base_scale = 0.15f; - - public Sprite AdditiveLayer; - - public Cursor() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, TextureStore textures, OsuColour colour) - { - Children = new Drawable[] - { - cursorContainer = new Container - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Sprite - { - Texture = textures.Get(@"Cursor/menu-cursor"), - }, - AdditiveLayer = new Sprite - { - Blending = BlendingMode.Additive, - Colour = colour.Pink, - Alpha = 0, - Texture = textures.Get(@"Cursor/menu-cursor-additive"), - }, - } - } - }; - - cursorScale = config.GetBindable(OsuSetting.MenuCursorSize); - cursorScale.ValueChanged += newScale => cursorContainer.Scale = new Vector2((float)newScale * base_scale); - cursorScale.TriggerChange(); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Configuration; +using System; +using System.Diagnostics; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Graphics.Cursor +{ + public class MenuCursor : CursorContainer + { + protected override Drawable CreateCursor() => new Cursor(); + + private Bindable cursorRotate; + private bool dragging; + + private bool startRotation; + + protected override bool OnMouseMove(InputState state) + { + if (cursorRotate && dragging) + { + Debug.Assert(state.Mouse.PositionMouseDown != null); + + // don't start rotating until we're moved a minimum distance away from the mouse down location, + // else it can have an annoying effect. + // ReSharper disable once PossibleInvalidOperationException + startRotation |= Vector2Extensions.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30; + + if (startRotation) + { + Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value; + float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f; + + // Always rotate in the direction of least distance + float diff = (degrees - ActiveCursor.Rotation) % 360; + if (diff < -180) diff += 360; + if (diff > 180) diff -= 360; + degrees = ActiveCursor.Rotation + diff; + + ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint); + } + } + + return base.OnMouseMove(state); + } + + protected override bool OnDragStart(InputState state) + { + dragging = true; + return base.OnDragStart(state); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + ActiveCursor.Scale = new Vector2(1); + ActiveCursor.ScaleTo(0.90f, 800, Easing.OutQuint); + + ((Cursor)ActiveCursor).AdditiveLayer.Alpha = 0; + ((Cursor)ActiveCursor).AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + if (!state.Mouse.HasMainButtonPressed) + { + dragging = false; + startRotation = false; + + ((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint); + ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf); + ActiveCursor.ScaleTo(1, 500, Easing.OutElastic); + } + + return base.OnMouseUp(state, args); + } + + protected override bool OnClick(InputState state) + { + ((Cursor)ActiveCursor).AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); + + return base.OnClick(state); + } + + protected override void PopIn() + { + ActiveCursor.FadeTo(1, 250, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + } + + protected override void PopOut() + { + ActiveCursor.FadeTo(0, 250, Easing.OutQuint); + ActiveCursor.ScaleTo(0.6f, 250, Easing.In); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + cursorRotate = config.GetBindable(OsuSetting.CursorRotation); + } + + public class Cursor : Container + { + private Container cursorContainer; + private Bindable cursorScale; + private const float base_scale = 0.15f; + + public Sprite AdditiveLayer; + + public Cursor() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, TextureStore textures, OsuColour colour) + { + Children = new Drawable[] + { + cursorContainer = new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Sprite + { + Texture = textures.Get(@"Cursor/menu-cursor"), + }, + AdditiveLayer = new Sprite + { + Blending = BlendingMode.Additive, + Colour = colour.Pink, + Alpha = 0, + Texture = textures.Get(@"Cursor/menu-cursor-additive"), + }, + } + } + }; + + cursorScale = config.GetBindable(OsuSetting.MenuCursorSize); + cursorScale.ValueChanged += newScale => cursorContainer.Scale = new Vector2((float)newScale * base_scale); + cursorScale.TriggerChange(); + } + } + } +} diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index d8910c5398..9408d63573 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Graphics.Cursor -{ - public class OsuContextMenuContainer : ContextMenuContainer - { - protected override Menu CreateMenu() => new OsuContextMenu(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.Cursor +{ + public class OsuContextMenuContainer : ContextMenuContainer + { + protected override Menu CreateMenu() => new OsuContextMenu(); + } +} diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 3706f31ded..c0e331148d 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -1,105 +1,105 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Graphics.Cursor -{ - public class OsuTooltipContainer : TooltipContainer - { - protected override ITooltip CreateTooltip() => new OsuTooltip(); - - public OsuTooltipContainer(CursorContainer cursor) : base(cursor) - { - } - - public class OsuTooltip : Tooltip - { - private readonly Box background; - private readonly OsuSpriteText text; - private bool instantMovement = true; - - public override string TooltipText - { - set - { - if (value == text.Text) return; - - text.Text = value; - if (IsPresent) - { - AutoSizeDuration = 250; - background.FlashColour(OsuColour.Gray(0.4f), 1000, Easing.OutQuint); - } - else - AutoSizeDuration = 0; - } - } - - private const float text_size = 16; - - public OsuTooltip() - { - AutoSizeEasing = Easing.OutQuint; - - CornerRadius = 5; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(40), - Radius = 5, - }; - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.9f, - }, - text = new OsuSpriteText - { - TextSize = text_size, - Padding = new MarginPadding(5), - Font = @"Exo2.0-Regular", - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - background.Colour = colour.Gray3; - } - - protected override void PopIn() - { - instantMovement |= !IsPresent; - this.FadeIn(500, Easing.OutQuint); - } - - protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint); - - public override void Move(Vector2 pos) - { - if (instantMovement) - { - Position = pos; - instantMovement = false; - } - else - { - this.MoveTo(pos, 200, Easing.OutQuint); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.Cursor +{ + public class OsuTooltipContainer : TooltipContainer + { + protected override ITooltip CreateTooltip() => new OsuTooltip(); + + public OsuTooltipContainer(CursorContainer cursor) : base(cursor) + { + } + + public class OsuTooltip : Tooltip + { + private readonly Box background; + private readonly OsuSpriteText text; + private bool instantMovement = true; + + public override string TooltipText + { + set + { + if (value == text.Text) return; + + text.Text = value; + if (IsPresent) + { + AutoSizeDuration = 250; + background.FlashColour(OsuColour.Gray(0.4f), 1000, Easing.OutQuint); + } + else + AutoSizeDuration = 0; + } + } + + private const float text_size = 16; + + public OsuTooltip() + { + AutoSizeEasing = Easing.OutQuint; + + CornerRadius = 5; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(40), + Radius = 5, + }; + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.9f, + }, + text = new OsuSpriteText + { + TextSize = text_size, + Padding = new MarginPadding(5), + Font = @"Exo2.0-Regular", + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + background.Colour = colour.Gray3; + } + + protected override void PopIn() + { + instantMovement |= !IsPresent; + this.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint); + + public override void Move(Vector2 pos) + { + if (instantMovement) + { + Position = pos; + instantMovement = false; + } + else + { + this.MoveTo(pos, 200, Easing.OutQuint); + } + } + } + } +} diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index d2a7250aaa..763e57e397 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -1,65 +1,65 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using Humanizer; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Graphics -{ - public class DrawableDate : OsuSpriteText, IHasTooltip - { - private readonly DateTimeOffset date; - - public DrawableDate(DateTimeOffset date) - { - AutoSizeAxes = Axes.Both; - Font = "Exo2.0-RegularItalic"; - - this.date = date.ToLocalTime(); - } - - [BackgroundDependencyLoader] - private void load() - { - updateTime(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Scheduler.Add(updateTimeWithReschedule); - } - - private void updateTimeWithReschedule() - { - updateTime(); - - var diffToNow = DateTimeOffset.Now.Subtract(date); - - double timeUntilNextUpdate = 1000; - if (diffToNow.TotalSeconds > 60) - { - timeUntilNextUpdate *= 60; - if (diffToNow.TotalMinutes > 60) - { - timeUntilNextUpdate *= 60; - - if (diffToNow.TotalHours > 24) - timeUntilNextUpdate *= 24; - } - } - - Scheduler.AddDelayed(updateTimeWithReschedule, timeUntilNextUpdate); - } - - public override bool HandleMouseInput => true; - - private void updateTime() => Text = date.Humanize(); - - public string TooltipText => date.ToString(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics +{ + public class DrawableDate : OsuSpriteText, IHasTooltip + { + private readonly DateTimeOffset date; + + public DrawableDate(DateTimeOffset date) + { + AutoSizeAxes = Axes.Both; + Font = "Exo2.0-RegularItalic"; + + this.date = date.ToLocalTime(); + } + + [BackgroundDependencyLoader] + private void load() + { + updateTime(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Scheduler.Add(updateTimeWithReschedule); + } + + private void updateTimeWithReschedule() + { + updateTime(); + + var diffToNow = DateTimeOffset.Now.Subtract(date); + + double timeUntilNextUpdate = 1000; + if (diffToNow.TotalSeconds > 60) + { + timeUntilNextUpdate *= 60; + if (diffToNow.TotalMinutes > 60) + { + timeUntilNextUpdate *= 60; + + if (diffToNow.TotalHours > 24) + timeUntilNextUpdate *= 24; + } + } + + Scheduler.AddDelayed(updateTimeWithReschedule, timeUntilNextUpdate); + } + + public override bool HandleMouseInput => true; + + private void updateTime() => Text = date.Humanize(); + + public string TooltipText => date.ToString(); + } +} diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs index 84120764d1..64c240aa84 100644 --- a/osu.Game/Graphics/IHasAccentColour.cs +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; - -namespace osu.Game.Graphics -{ - /// - /// A type of drawable that has an accent colour. - /// The accent colour is used to colorize various objects inside a drawable - /// without colorizing the drawable itself. - /// - public interface IHasAccentColour : IDrawable - { - Color4 AccentColour { get; set; } - } - - public static class AccentedColourExtensions - { - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public static TransformSequence FadeAccent(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None) - where T : IHasAccentColour - => accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing); - - /// - /// Smoothly adjusts over time. - /// - /// A to which further transforms can be added. - public static TransformSequence FadeAccent(this TransformSequence t, Color4 newColour, double duration = 0, Easing easing = Easing.None) - where T : Drawable, IHasAccentColour - => t.Append(o => o.FadeAccent(newColour, duration, easing)); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Graphics +{ + /// + /// A type of drawable that has an accent colour. + /// The accent colour is used to colorize various objects inside a drawable + /// without colorizing the drawable itself. + /// + public interface IHasAccentColour : IDrawable + { + Color4 AccentColour { get; set; } + } + + public static class AccentedColourExtensions + { + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public static TransformSequence FadeAccent(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None) + where T : IHasAccentColour + => accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public static TransformSequence FadeAccent(this TransformSequence t, Color4 newColour, double duration = 0, Easing easing = Easing.None) + where T : Drawable, IHasAccentColour + => t.Append(o => o.FadeAccent(newColour, duration, easing)); + } +} diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 8e24807eaa..7236248f18 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -1,96 +1,96 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK.Graphics; - -namespace osu.Game.Graphics -{ - public class OsuColour - { - public static Color4 Gray(float amt) => new Color4(amt, amt, amt, 1f); - public static Color4 Gray(byte amt) => new Color4(amt, amt, amt, 255); - - public static Color4 FromHex(string hex) - { - if (hex[0] == '#') - hex = hex.Substring(1); - - switch (hex.Length) - { - default: - throw new ArgumentException(@"Invalid hex string length!"); - case 3: - return new Color4( - (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), - (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), - (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), - 255); - case 6: - return new Color4( - Convert.ToByte(hex.Substring(0, 2), 16), - Convert.ToByte(hex.Substring(2, 2), 16), - Convert.ToByte(hex.Substring(4, 2), 16), - 255); - } - } - - // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less - public readonly Color4 PurpleLighter = FromHex(@"eeeeff"); - public readonly Color4 PurpleLight = FromHex(@"aa88ff"); - public readonly Color4 Purple = FromHex(@"8866ee"); - public readonly Color4 PurpleDark = FromHex(@"6644cc"); - public readonly Color4 PurpleDarker = FromHex(@"441188"); - - public readonly Color4 PinkLighter = FromHex(@"ffddee"); - public readonly Color4 PinkLight = FromHex(@"ff99cc"); - public readonly Color4 Pink = FromHex(@"ff66aa"); - public readonly Color4 PinkDark = FromHex(@"cc5288"); - public readonly Color4 PinkDarker = FromHex(@"bb1177"); - - public readonly Color4 BlueLighter = FromHex(@"ddffff"); - public readonly Color4 BlueLight = FromHex(@"99eeff"); - public readonly Color4 Blue = FromHex(@"66ccff"); - public readonly Color4 BlueDark = FromHex(@"44aadd"); - public readonly Color4 BlueDarker = FromHex(@"2299bb"); - - public readonly Color4 YellowLighter = FromHex(@"ffffdd"); - public readonly Color4 YellowLight = FromHex(@"ffdd55"); - public readonly Color4 Yellow = FromHex(@"ffcc22"); - public readonly Color4 YellowDark = FromHex(@"eeaa00"); - public readonly Color4 YellowDarker = FromHex(@"cc6600"); - - public readonly Color4 GreenLighter = FromHex(@"eeffcc"); - public readonly Color4 GreenLight = FromHex(@"b3d944"); - public readonly Color4 Green = FromHex(@"88b300"); - public readonly Color4 GreenDark = FromHex(@"668800"); - public readonly Color4 GreenDarker = FromHex(@"445500"); - - public readonly Color4 Gray0 = FromHex(@"000"); - public readonly Color4 Gray1 = FromHex(@"111"); - public readonly Color4 Gray2 = FromHex(@"222"); - public readonly Color4 Gray3 = FromHex(@"333"); - public readonly Color4 Gray4 = FromHex(@"444"); - public readonly Color4 Gray5 = FromHex(@"555"); - public readonly Color4 Gray6 = FromHex(@"666"); - public readonly Color4 Gray7 = FromHex(@"777"); - public readonly Color4 Gray8 = FromHex(@"888"); - public readonly Color4 Gray9 = FromHex(@"999"); - public readonly Color4 GrayA = FromHex(@"aaa"); - public readonly Color4 GrayB = FromHex(@"bbb"); - public readonly Color4 GrayC = FromHex(@"ccc"); - public readonly Color4 GrayD = FromHex(@"ddd"); - public readonly Color4 GrayE = FromHex(@"eee"); - public readonly Color4 GrayF = FromHex(@"fff"); - - public readonly Color4 RedLighter = FromHex(@"ffeded"); - public readonly Color4 RedLight = FromHex(@"ed7787"); - public readonly Color4 Red = FromHex(@"ed1121"); - public readonly Color4 RedDark = FromHex(@"ba0011"); - public readonly Color4 RedDarker = FromHex(@"870000"); - - public readonly Color4 ChatBlue = FromHex(@"17292e"); - - public readonly Color4 ContextMenuGray = FromHex(@"223034"); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK.Graphics; + +namespace osu.Game.Graphics +{ + public class OsuColour + { + public static Color4 Gray(float amt) => new Color4(amt, amt, amt, 1f); + public static Color4 Gray(byte amt) => new Color4(amt, amt, amt, 255); + + public static Color4 FromHex(string hex) + { + if (hex[0] == '#') + hex = hex.Substring(1); + + switch (hex.Length) + { + default: + throw new ArgumentException(@"Invalid hex string length!"); + case 3: + return new Color4( + (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), + (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), + (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), + 255); + case 6: + return new Color4( + Convert.ToByte(hex.Substring(0, 2), 16), + Convert.ToByte(hex.Substring(2, 2), 16), + Convert.ToByte(hex.Substring(4, 2), 16), + 255); + } + } + + // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less + public readonly Color4 PurpleLighter = FromHex(@"eeeeff"); + public readonly Color4 PurpleLight = FromHex(@"aa88ff"); + public readonly Color4 Purple = FromHex(@"8866ee"); + public readonly Color4 PurpleDark = FromHex(@"6644cc"); + public readonly Color4 PurpleDarker = FromHex(@"441188"); + + public readonly Color4 PinkLighter = FromHex(@"ffddee"); + public readonly Color4 PinkLight = FromHex(@"ff99cc"); + public readonly Color4 Pink = FromHex(@"ff66aa"); + public readonly Color4 PinkDark = FromHex(@"cc5288"); + public readonly Color4 PinkDarker = FromHex(@"bb1177"); + + public readonly Color4 BlueLighter = FromHex(@"ddffff"); + public readonly Color4 BlueLight = FromHex(@"99eeff"); + public readonly Color4 Blue = FromHex(@"66ccff"); + public readonly Color4 BlueDark = FromHex(@"44aadd"); + public readonly Color4 BlueDarker = FromHex(@"2299bb"); + + public readonly Color4 YellowLighter = FromHex(@"ffffdd"); + public readonly Color4 YellowLight = FromHex(@"ffdd55"); + public readonly Color4 Yellow = FromHex(@"ffcc22"); + public readonly Color4 YellowDark = FromHex(@"eeaa00"); + public readonly Color4 YellowDarker = FromHex(@"cc6600"); + + public readonly Color4 GreenLighter = FromHex(@"eeffcc"); + public readonly Color4 GreenLight = FromHex(@"b3d944"); + public readonly Color4 Green = FromHex(@"88b300"); + public readonly Color4 GreenDark = FromHex(@"668800"); + public readonly Color4 GreenDarker = FromHex(@"445500"); + + public readonly Color4 Gray0 = FromHex(@"000"); + public readonly Color4 Gray1 = FromHex(@"111"); + public readonly Color4 Gray2 = FromHex(@"222"); + public readonly Color4 Gray3 = FromHex(@"333"); + public readonly Color4 Gray4 = FromHex(@"444"); + public readonly Color4 Gray5 = FromHex(@"555"); + public readonly Color4 Gray6 = FromHex(@"666"); + public readonly Color4 Gray7 = FromHex(@"777"); + public readonly Color4 Gray8 = FromHex(@"888"); + public readonly Color4 Gray9 = FromHex(@"999"); + public readonly Color4 GrayA = FromHex(@"aaa"); + public readonly Color4 GrayB = FromHex(@"bbb"); + public readonly Color4 GrayC = FromHex(@"ccc"); + public readonly Color4 GrayD = FromHex(@"ddd"); + public readonly Color4 GrayE = FromHex(@"eee"); + public readonly Color4 GrayF = FromHex(@"fff"); + + public readonly Color4 RedLighter = FromHex(@"ffeded"); + public readonly Color4 RedLight = FromHex(@"ed7787"); + public readonly Color4 Red = FromHex(@"ed1121"); + public readonly Color4 RedDark = FromHex(@"ba0011"); + public readonly Color4 RedDarker = FromHex(@"870000"); + + public readonly Color4 ChatBlue = FromHex(@"17292e"); + + public readonly Color4 ContextMenuGray = FromHex(@"223034"); + } +} diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index b0cd997837..5e0b9c9340 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -1,110 +1,110 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Drawing.Imaging; -using System.IO; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Platform; -using osu.Game.Configuration; -using osu.Game.Input.Bindings; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; - -namespace osu.Game.Graphics -{ - public class ScreenshotManager : Container, IKeyBindingHandler, IHandleGlobalInput - { - private Bindable screenshotFormat; - private GameHost host; - private Storage storage; - private NotificationOverlay notificationOverlay; - - private SampleChannel shutter; - - [BackgroundDependencyLoader] - private void load(GameHost host, OsuConfigManager config, Storage storage, NotificationOverlay notificationOverlay, AudioManager audio) - { - this.host = host; - this.storage = storage.GetStorageForDirectory(@"screenshots"); - this.notificationOverlay = notificationOverlay; - - screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); - - shutter = audio.Sample.Get("UI/shutter"); - } - - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.TakeScreenshot: - shutter.Play(); - TakeScreenshotAsync(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => false; - - public async void TakeScreenshotAsync() - { - using (var bitmap = await host.TakeScreenshotAsync()) - { - var fileName = getFileName(); - if (fileName == null) return; - - var stream = storage.GetStream(fileName, FileAccess.Write); - - switch (screenshotFormat.Value) - { - case ScreenshotFormat.Png: - bitmap.Save(stream, ImageFormat.Png); - break; - case ScreenshotFormat.Jpg: - bitmap.Save(stream, ImageFormat.Jpeg); - break; - default: - throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); - } - - notificationOverlay.Post(new SimpleNotification - { - Text = $"{fileName} saved!", - Activated = () => - { - storage.OpenInNativeExplorer(); - return true; - } - }); - } - } - - private string getFileName() - { - var dt = DateTime.Now; - var fileExt = screenshotFormat.ToString().ToLower(); - - var withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}"; - if (!storage.Exists(withoutIndex)) - return withoutIndex; - - for (ulong i = 1; i < ulong.MaxValue; i++) - { - var indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}"; - if (!storage.Exists(indexedName)) - return indexedName; - } - - return null; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Drawing.Imaging; +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Platform; +using osu.Game.Configuration; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Graphics +{ + public class ScreenshotManager : Container, IKeyBindingHandler, IHandleGlobalInput + { + private Bindable screenshotFormat; + private GameHost host; + private Storage storage; + private NotificationOverlay notificationOverlay; + + private SampleChannel shutter; + + [BackgroundDependencyLoader] + private void load(GameHost host, OsuConfigManager config, Storage storage, NotificationOverlay notificationOverlay, AudioManager audio) + { + this.host = host; + this.storage = storage.GetStorageForDirectory(@"screenshots"); + this.notificationOverlay = notificationOverlay; + + screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); + + shutter = audio.Sample.Get("UI/shutter"); + } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.TakeScreenshot: + shutter.Play(); + TakeScreenshotAsync(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => false; + + public async void TakeScreenshotAsync() + { + using (var bitmap = await host.TakeScreenshotAsync()) + { + var fileName = getFileName(); + if (fileName == null) return; + + var stream = storage.GetStream(fileName, FileAccess.Write); + + switch (screenshotFormat.Value) + { + case ScreenshotFormat.Png: + bitmap.Save(stream, ImageFormat.Png); + break; + case ScreenshotFormat.Jpg: + bitmap.Save(stream, ImageFormat.Jpeg); + break; + default: + throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); + } + + notificationOverlay.Post(new SimpleNotification + { + Text = $"{fileName} saved!", + Activated = () => + { + storage.OpenInNativeExplorer(); + return true; + } + }); + } + } + + private string getFileName() + { + var dt = DateTime.Now; + var fileExt = screenshotFormat.ToString().ToLower(); + + var withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}"; + if (!storage.Exists(withoutIndex)) + return withoutIndex; + + for (ulong i = 1; i < ulong.MaxValue; i++) + { + var indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}"; + if (!storage.Exists(indexedName)) + return indexedName; + } + + return null; + } + } +} diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs index 4324119481..6acd20719e 100644 --- a/osu.Game/Graphics/SpriteIcon.cs +++ b/osu.Game/Graphics/SpriteIcon.cs @@ -1,1009 +1,1009 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics; -using osu.Framework.IO.Stores; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Caching; - -namespace osu.Game.Graphics -{ - public class SpriteIcon : CompositeDrawable - { - private Sprite spriteShadow; - private Sprite spriteMain; - - private Cached layout = new Cached(); - private Container shadowVisibility; - - private FontStore store; - - [BackgroundDependencyLoader] - private void load(FontStore store) - { - this.store = store; - - InternalChildren = new Drawable[] - { - shadowVisibility = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = spriteShadow = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Y = 2, - Colour = new Color4(0f, 0f, 0f, 0.2f), - }, - Alpha = shadow ? 1 : 0, - }, - spriteMain = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - }, - }; - - updateTexture(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateTexture(); - } - - private FontAwesome loadedIcon; - private void updateTexture() - { - var loadableIcon = icon; - - if (loadableIcon == loadedIcon) return; - - var texture = store?.Get(((char)loadableIcon).ToString()); - - spriteMain.Texture = texture; - spriteShadow.Texture = texture; - - if (Size == Vector2.Zero) - Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); - - loadedIcon = loadableIcon; - } - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.Colour) > 0 && Shadow) - layout.Invalidate(); - return base.Invalidate(invalidation, source, shallPropagate); - } - - protected override void Update() - { - if (!layout.IsValid) - { - //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. - //squared result for quadratic fall-off seems to give the best result. - var avgColour = (Color4)DrawInfo.Colour.AverageColour; - - spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); - - layout.Validate(); - } - } - - private bool shadow; - public bool Shadow - { - get { return shadow; } - set - { - shadow = value; - if (shadowVisibility != null) - shadowVisibility.Alpha = value ? 1 : 0; - } - } - - private FontAwesome icon; - - public FontAwesome Icon - { - get - { - return icon; - } - - set - { - if (icon == value) return; - - icon = value; - if (IsLoaded) - updateTexture(); - } - } - } - - public enum FontAwesome - { - fa_500px = 0xf26e, - fa_address_book = 0xf2b9, - fa_address_book_o = 0xf2ba, - fa_address_card = 0xf2bb, - fa_address_card_o = 0xf2bc, - fa_adjust = 0xf042, - fa_adn = 0xf170, - fa_align_center = 0xf037, - fa_align_justify = 0xf039, - fa_align_left = 0xf036, - fa_align_right = 0xf038, - fa_amazon = 0xf270, - fa_ambulance = 0xf0f9, - fa_american_sign_language_interpreting = 0xf2a3, - fa_anchor = 0xf13d, - fa_android = 0xf17b, - fa_angellist = 0xf209, - fa_angle_double_down = 0xf103, - fa_angle_double_left = 0xf100, - fa_angle_double_right = 0xf101, - fa_angle_double_up = 0xf102, - fa_angle_down = 0xf107, - fa_angle_left = 0xf104, - fa_angle_right = 0xf105, - fa_angle_up = 0xf106, - fa_apple = 0xf179, - fa_archive = 0xf187, - fa_area_chart = 0xf1fe, - fa_arrow_circle_down = 0xf0ab, - fa_arrow_circle_left = 0xf0a8, - fa_arrow_circle_o_down = 0xf01a, - fa_arrow_circle_o_left = 0xf190, - fa_arrow_circle_o_right = 0xf18e, - fa_arrow_circle_o_up = 0xf01b, - fa_arrow_circle_right = 0xf0a9, - fa_arrow_circle_up = 0xf0aa, - fa_arrow_down = 0xf063, - fa_arrow_left = 0xf060, - fa_arrow_right = 0xf061, - fa_arrow_up = 0xf062, - fa_arrows = 0xf047, - fa_arrows_alt = 0xf0b2, - fa_arrows_h = 0xf07e, - fa_arrows_v = 0xf07d, - fa_asl_interpreting = 0xf2a3, - fa_assistive_listening_systems = 0xf2a2, - fa_asterisk = 0xf069, - fa_at = 0xf1fa, - fa_audio_description = 0xf29e, - fa_automobile = 0xf1b9, - fa_backward = 0xf04a, - fa_balance_scale = 0xf24e, - fa_ban = 0xf05e, - fa_bandcamp = 0xf2d5, - fa_bank = 0xf19c, - fa_bar_chart = 0xf080, - fa_bar_chart_o = 0xf080, - fa_barcode = 0xf02a, - fa_bars = 0xf0c9, - fa_bath = 0xf2cd, - fa_bathtub = 0xf2cd, - fa_battery = 0xf240, - fa_battery_0 = 0xf244, - fa_battery_1 = 0xf243, - fa_battery_2 = 0xf242, - fa_battery_3 = 0xf241, - fa_battery_4 = 0xf240, - fa_battery_empty = 0xf244, - fa_battery_full = 0xf240, - fa_battery_half = 0xf242, - fa_battery_quarter = 0xf243, - fa_battery_three_quarters = 0xf241, - fa_bed = 0xf236, - fa_beer = 0xf0fc, - fa_behance = 0xf1b4, - fa_behance_square = 0xf1b5, - fa_bell = 0xf0f3, - fa_bell_o = 0xf0a2, - fa_bell_slash = 0xf1f6, - fa_bell_slash_o = 0xf1f7, - fa_bicycle = 0xf206, - fa_binoculars = 0xf1e5, - fa_birthday_cake = 0xf1fd, - fa_bitbucket = 0xf171, - fa_bitbucket_square = 0xf172, - fa_bitcoin = 0xf15a, - fa_black_tie = 0xf27e, - fa_blind = 0xf29d, - fa_bluetooth = 0xf293, - fa_bluetooth_b = 0xf294, - fa_bold = 0xf032, - fa_bolt = 0xf0e7, - fa_bomb = 0xf1e2, - fa_book = 0xf02d, - fa_bookmark = 0xf02e, - fa_bookmark_o = 0xf097, - fa_braille = 0xf2a1, - fa_briefcase = 0xf0b1, - fa_btc = 0xf15a, - fa_bug = 0xf188, - fa_building = 0xf1ad, - fa_building_o = 0xf0f7, - fa_bullhorn = 0xf0a1, - fa_bullseye = 0xf140, - fa_bus = 0xf207, - fa_buysellads = 0xf20d, - fa_cab = 0xf1ba, - fa_calculator = 0xf1ec, - fa_calendar = 0xf073, - fa_calendar_check_o = 0xf274, - fa_calendar_minus_o = 0xf272, - fa_calendar_o = 0xf133, - fa_calendar_plus_o = 0xf271, - fa_calendar_times_o = 0xf273, - fa_camera = 0xf030, - fa_camera_retro = 0xf083, - fa_car = 0xf1b9, - fa_caret_down = 0xf0d7, - fa_caret_left = 0xf0d9, - fa_caret_right = 0xf0da, - fa_caret_square_o_down = 0xf150, - fa_caret_square_o_left = 0xf191, - fa_caret_square_o_right = 0xf152, - fa_caret_square_o_up = 0xf151, - fa_caret_up = 0xf0d8, - fa_cart_arrow_down = 0xf218, - fa_cart_plus = 0xf217, - fa_cc = 0xf20a, - fa_cc_amex = 0xf1f3, - fa_cc_diners_club = 0xf24c, - fa_cc_discover = 0xf1f2, - fa_cc_jcb = 0xf24b, - fa_cc_mastercard = 0xf1f1, - fa_cc_paypal = 0xf1f4, - fa_cc_stripe = 0xf1f5, - fa_cc_visa = 0xf1f0, - fa_certificate = 0xf0a3, - fa_chain = 0xf0c1, - fa_chain_broken = 0xf127, - fa_check = 0xf00c, - fa_check_circle = 0xf058, - fa_check_circle_o = 0xf05d, - fa_check_square = 0xf14a, - fa_check_square_o = 0xf046, - fa_chevron_circle_down = 0xf13a, - fa_chevron_circle_left = 0xf137, - fa_chevron_circle_right = 0xf138, - fa_chevron_circle_up = 0xf139, - fa_chevron_down = 0xf078, - fa_chevron_left = 0xf053, - fa_chevron_right = 0xf054, - fa_chevron_up = 0xf077, - fa_child = 0xf1ae, - fa_chrome = 0xf268, - fa_circle = 0xf111, - fa_circle_o = 0xf10c, - fa_circle_o_notch = 0xf1ce, - fa_circle_thin = 0xf1db, - fa_clipboard = 0xf0ea, - fa_clock_o = 0xf017, - fa_clone = 0xf24d, - fa_close = 0xf00d, - fa_cloud = 0xf0c2, - fa_cloud_download = 0xf0ed, - fa_cloud_upload = 0xf0ee, - fa_cny = 0xf157, - fa_code = 0xf121, - fa_code_fork = 0xf126, - fa_codepen = 0xf1cb, - fa_codiepie = 0xf284, - fa_coffee = 0xf0f4, - fa_cog = 0xf013, - fa_cogs = 0xf085, - fa_columns = 0xf0db, - fa_comment = 0xf075, - fa_comment_o = 0xf0e5, - fa_commenting = 0xf27a, - fa_commenting_o = 0xf27b, - fa_comments = 0xf086, - fa_comments_o = 0xf0e6, - fa_compass = 0xf14e, - fa_compress = 0xf066, - fa_connectdevelop = 0xf20e, - fa_contao = 0xf26d, - fa_copy = 0xf0c5, - fa_copyright = 0xf1f9, - fa_creative_commons = 0xf25e, - fa_credit_card = 0xf09d, - fa_credit_card_alt = 0xf283, - fa_crop = 0xf125, - fa_crosshairs = 0xf05b, - fa_css3 = 0xf13c, - fa_cube = 0xf1b2, - fa_cubes = 0xf1b3, - fa_cut = 0xf0c4, - fa_cutlery = 0xf0f5, - fa_dashboard = 0xf0e4, - fa_dashcube = 0xf210, - fa_database = 0xf1c0, - fa_deaf = 0xf2a4, - fa_deafness = 0xf2a4, - fa_dedent = 0xf03b, - fa_delicious = 0xf1a5, - fa_desktop = 0xf108, - fa_deviantart = 0xf1bd, - fa_diamond = 0xf219, - fa_digg = 0xf1a6, - fa_dollar = 0xf155, - fa_dot_circle_o = 0xf192, - fa_download = 0xf019, - fa_dribbble = 0xf17d, - fa_drivers_license = 0xf2c2, - fa_drivers_license_o = 0xf2c3, - fa_dropbox = 0xf16b, - fa_drupal = 0xf1a9, - fa_edge = 0xf282, - fa_edit = 0xf044, - fa_eercast = 0xf2da, - fa_eject = 0xf052, - fa_ellipsis_h = 0xf141, - fa_ellipsis_v = 0xf142, - fa_empire = 0xf1d1, - fa_envelope = 0xf0e0, - fa_envelope_o = 0xf003, - fa_envelope_open = 0xf2b6, - fa_envelope_open_o = 0xf2b7, - fa_envelope_square = 0xf199, - fa_envira = 0xf299, - fa_eraser = 0xf12d, - fa_etsy = 0xf2d7, - fa_eur = 0xf153, - fa_euro = 0xf153, - fa_exchange = 0xf0ec, - fa_exclamation = 0xf12a, - fa_exclamation_circle = 0xf06a, - fa_exclamation_triangle = 0xf071, - fa_expand = 0xf065, - fa_expeditedssl = 0xf23e, - fa_external_link = 0xf08e, - fa_external_link_square = 0xf14c, - fa_eye = 0xf06e, - fa_eye_slash = 0xf070, - fa_eyedropper = 0xf1fb, - fa_fa = 0xf2b4, - fa_facebook = 0xf09a, - fa_facebook_f = 0xf09a, - fa_facebook_official = 0xf230, - fa_facebook_square = 0xf082, - fa_fast_backward = 0xf049, - fa_fast_forward = 0xf050, - fa_fax = 0xf1ac, - fa_feed = 0xf09e, - fa_female = 0xf182, - fa_fighter_jet = 0xf0fb, - fa_file = 0xf15b, - fa_file_archive_o = 0xf1c6, - fa_file_audio_o = 0xf1c7, - fa_file_code_o = 0xf1c9, - fa_file_excel_o = 0xf1c3, - fa_file_image_o = 0xf1c5, - fa_file_movie_o = 0xf1c8, - fa_file_o = 0xf016, - fa_file_pdf_o = 0xf1c1, - fa_file_photo_o = 0xf1c5, - fa_file_picture_o = 0xf1c5, - fa_file_powerpoint_o = 0xf1c4, - fa_file_sound_o = 0xf1c7, - fa_file_text = 0xf15c, - fa_file_text_o = 0xf0f6, - fa_file_video_o = 0xf1c8, - fa_file_word_o = 0xf1c2, - fa_file_zip_o = 0xf1c6, - fa_files_o = 0xf0c5, - fa_film = 0xf008, - fa_filter = 0xf0b0, - fa_fire = 0xf06d, - fa_fire_extinguisher = 0xf134, - fa_firefox = 0xf269, - fa_first_order = 0xf2b0, - fa_flag = 0xf024, - fa_flag_checkered = 0xf11e, - fa_flag_o = 0xf11d, - fa_flash = 0xf0e7, - fa_flask = 0xf0c3, - fa_flickr = 0xf16e, - fa_floppy_o = 0xf0c7, - fa_folder = 0xf07b, - fa_folder_o = 0xf114, - fa_folder_open = 0xf07c, - fa_folder_open_o = 0xf115, - fa_font = 0xf031, - fa_font_awesome = 0xf2b4, - fa_fonticons = 0xf280, - fa_fort_awesome = 0xf286, - fa_forumbee = 0xf211, - fa_forward = 0xf04e, - fa_foursquare = 0xf180, - fa_free_code_camp = 0xf2c5, - fa_frown_o = 0xf119, - fa_futbol_o = 0xf1e3, - fa_gamepad = 0xf11b, - fa_gavel = 0xf0e3, - fa_gbp = 0xf154, - fa_ge = 0xf1d1, - fa_gear = 0xf013, - fa_gears = 0xf085, - fa_genderless = 0xf22d, - fa_get_pocket = 0xf265, - fa_gg = 0xf260, - fa_gg_circle = 0xf261, - fa_gift = 0xf06b, - fa_git = 0xf1d3, - fa_git_square = 0xf1d2, - fa_github = 0xf09b, - fa_github_alt = 0xf113, - fa_github_square = 0xf092, - fa_gitlab = 0xf296, - fa_gittip = 0xf184, - fa_glass = 0xf000, - fa_glide = 0xf2a5, - fa_glide_g = 0xf2a6, - fa_globe = 0xf0ac, - fa_google = 0xf1a0, - fa_google_plus = 0xf0d5, - fa_google_plus_circle = 0xf2b3, - fa_google_plus_official = 0xf2b3, - fa_google_plus_square = 0xf0d4, - fa_google_wallet = 0xf1ee, - fa_graduation_cap = 0xf19d, - fa_gratipay = 0xf184, - fa_grav = 0xf2d6, - fa_group = 0xf0c0, - fa_h_square = 0xf0fd, - fa_hacker_news = 0xf1d4, - fa_hand_grab_o = 0xf255, - fa_hand_lizard_o = 0xf258, - fa_hand_o_down = 0xf0a7, - fa_hand_o_left = 0xf0a5, - fa_hand_o_right = 0xf0a4, - fa_hand_o_up = 0xf0a6, - fa_hand_paper_o = 0xf256, - fa_hand_peace_o = 0xf25b, - fa_hand_pointer_o = 0xf25a, - fa_hand_rock_o = 0xf255, - fa_hand_scissors_o = 0xf257, - fa_hand_spock_o = 0xf259, - fa_hand_stop_o = 0xf256, - fa_handshake_o = 0xf2b5, - fa_hard_of_hearing = 0xf2a4, - fa_hashtag = 0xf292, - fa_hdd_o = 0xf0a0, - fa_header = 0xf1dc, - fa_headphones = 0xf025, - fa_heart = 0xf004, - fa_heart_o = 0xf08a, - fa_heartbeat = 0xf21e, - fa_history = 0xf1da, - fa_home = 0xf015, - fa_hospital_o = 0xf0f8, - fa_hotel = 0xf236, - fa_hourglass = 0xf254, - fa_hourglass_1 = 0xf251, - fa_hourglass_2 = 0xf252, - fa_hourglass_3 = 0xf253, - fa_hourglass_end = 0xf253, - fa_hourglass_half = 0xf252, - fa_hourglass_o = 0xf250, - fa_hourglass_start = 0xf251, - fa_houzz = 0xf27c, - fa_html5 = 0xf13b, - fa_i_cursor = 0xf246, - fa_id_badge = 0xf2c1, - fa_id_card = 0xf2c2, - fa_id_card_o = 0xf2c3, - fa_ils = 0xf20b, - fa_image = 0xf03e, - fa_imdb = 0xf2d8, - fa_inbox = 0xf01c, - fa_indent = 0xf03c, - fa_industry = 0xf275, - fa_info = 0xf129, - fa_info_circle = 0xf05a, - fa_inr = 0xf156, - fa_instagram = 0xf16d, - fa_institution = 0xf19c, - fa_internet_explorer = 0xf26b, - fa_intersex = 0xf224, - fa_ioxhost = 0xf208, - fa_italic = 0xf033, - fa_joomla = 0xf1aa, - fa_jpy = 0xf157, - fa_jsfiddle = 0xf1cc, - fa_key = 0xf084, - fa_keyboard_o = 0xf11c, - fa_krw = 0xf159, - fa_language = 0xf1ab, - fa_laptop = 0xf109, - fa_lastfm = 0xf202, - fa_lastfm_square = 0xf203, - fa_leaf = 0xf06c, - fa_leanpub = 0xf212, - fa_legal = 0xf0e3, - fa_lemon_o = 0xf094, - fa_level_down = 0xf149, - fa_level_up = 0xf148, - fa_life_bouy = 0xf1cd, - fa_life_buoy = 0xf1cd, - fa_life_ring = 0xf1cd, - fa_life_saver = 0xf1cd, - fa_lightbulb_o = 0xf0eb, - fa_line_chart = 0xf201, - fa_link = 0xf0c1, - fa_linkedin = 0xf0e1, - fa_linkedin_square = 0xf08c, - fa_linode = 0xf2b8, - fa_linux = 0xf17c, - fa_list = 0xf03a, - fa_list_alt = 0xf022, - fa_list_ol = 0xf0cb, - fa_list_ul = 0xf0ca, - fa_location_arrow = 0xf124, - fa_lock = 0xf023, - fa_long_arrow_down = 0xf175, - fa_long_arrow_left = 0xf177, - fa_long_arrow_right = 0xf178, - fa_long_arrow_up = 0xf176, - fa_low_vision = 0xf2a8, - fa_magic = 0xf0d0, - fa_magnet = 0xf076, - fa_mail_forward = 0xf064, - fa_mail_reply = 0xf112, - fa_mail_reply_all = 0xf122, - fa_male = 0xf183, - fa_map = 0xf279, - fa_map_marker = 0xf041, - fa_map_o = 0xf278, - fa_map_pin = 0xf276, - fa_map_signs = 0xf277, - fa_mars = 0xf222, - fa_mars_double = 0xf227, - fa_mars_stroke = 0xf229, - fa_mars_stroke_h = 0xf22b, - fa_mars_stroke_v = 0xf22a, - fa_maxcdn = 0xf136, - fa_meanpath = 0xf20c, - fa_medium = 0xf23a, - fa_medkit = 0xf0fa, - fa_meetup = 0xf2e0, - fa_meh_o = 0xf11a, - fa_mercury = 0xf223, - fa_microchip = 0xf2db, - fa_microphone = 0xf130, - fa_microphone_slash = 0xf131, - fa_minus = 0xf068, - fa_minus_circle = 0xf056, - fa_minus_square = 0xf146, - fa_minus_square_o = 0xf147, - fa_mixcloud = 0xf289, - fa_mobile = 0xf10b, - fa_mobile_phone = 0xf10b, - fa_modx = 0xf285, - fa_money = 0xf0d6, - fa_moon_o = 0xf186, - fa_mortar_board = 0xf19d, - fa_motorcycle = 0xf21c, - fa_mouse_pointer = 0xf245, - fa_music = 0xf001, - fa_navicon = 0xf0c9, - fa_neuter = 0xf22c, - fa_newspaper_o = 0xf1ea, - fa_object_group = 0xf247, - fa_object_ungroup = 0xf248, - fa_odnoklassniki = 0xf263, - fa_odnoklassniki_square = 0xf264, - fa_opencart = 0xf23d, - fa_openid = 0xf19b, - fa_opera = 0xf26a, - fa_optin_monster = 0xf23c, - fa_outdent = 0xf03b, - fa_pagelines = 0xf18c, - fa_paint_brush = 0xf1fc, - fa_paper_plane = 0xf1d8, - fa_paper_plane_o = 0xf1d9, - fa_paperclip = 0xf0c6, - fa_paragraph = 0xf1dd, - fa_paste = 0xf0ea, - fa_pause = 0xf04c, - fa_pause_circle = 0xf28b, - fa_pause_circle_o = 0xf28c, - fa_paw = 0xf1b0, - fa_paypal = 0xf1ed, - fa_pencil = 0xf040, - fa_pencil_square = 0xf14b, - fa_pencil_square_o = 0xf044, - fa_percent = 0xf295, - fa_phone = 0xf095, - fa_phone_square = 0xf098, - fa_photo = 0xf03e, - fa_picture_o = 0xf03e, - fa_pie_chart = 0xf200, - fa_pied_piper = 0xf2ae, - fa_pied_piper_alt = 0xf1a8, - fa_pied_piper_pp = 0xf1a7, - fa_pinterest = 0xf0d2, - fa_pinterest_p = 0xf231, - fa_pinterest_square = 0xf0d3, - fa_plane = 0xf072, - fa_play = 0xf04b, - fa_play_circle = 0xf144, - fa_play_circle_o = 0xf01d, - fa_plug = 0xf1e6, - fa_plus = 0xf067, - fa_plus_circle = 0xf055, - fa_plus_square = 0xf0fe, - fa_plus_square_o = 0xf196, - fa_podcast = 0xf2ce, - fa_power_off = 0xf011, - fa_print = 0xf02f, - fa_product_hunt = 0xf288, - fa_puzzle_piece = 0xf12e, - fa_qq = 0xf1d6, - fa_qrcode = 0xf029, - fa_question = 0xf128, - fa_question_circle = 0xf059, - fa_question_circle_o = 0xf29c, - fa_quora = 0xf2c4, - fa_quote_left = 0xf10d, - fa_quote_right = 0xf10e, - fa_ra = 0xf1d0, - fa_random = 0xf074, - fa_ravelry = 0xf2d9, - fa_rebel = 0xf1d0, - fa_recycle = 0xf1b8, - fa_reddit = 0xf1a1, - fa_reddit_alien = 0xf281, - fa_reddit_square = 0xf1a2, - fa_refresh = 0xf021, - fa_registered = 0xf25d, - fa_remove = 0xf00d, - fa_renren = 0xf18b, - fa_reorder = 0xf0c9, - fa_repeat = 0xf01e, - fa_reply = 0xf112, - fa_reply_all = 0xf122, - fa_resistance = 0xf1d0, - fa_retweet = 0xf079, - fa_rmb = 0xf157, - fa_road = 0xf018, - fa_rocket = 0xf135, - fa_rotate_left = 0xf0e2, - fa_rotate_right = 0xf01e, - fa_rouble = 0xf158, - fa_rss = 0xf09e, - fa_rss_square = 0xf143, - fa_rub = 0xf158, - fa_ruble = 0xf158, - fa_rupee = 0xf156, - fa_s15 = 0xf2cd, - fa_safari = 0xf267, - fa_save = 0xf0c7, - fa_scissors = 0xf0c4, - fa_scribd = 0xf28a, - fa_search = 0xf002, - fa_search_minus = 0xf010, - fa_search_plus = 0xf00e, - fa_sellsy = 0xf213, - fa_send = 0xf1d8, - fa_send_o = 0xf1d9, - fa_server = 0xf233, - fa_share = 0xf064, - fa_share_alt = 0xf1e0, - fa_share_alt_square = 0xf1e1, - fa_share_square = 0xf14d, - fa_share_square_o = 0xf045, - fa_shekel = 0xf20b, - fa_sheqel = 0xf20b, - fa_shield = 0xf132, - fa_ship = 0xf21a, - fa_shirtsinbulk = 0xf214, - fa_shopping_bag = 0xf290, - fa_shopping_basket = 0xf291, - fa_shopping_cart = 0xf07a, - fa_shower = 0xf2cc, - fa_sign_in = 0xf090, - fa_sign_language = 0xf2a7, - fa_sign_out = 0xf08b, - fa_signal = 0xf012, - fa_signing = 0xf2a7, - fa_simplybuilt = 0xf215, - fa_sitemap = 0xf0e8, - fa_skyatlas = 0xf216, - fa_skype = 0xf17e, - fa_slack = 0xf198, - fa_sliders = 0xf1de, - fa_slideshare = 0xf1e7, - fa_smile_o = 0xf118, - fa_snapchat = 0xf2ab, - fa_snapchat_ghost = 0xf2ac, - fa_snapchat_square = 0xf2ad, - fa_snowflake_o = 0xf2dc, - fa_soccer_ball_o = 0xf1e3, - fa_sort = 0xf0dc, - fa_sort_alpha_asc = 0xf15d, - fa_sort_alpha_desc = 0xf15e, - fa_sort_amount_asc = 0xf160, - fa_sort_amount_desc = 0xf161, - fa_sort_asc = 0xf0de, - fa_sort_desc = 0xf0dd, - fa_sort_down = 0xf0dd, - fa_sort_numeric_asc = 0xf162, - fa_sort_numeric_desc = 0xf163, - fa_sort_up = 0xf0de, - fa_soundcloud = 0xf1be, - fa_space_shuttle = 0xf197, - fa_spinner = 0xf110, - fa_spoon = 0xf1b1, - fa_spotify = 0xf1bc, - fa_square = 0xf0c8, - fa_square_o = 0xf096, - fa_stack_exchange = 0xf18d, - fa_stack_overflow = 0xf16c, - fa_star = 0xf005, - fa_star_half = 0xf089, - fa_star_half_empty = 0xf123, - fa_star_half_full = 0xf123, - fa_star_half_o = 0xf123, - fa_star_o = 0xf006, - fa_steam = 0xf1b6, - fa_steam_square = 0xf1b7, - fa_step_backward = 0xf048, - fa_step_forward = 0xf051, - fa_stethoscope = 0xf0f1, - fa_sticky_note = 0xf249, - fa_sticky_note_o = 0xf24a, - fa_stop = 0xf04d, - fa_stop_circle = 0xf28d, - fa_stop_circle_o = 0xf28e, - fa_street_view = 0xf21d, - fa_strikethrough = 0xf0cc, - fa_stumbleupon = 0xf1a4, - fa_stumbleupon_circle = 0xf1a3, - fa_subscript = 0xf12c, - fa_subway = 0xf239, - fa_suitcase = 0xf0f2, - fa_sun_o = 0xf185, - fa_superpowers = 0xf2dd, - fa_superscript = 0xf12b, - fa_support = 0xf1cd, - fa_table = 0xf0ce, - fa_tablet = 0xf10a, - fa_tachometer = 0xf0e4, - fa_tag = 0xf02b, - fa_tags = 0xf02c, - fa_tasks = 0xf0ae, - fa_taxi = 0xf1ba, - fa_telegram = 0xf2c6, - fa_television = 0xf26c, - fa_tencent_weibo = 0xf1d5, - fa_terminal = 0xf120, - fa_text_height = 0xf034, - fa_text_width = 0xf035, - fa_th = 0xf00a, - fa_th_large = 0xf009, - fa_th_list = 0xf00b, - fa_themeisle = 0xf2b2, - fa_thermometer = 0xf2c7, - fa_thermometer_0 = 0xf2cb, - fa_thermometer_1 = 0xf2ca, - fa_thermometer_2 = 0xf2c9, - fa_thermometer_3 = 0xf2c8, - fa_thermometer_4 = 0xf2c7, - fa_thermometer_empty = 0xf2cb, - fa_thermometer_full = 0xf2c7, - fa_thermometer_half = 0xf2c9, - fa_thermometer_quarter = 0xf2ca, - fa_thermometer_three_quarters = 0xf2c8, - fa_thumb_tack = 0xf08d, - fa_thumbs_down = 0xf165, - fa_thumbs_o_down = 0xf088, - fa_thumbs_o_up = 0xf087, - fa_thumbs_up = 0xf164, - fa_ticket = 0xf145, - fa_times = 0xf00d, - fa_times_circle = 0xf057, - fa_times_circle_o = 0xf05c, - fa_times_rectangle = 0xf2d3, - fa_times_rectangle_o = 0xf2d4, - fa_tint = 0xf043, - fa_toggle_down = 0xf150, - fa_toggle_left = 0xf191, - fa_toggle_off = 0xf204, - fa_toggle_on = 0xf205, - fa_toggle_right = 0xf152, - fa_toggle_up = 0xf151, - fa_trademark = 0xf25c, - fa_train = 0xf238, - fa_transgender = 0xf224, - fa_transgender_alt = 0xf225, - fa_trash = 0xf1f8, - fa_trash_o = 0xf014, - fa_tree = 0xf1bb, - fa_trello = 0xf181, - fa_tripadvisor = 0xf262, - fa_trophy = 0xf091, - fa_truck = 0xf0d1, - fa_try = 0xf195, - fa_tty = 0xf1e4, - fa_tumblr = 0xf173, - fa_tumblr_square = 0xf174, - fa_turkish_lira = 0xf195, - fa_tv = 0xf26c, - fa_twitch = 0xf1e8, - fa_twitter = 0xf099, - fa_twitter_square = 0xf081, - fa_umbrella = 0xf0e9, - fa_underline = 0xf0cd, - fa_undo = 0xf0e2, - fa_universal_access = 0xf29a, - fa_university = 0xf19c, - fa_unlink = 0xf127, - fa_unlock = 0xf09c, - fa_unlock_alt = 0xf13e, - fa_unsorted = 0xf0dc, - fa_upload = 0xf093, - fa_usb = 0xf287, - fa_usd = 0xf155, - fa_user = 0xf007, - fa_user_circle = 0xf2bd, - fa_user_circle_o = 0xf2be, - fa_user_md = 0xf0f0, - fa_user_o = 0xf2c0, - fa_user_plus = 0xf234, - fa_user_secret = 0xf21b, - fa_user_times = 0xf235, - fa_users = 0xf0c0, - fa_vcard = 0xf2bb, - fa_vcard_o = 0xf2bc, - fa_venus = 0xf221, - fa_venus_double = 0xf226, - fa_venus_mars = 0xf228, - fa_viacoin = 0xf237, - fa_viadeo = 0xf2a9, - fa_viadeo_square = 0xf2aa, - fa_video_camera = 0xf03d, - fa_vimeo = 0xf27d, - fa_vimeo_square = 0xf194, - fa_vine = 0xf1ca, - fa_vk = 0xf189, - fa_volume_control_phone = 0xf2a0, - fa_volume_down = 0xf027, - fa_volume_off = 0xf026, - fa_volume_up = 0xf028, - fa_warning = 0xf071, - fa_wechat = 0xf1d7, - fa_weibo = 0xf18a, - fa_weixin = 0xf1d7, - fa_whatsapp = 0xf232, - fa_wheelchair = 0xf193, - fa_wheelchair_alt = 0xf29b, - fa_wifi = 0xf1eb, - fa_wikipedia_w = 0xf266, - fa_window_close = 0xf2d3, - fa_window_close_o = 0xf2d4, - fa_window_maximize = 0xf2d0, - fa_window_minimize = 0xf2d1, - fa_window_restore = 0xf2d2, - fa_windows = 0xf17a, - fa_won = 0xf159, - fa_wordpress = 0xf19a, - fa_wpbeginner = 0xf297, - fa_wpexplorer = 0xf2de, - fa_wpforms = 0xf298, - fa_wrench = 0xf0ad, - fa_xing = 0xf168, - fa_xing_square = 0xf169, - fa_y_combinator = 0xf23b, - fa_y_combinator_square = 0xf1d4, - fa_yahoo = 0xf19e, - fa_yc = 0xf23b, - fa_yc_square = 0xf1d4, - fa_yelp = 0xf1e9, - fa_yen = 0xf157, - fa_yoast = 0xf2b1, - fa_youtube = 0xf167, - fa_youtube_play = 0xf16a, - fa_youtube_square = 0xf166, - - // ruleset icons in circles - fa_osu_osu_o = 0xe000, - fa_osu_mania_o = 0xe001, - fa_osu_fruits_o = 0xe002, - fa_osu_taiko_o = 0xe003, - - // ruleset icons without circles - fa_osu_filled_circle = 0xe004, - fa_osu_cross_o = 0xe005, - fa_osu_logo = 0xe006, - fa_osu_chevron_down_o = 0xe007, - fa_osu_edit_o = 0xe033, - fa_osu_left_o = 0xe034, - fa_osu_right_o = 0xe035, - fa_osu_charts = 0xe036, - fa_osu_solo = 0xe037, - fa_osu_multi = 0xe038, - fa_osu_gear = 0xe039, - - // misc icons - fa_osu_bat = 0xe008, - fa_osu_bubble = 0xe009, - fa_osu_bubble_pop = 0xe02e, - fa_osu_dice = 0xe011, - fa_osu_heart1 = 0xe02f, - fa_osu_heart1_break = 0xe030, - fa_osu_hot = 0xe031, - fa_osu_list_search = 0xe032, - - //osu! playstyles - fa_osu_playstyle_tablet = 0xe02a, - fa_osu_playstyle_mouse = 0xe029, - fa_osu_playstyle_keyboard = 0xe02b, - fa_osu_playstyle_touch = 0xe02c, - - // osu! difficulties - fa_osu_easy_osu = 0xe015, - fa_osu_normal_osu = 0xe016, - fa_osu_hard_osu = 0xe017, - fa_osu_insane_osu = 0xe018, - fa_osu_expert_osu = 0xe019, - - // taiko difficulties - fa_osu_easy_taiko = 0xe01a, - fa_osu_normal_taiko = 0xe01b, - fa_osu_hard_taiko = 0xe01c, - fa_osu_insane_taiko = 0xe01d, - fa_osu_expert_taiko = 0xe01e, - - // fruits difficulties - fa_osu_easy_fruits = 0xe01f, - fa_osu_normal_fruits = 0xe020, - fa_osu_hard_fruits = 0xe021, - fa_osu_insane_fruits = 0xe022, - fa_osu_expert_fruits = 0xe023, - - // mania difficulties - fa_osu_easy_mania = 0xe024, - fa_osu_normal_mania = 0xe025, - fa_osu_hard_mania = 0xe026, - fa_osu_insane_mania = 0xe027, - fa_osu_expert_mania = 0xe028, - - // mod icons - fa_osu_mod_perfect = 0xe049, - fa_osu_mod_autopilot = 0xe03a, - fa_osu_mod_auto = 0xe03b, - fa_osu_mod_cinema = 0xe03c, - fa_osu_mod_doubletime = 0xe03d, - fa_osu_mod_easy = 0xe03e, - fa_osu_mod_flashlight = 0xe03f, - fa_osu_mod_halftime = 0xe040, - fa_osu_mod_hardrock = 0xe041, - fa_osu_mod_hidden = 0xe042, - fa_osu_mod_nightcore = 0xe043, - fa_osu_mod_nofail = 0xe044, - fa_osu_mod_relax = 0xe045, - fa_osu_mod_spunout = 0xe046, - fa_osu_mod_suddendeath = 0xe047, - fa_osu_mod_target = 0xe048, - fa_osu_mod_bg = 0xe04a, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics; +using osu.Framework.IO.Stores; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Caching; + +namespace osu.Game.Graphics +{ + public class SpriteIcon : CompositeDrawable + { + private Sprite spriteShadow; + private Sprite spriteMain; + + private Cached layout = new Cached(); + private Container shadowVisibility; + + private FontStore store; + + [BackgroundDependencyLoader] + private void load(FontStore store) + { + this.store = store; + + InternalChildren = new Drawable[] + { + shadowVisibility = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Child = spriteShadow = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Y = 2, + Colour = new Color4(0f, 0f, 0f, 0.2f), + }, + Alpha = shadow ? 1 : 0, + }, + spriteMain = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }, + }; + + updateTexture(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateTexture(); + } + + private FontAwesome loadedIcon; + private void updateTexture() + { + var loadableIcon = icon; + + if (loadableIcon == loadedIcon) return; + + var texture = store?.Get(((char)loadableIcon).ToString()); + + spriteMain.Texture = texture; + spriteShadow.Texture = texture; + + if (Size == Vector2.Zero) + Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); + + loadedIcon = loadableIcon; + } + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.Colour) > 0 && Shadow) + layout.Invalidate(); + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + if (!layout.IsValid) + { + //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. + //squared result for quadratic fall-off seems to give the best result. + var avgColour = (Color4)DrawInfo.Colour.AverageColour; + + spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); + + layout.Validate(); + } + } + + private bool shadow; + public bool Shadow + { + get { return shadow; } + set + { + shadow = value; + if (shadowVisibility != null) + shadowVisibility.Alpha = value ? 1 : 0; + } + } + + private FontAwesome icon; + + public FontAwesome Icon + { + get + { + return icon; + } + + set + { + if (icon == value) return; + + icon = value; + if (IsLoaded) + updateTexture(); + } + } + } + + public enum FontAwesome + { + fa_500px = 0xf26e, + fa_address_book = 0xf2b9, + fa_address_book_o = 0xf2ba, + fa_address_card = 0xf2bb, + fa_address_card_o = 0xf2bc, + fa_adjust = 0xf042, + fa_adn = 0xf170, + fa_align_center = 0xf037, + fa_align_justify = 0xf039, + fa_align_left = 0xf036, + fa_align_right = 0xf038, + fa_amazon = 0xf270, + fa_ambulance = 0xf0f9, + fa_american_sign_language_interpreting = 0xf2a3, + fa_anchor = 0xf13d, + fa_android = 0xf17b, + fa_angellist = 0xf209, + fa_angle_double_down = 0xf103, + fa_angle_double_left = 0xf100, + fa_angle_double_right = 0xf101, + fa_angle_double_up = 0xf102, + fa_angle_down = 0xf107, + fa_angle_left = 0xf104, + fa_angle_right = 0xf105, + fa_angle_up = 0xf106, + fa_apple = 0xf179, + fa_archive = 0xf187, + fa_area_chart = 0xf1fe, + fa_arrow_circle_down = 0xf0ab, + fa_arrow_circle_left = 0xf0a8, + fa_arrow_circle_o_down = 0xf01a, + fa_arrow_circle_o_left = 0xf190, + fa_arrow_circle_o_right = 0xf18e, + fa_arrow_circle_o_up = 0xf01b, + fa_arrow_circle_right = 0xf0a9, + fa_arrow_circle_up = 0xf0aa, + fa_arrow_down = 0xf063, + fa_arrow_left = 0xf060, + fa_arrow_right = 0xf061, + fa_arrow_up = 0xf062, + fa_arrows = 0xf047, + fa_arrows_alt = 0xf0b2, + fa_arrows_h = 0xf07e, + fa_arrows_v = 0xf07d, + fa_asl_interpreting = 0xf2a3, + fa_assistive_listening_systems = 0xf2a2, + fa_asterisk = 0xf069, + fa_at = 0xf1fa, + fa_audio_description = 0xf29e, + fa_automobile = 0xf1b9, + fa_backward = 0xf04a, + fa_balance_scale = 0xf24e, + fa_ban = 0xf05e, + fa_bandcamp = 0xf2d5, + fa_bank = 0xf19c, + fa_bar_chart = 0xf080, + fa_bar_chart_o = 0xf080, + fa_barcode = 0xf02a, + fa_bars = 0xf0c9, + fa_bath = 0xf2cd, + fa_bathtub = 0xf2cd, + fa_battery = 0xf240, + fa_battery_0 = 0xf244, + fa_battery_1 = 0xf243, + fa_battery_2 = 0xf242, + fa_battery_3 = 0xf241, + fa_battery_4 = 0xf240, + fa_battery_empty = 0xf244, + fa_battery_full = 0xf240, + fa_battery_half = 0xf242, + fa_battery_quarter = 0xf243, + fa_battery_three_quarters = 0xf241, + fa_bed = 0xf236, + fa_beer = 0xf0fc, + fa_behance = 0xf1b4, + fa_behance_square = 0xf1b5, + fa_bell = 0xf0f3, + fa_bell_o = 0xf0a2, + fa_bell_slash = 0xf1f6, + fa_bell_slash_o = 0xf1f7, + fa_bicycle = 0xf206, + fa_binoculars = 0xf1e5, + fa_birthday_cake = 0xf1fd, + fa_bitbucket = 0xf171, + fa_bitbucket_square = 0xf172, + fa_bitcoin = 0xf15a, + fa_black_tie = 0xf27e, + fa_blind = 0xf29d, + fa_bluetooth = 0xf293, + fa_bluetooth_b = 0xf294, + fa_bold = 0xf032, + fa_bolt = 0xf0e7, + fa_bomb = 0xf1e2, + fa_book = 0xf02d, + fa_bookmark = 0xf02e, + fa_bookmark_o = 0xf097, + fa_braille = 0xf2a1, + fa_briefcase = 0xf0b1, + fa_btc = 0xf15a, + fa_bug = 0xf188, + fa_building = 0xf1ad, + fa_building_o = 0xf0f7, + fa_bullhorn = 0xf0a1, + fa_bullseye = 0xf140, + fa_bus = 0xf207, + fa_buysellads = 0xf20d, + fa_cab = 0xf1ba, + fa_calculator = 0xf1ec, + fa_calendar = 0xf073, + fa_calendar_check_o = 0xf274, + fa_calendar_minus_o = 0xf272, + fa_calendar_o = 0xf133, + fa_calendar_plus_o = 0xf271, + fa_calendar_times_o = 0xf273, + fa_camera = 0xf030, + fa_camera_retro = 0xf083, + fa_car = 0xf1b9, + fa_caret_down = 0xf0d7, + fa_caret_left = 0xf0d9, + fa_caret_right = 0xf0da, + fa_caret_square_o_down = 0xf150, + fa_caret_square_o_left = 0xf191, + fa_caret_square_o_right = 0xf152, + fa_caret_square_o_up = 0xf151, + fa_caret_up = 0xf0d8, + fa_cart_arrow_down = 0xf218, + fa_cart_plus = 0xf217, + fa_cc = 0xf20a, + fa_cc_amex = 0xf1f3, + fa_cc_diners_club = 0xf24c, + fa_cc_discover = 0xf1f2, + fa_cc_jcb = 0xf24b, + fa_cc_mastercard = 0xf1f1, + fa_cc_paypal = 0xf1f4, + fa_cc_stripe = 0xf1f5, + fa_cc_visa = 0xf1f0, + fa_certificate = 0xf0a3, + fa_chain = 0xf0c1, + fa_chain_broken = 0xf127, + fa_check = 0xf00c, + fa_check_circle = 0xf058, + fa_check_circle_o = 0xf05d, + fa_check_square = 0xf14a, + fa_check_square_o = 0xf046, + fa_chevron_circle_down = 0xf13a, + fa_chevron_circle_left = 0xf137, + fa_chevron_circle_right = 0xf138, + fa_chevron_circle_up = 0xf139, + fa_chevron_down = 0xf078, + fa_chevron_left = 0xf053, + fa_chevron_right = 0xf054, + fa_chevron_up = 0xf077, + fa_child = 0xf1ae, + fa_chrome = 0xf268, + fa_circle = 0xf111, + fa_circle_o = 0xf10c, + fa_circle_o_notch = 0xf1ce, + fa_circle_thin = 0xf1db, + fa_clipboard = 0xf0ea, + fa_clock_o = 0xf017, + fa_clone = 0xf24d, + fa_close = 0xf00d, + fa_cloud = 0xf0c2, + fa_cloud_download = 0xf0ed, + fa_cloud_upload = 0xf0ee, + fa_cny = 0xf157, + fa_code = 0xf121, + fa_code_fork = 0xf126, + fa_codepen = 0xf1cb, + fa_codiepie = 0xf284, + fa_coffee = 0xf0f4, + fa_cog = 0xf013, + fa_cogs = 0xf085, + fa_columns = 0xf0db, + fa_comment = 0xf075, + fa_comment_o = 0xf0e5, + fa_commenting = 0xf27a, + fa_commenting_o = 0xf27b, + fa_comments = 0xf086, + fa_comments_o = 0xf0e6, + fa_compass = 0xf14e, + fa_compress = 0xf066, + fa_connectdevelop = 0xf20e, + fa_contao = 0xf26d, + fa_copy = 0xf0c5, + fa_copyright = 0xf1f9, + fa_creative_commons = 0xf25e, + fa_credit_card = 0xf09d, + fa_credit_card_alt = 0xf283, + fa_crop = 0xf125, + fa_crosshairs = 0xf05b, + fa_css3 = 0xf13c, + fa_cube = 0xf1b2, + fa_cubes = 0xf1b3, + fa_cut = 0xf0c4, + fa_cutlery = 0xf0f5, + fa_dashboard = 0xf0e4, + fa_dashcube = 0xf210, + fa_database = 0xf1c0, + fa_deaf = 0xf2a4, + fa_deafness = 0xf2a4, + fa_dedent = 0xf03b, + fa_delicious = 0xf1a5, + fa_desktop = 0xf108, + fa_deviantart = 0xf1bd, + fa_diamond = 0xf219, + fa_digg = 0xf1a6, + fa_dollar = 0xf155, + fa_dot_circle_o = 0xf192, + fa_download = 0xf019, + fa_dribbble = 0xf17d, + fa_drivers_license = 0xf2c2, + fa_drivers_license_o = 0xf2c3, + fa_dropbox = 0xf16b, + fa_drupal = 0xf1a9, + fa_edge = 0xf282, + fa_edit = 0xf044, + fa_eercast = 0xf2da, + fa_eject = 0xf052, + fa_ellipsis_h = 0xf141, + fa_ellipsis_v = 0xf142, + fa_empire = 0xf1d1, + fa_envelope = 0xf0e0, + fa_envelope_o = 0xf003, + fa_envelope_open = 0xf2b6, + fa_envelope_open_o = 0xf2b7, + fa_envelope_square = 0xf199, + fa_envira = 0xf299, + fa_eraser = 0xf12d, + fa_etsy = 0xf2d7, + fa_eur = 0xf153, + fa_euro = 0xf153, + fa_exchange = 0xf0ec, + fa_exclamation = 0xf12a, + fa_exclamation_circle = 0xf06a, + fa_exclamation_triangle = 0xf071, + fa_expand = 0xf065, + fa_expeditedssl = 0xf23e, + fa_external_link = 0xf08e, + fa_external_link_square = 0xf14c, + fa_eye = 0xf06e, + fa_eye_slash = 0xf070, + fa_eyedropper = 0xf1fb, + fa_fa = 0xf2b4, + fa_facebook = 0xf09a, + fa_facebook_f = 0xf09a, + fa_facebook_official = 0xf230, + fa_facebook_square = 0xf082, + fa_fast_backward = 0xf049, + fa_fast_forward = 0xf050, + fa_fax = 0xf1ac, + fa_feed = 0xf09e, + fa_female = 0xf182, + fa_fighter_jet = 0xf0fb, + fa_file = 0xf15b, + fa_file_archive_o = 0xf1c6, + fa_file_audio_o = 0xf1c7, + fa_file_code_o = 0xf1c9, + fa_file_excel_o = 0xf1c3, + fa_file_image_o = 0xf1c5, + fa_file_movie_o = 0xf1c8, + fa_file_o = 0xf016, + fa_file_pdf_o = 0xf1c1, + fa_file_photo_o = 0xf1c5, + fa_file_picture_o = 0xf1c5, + fa_file_powerpoint_o = 0xf1c4, + fa_file_sound_o = 0xf1c7, + fa_file_text = 0xf15c, + fa_file_text_o = 0xf0f6, + fa_file_video_o = 0xf1c8, + fa_file_word_o = 0xf1c2, + fa_file_zip_o = 0xf1c6, + fa_files_o = 0xf0c5, + fa_film = 0xf008, + fa_filter = 0xf0b0, + fa_fire = 0xf06d, + fa_fire_extinguisher = 0xf134, + fa_firefox = 0xf269, + fa_first_order = 0xf2b0, + fa_flag = 0xf024, + fa_flag_checkered = 0xf11e, + fa_flag_o = 0xf11d, + fa_flash = 0xf0e7, + fa_flask = 0xf0c3, + fa_flickr = 0xf16e, + fa_floppy_o = 0xf0c7, + fa_folder = 0xf07b, + fa_folder_o = 0xf114, + fa_folder_open = 0xf07c, + fa_folder_open_o = 0xf115, + fa_font = 0xf031, + fa_font_awesome = 0xf2b4, + fa_fonticons = 0xf280, + fa_fort_awesome = 0xf286, + fa_forumbee = 0xf211, + fa_forward = 0xf04e, + fa_foursquare = 0xf180, + fa_free_code_camp = 0xf2c5, + fa_frown_o = 0xf119, + fa_futbol_o = 0xf1e3, + fa_gamepad = 0xf11b, + fa_gavel = 0xf0e3, + fa_gbp = 0xf154, + fa_ge = 0xf1d1, + fa_gear = 0xf013, + fa_gears = 0xf085, + fa_genderless = 0xf22d, + fa_get_pocket = 0xf265, + fa_gg = 0xf260, + fa_gg_circle = 0xf261, + fa_gift = 0xf06b, + fa_git = 0xf1d3, + fa_git_square = 0xf1d2, + fa_github = 0xf09b, + fa_github_alt = 0xf113, + fa_github_square = 0xf092, + fa_gitlab = 0xf296, + fa_gittip = 0xf184, + fa_glass = 0xf000, + fa_glide = 0xf2a5, + fa_glide_g = 0xf2a6, + fa_globe = 0xf0ac, + fa_google = 0xf1a0, + fa_google_plus = 0xf0d5, + fa_google_plus_circle = 0xf2b3, + fa_google_plus_official = 0xf2b3, + fa_google_plus_square = 0xf0d4, + fa_google_wallet = 0xf1ee, + fa_graduation_cap = 0xf19d, + fa_gratipay = 0xf184, + fa_grav = 0xf2d6, + fa_group = 0xf0c0, + fa_h_square = 0xf0fd, + fa_hacker_news = 0xf1d4, + fa_hand_grab_o = 0xf255, + fa_hand_lizard_o = 0xf258, + fa_hand_o_down = 0xf0a7, + fa_hand_o_left = 0xf0a5, + fa_hand_o_right = 0xf0a4, + fa_hand_o_up = 0xf0a6, + fa_hand_paper_o = 0xf256, + fa_hand_peace_o = 0xf25b, + fa_hand_pointer_o = 0xf25a, + fa_hand_rock_o = 0xf255, + fa_hand_scissors_o = 0xf257, + fa_hand_spock_o = 0xf259, + fa_hand_stop_o = 0xf256, + fa_handshake_o = 0xf2b5, + fa_hard_of_hearing = 0xf2a4, + fa_hashtag = 0xf292, + fa_hdd_o = 0xf0a0, + fa_header = 0xf1dc, + fa_headphones = 0xf025, + fa_heart = 0xf004, + fa_heart_o = 0xf08a, + fa_heartbeat = 0xf21e, + fa_history = 0xf1da, + fa_home = 0xf015, + fa_hospital_o = 0xf0f8, + fa_hotel = 0xf236, + fa_hourglass = 0xf254, + fa_hourglass_1 = 0xf251, + fa_hourglass_2 = 0xf252, + fa_hourglass_3 = 0xf253, + fa_hourglass_end = 0xf253, + fa_hourglass_half = 0xf252, + fa_hourglass_o = 0xf250, + fa_hourglass_start = 0xf251, + fa_houzz = 0xf27c, + fa_html5 = 0xf13b, + fa_i_cursor = 0xf246, + fa_id_badge = 0xf2c1, + fa_id_card = 0xf2c2, + fa_id_card_o = 0xf2c3, + fa_ils = 0xf20b, + fa_image = 0xf03e, + fa_imdb = 0xf2d8, + fa_inbox = 0xf01c, + fa_indent = 0xf03c, + fa_industry = 0xf275, + fa_info = 0xf129, + fa_info_circle = 0xf05a, + fa_inr = 0xf156, + fa_instagram = 0xf16d, + fa_institution = 0xf19c, + fa_internet_explorer = 0xf26b, + fa_intersex = 0xf224, + fa_ioxhost = 0xf208, + fa_italic = 0xf033, + fa_joomla = 0xf1aa, + fa_jpy = 0xf157, + fa_jsfiddle = 0xf1cc, + fa_key = 0xf084, + fa_keyboard_o = 0xf11c, + fa_krw = 0xf159, + fa_language = 0xf1ab, + fa_laptop = 0xf109, + fa_lastfm = 0xf202, + fa_lastfm_square = 0xf203, + fa_leaf = 0xf06c, + fa_leanpub = 0xf212, + fa_legal = 0xf0e3, + fa_lemon_o = 0xf094, + fa_level_down = 0xf149, + fa_level_up = 0xf148, + fa_life_bouy = 0xf1cd, + fa_life_buoy = 0xf1cd, + fa_life_ring = 0xf1cd, + fa_life_saver = 0xf1cd, + fa_lightbulb_o = 0xf0eb, + fa_line_chart = 0xf201, + fa_link = 0xf0c1, + fa_linkedin = 0xf0e1, + fa_linkedin_square = 0xf08c, + fa_linode = 0xf2b8, + fa_linux = 0xf17c, + fa_list = 0xf03a, + fa_list_alt = 0xf022, + fa_list_ol = 0xf0cb, + fa_list_ul = 0xf0ca, + fa_location_arrow = 0xf124, + fa_lock = 0xf023, + fa_long_arrow_down = 0xf175, + fa_long_arrow_left = 0xf177, + fa_long_arrow_right = 0xf178, + fa_long_arrow_up = 0xf176, + fa_low_vision = 0xf2a8, + fa_magic = 0xf0d0, + fa_magnet = 0xf076, + fa_mail_forward = 0xf064, + fa_mail_reply = 0xf112, + fa_mail_reply_all = 0xf122, + fa_male = 0xf183, + fa_map = 0xf279, + fa_map_marker = 0xf041, + fa_map_o = 0xf278, + fa_map_pin = 0xf276, + fa_map_signs = 0xf277, + fa_mars = 0xf222, + fa_mars_double = 0xf227, + fa_mars_stroke = 0xf229, + fa_mars_stroke_h = 0xf22b, + fa_mars_stroke_v = 0xf22a, + fa_maxcdn = 0xf136, + fa_meanpath = 0xf20c, + fa_medium = 0xf23a, + fa_medkit = 0xf0fa, + fa_meetup = 0xf2e0, + fa_meh_o = 0xf11a, + fa_mercury = 0xf223, + fa_microchip = 0xf2db, + fa_microphone = 0xf130, + fa_microphone_slash = 0xf131, + fa_minus = 0xf068, + fa_minus_circle = 0xf056, + fa_minus_square = 0xf146, + fa_minus_square_o = 0xf147, + fa_mixcloud = 0xf289, + fa_mobile = 0xf10b, + fa_mobile_phone = 0xf10b, + fa_modx = 0xf285, + fa_money = 0xf0d6, + fa_moon_o = 0xf186, + fa_mortar_board = 0xf19d, + fa_motorcycle = 0xf21c, + fa_mouse_pointer = 0xf245, + fa_music = 0xf001, + fa_navicon = 0xf0c9, + fa_neuter = 0xf22c, + fa_newspaper_o = 0xf1ea, + fa_object_group = 0xf247, + fa_object_ungroup = 0xf248, + fa_odnoklassniki = 0xf263, + fa_odnoklassniki_square = 0xf264, + fa_opencart = 0xf23d, + fa_openid = 0xf19b, + fa_opera = 0xf26a, + fa_optin_monster = 0xf23c, + fa_outdent = 0xf03b, + fa_pagelines = 0xf18c, + fa_paint_brush = 0xf1fc, + fa_paper_plane = 0xf1d8, + fa_paper_plane_o = 0xf1d9, + fa_paperclip = 0xf0c6, + fa_paragraph = 0xf1dd, + fa_paste = 0xf0ea, + fa_pause = 0xf04c, + fa_pause_circle = 0xf28b, + fa_pause_circle_o = 0xf28c, + fa_paw = 0xf1b0, + fa_paypal = 0xf1ed, + fa_pencil = 0xf040, + fa_pencil_square = 0xf14b, + fa_pencil_square_o = 0xf044, + fa_percent = 0xf295, + fa_phone = 0xf095, + fa_phone_square = 0xf098, + fa_photo = 0xf03e, + fa_picture_o = 0xf03e, + fa_pie_chart = 0xf200, + fa_pied_piper = 0xf2ae, + fa_pied_piper_alt = 0xf1a8, + fa_pied_piper_pp = 0xf1a7, + fa_pinterest = 0xf0d2, + fa_pinterest_p = 0xf231, + fa_pinterest_square = 0xf0d3, + fa_plane = 0xf072, + fa_play = 0xf04b, + fa_play_circle = 0xf144, + fa_play_circle_o = 0xf01d, + fa_plug = 0xf1e6, + fa_plus = 0xf067, + fa_plus_circle = 0xf055, + fa_plus_square = 0xf0fe, + fa_plus_square_o = 0xf196, + fa_podcast = 0xf2ce, + fa_power_off = 0xf011, + fa_print = 0xf02f, + fa_product_hunt = 0xf288, + fa_puzzle_piece = 0xf12e, + fa_qq = 0xf1d6, + fa_qrcode = 0xf029, + fa_question = 0xf128, + fa_question_circle = 0xf059, + fa_question_circle_o = 0xf29c, + fa_quora = 0xf2c4, + fa_quote_left = 0xf10d, + fa_quote_right = 0xf10e, + fa_ra = 0xf1d0, + fa_random = 0xf074, + fa_ravelry = 0xf2d9, + fa_rebel = 0xf1d0, + fa_recycle = 0xf1b8, + fa_reddit = 0xf1a1, + fa_reddit_alien = 0xf281, + fa_reddit_square = 0xf1a2, + fa_refresh = 0xf021, + fa_registered = 0xf25d, + fa_remove = 0xf00d, + fa_renren = 0xf18b, + fa_reorder = 0xf0c9, + fa_repeat = 0xf01e, + fa_reply = 0xf112, + fa_reply_all = 0xf122, + fa_resistance = 0xf1d0, + fa_retweet = 0xf079, + fa_rmb = 0xf157, + fa_road = 0xf018, + fa_rocket = 0xf135, + fa_rotate_left = 0xf0e2, + fa_rotate_right = 0xf01e, + fa_rouble = 0xf158, + fa_rss = 0xf09e, + fa_rss_square = 0xf143, + fa_rub = 0xf158, + fa_ruble = 0xf158, + fa_rupee = 0xf156, + fa_s15 = 0xf2cd, + fa_safari = 0xf267, + fa_save = 0xf0c7, + fa_scissors = 0xf0c4, + fa_scribd = 0xf28a, + fa_search = 0xf002, + fa_search_minus = 0xf010, + fa_search_plus = 0xf00e, + fa_sellsy = 0xf213, + fa_send = 0xf1d8, + fa_send_o = 0xf1d9, + fa_server = 0xf233, + fa_share = 0xf064, + fa_share_alt = 0xf1e0, + fa_share_alt_square = 0xf1e1, + fa_share_square = 0xf14d, + fa_share_square_o = 0xf045, + fa_shekel = 0xf20b, + fa_sheqel = 0xf20b, + fa_shield = 0xf132, + fa_ship = 0xf21a, + fa_shirtsinbulk = 0xf214, + fa_shopping_bag = 0xf290, + fa_shopping_basket = 0xf291, + fa_shopping_cart = 0xf07a, + fa_shower = 0xf2cc, + fa_sign_in = 0xf090, + fa_sign_language = 0xf2a7, + fa_sign_out = 0xf08b, + fa_signal = 0xf012, + fa_signing = 0xf2a7, + fa_simplybuilt = 0xf215, + fa_sitemap = 0xf0e8, + fa_skyatlas = 0xf216, + fa_skype = 0xf17e, + fa_slack = 0xf198, + fa_sliders = 0xf1de, + fa_slideshare = 0xf1e7, + fa_smile_o = 0xf118, + fa_snapchat = 0xf2ab, + fa_snapchat_ghost = 0xf2ac, + fa_snapchat_square = 0xf2ad, + fa_snowflake_o = 0xf2dc, + fa_soccer_ball_o = 0xf1e3, + fa_sort = 0xf0dc, + fa_sort_alpha_asc = 0xf15d, + fa_sort_alpha_desc = 0xf15e, + fa_sort_amount_asc = 0xf160, + fa_sort_amount_desc = 0xf161, + fa_sort_asc = 0xf0de, + fa_sort_desc = 0xf0dd, + fa_sort_down = 0xf0dd, + fa_sort_numeric_asc = 0xf162, + fa_sort_numeric_desc = 0xf163, + fa_sort_up = 0xf0de, + fa_soundcloud = 0xf1be, + fa_space_shuttle = 0xf197, + fa_spinner = 0xf110, + fa_spoon = 0xf1b1, + fa_spotify = 0xf1bc, + fa_square = 0xf0c8, + fa_square_o = 0xf096, + fa_stack_exchange = 0xf18d, + fa_stack_overflow = 0xf16c, + fa_star = 0xf005, + fa_star_half = 0xf089, + fa_star_half_empty = 0xf123, + fa_star_half_full = 0xf123, + fa_star_half_o = 0xf123, + fa_star_o = 0xf006, + fa_steam = 0xf1b6, + fa_steam_square = 0xf1b7, + fa_step_backward = 0xf048, + fa_step_forward = 0xf051, + fa_stethoscope = 0xf0f1, + fa_sticky_note = 0xf249, + fa_sticky_note_o = 0xf24a, + fa_stop = 0xf04d, + fa_stop_circle = 0xf28d, + fa_stop_circle_o = 0xf28e, + fa_street_view = 0xf21d, + fa_strikethrough = 0xf0cc, + fa_stumbleupon = 0xf1a4, + fa_stumbleupon_circle = 0xf1a3, + fa_subscript = 0xf12c, + fa_subway = 0xf239, + fa_suitcase = 0xf0f2, + fa_sun_o = 0xf185, + fa_superpowers = 0xf2dd, + fa_superscript = 0xf12b, + fa_support = 0xf1cd, + fa_table = 0xf0ce, + fa_tablet = 0xf10a, + fa_tachometer = 0xf0e4, + fa_tag = 0xf02b, + fa_tags = 0xf02c, + fa_tasks = 0xf0ae, + fa_taxi = 0xf1ba, + fa_telegram = 0xf2c6, + fa_television = 0xf26c, + fa_tencent_weibo = 0xf1d5, + fa_terminal = 0xf120, + fa_text_height = 0xf034, + fa_text_width = 0xf035, + fa_th = 0xf00a, + fa_th_large = 0xf009, + fa_th_list = 0xf00b, + fa_themeisle = 0xf2b2, + fa_thermometer = 0xf2c7, + fa_thermometer_0 = 0xf2cb, + fa_thermometer_1 = 0xf2ca, + fa_thermometer_2 = 0xf2c9, + fa_thermometer_3 = 0xf2c8, + fa_thermometer_4 = 0xf2c7, + fa_thermometer_empty = 0xf2cb, + fa_thermometer_full = 0xf2c7, + fa_thermometer_half = 0xf2c9, + fa_thermometer_quarter = 0xf2ca, + fa_thermometer_three_quarters = 0xf2c8, + fa_thumb_tack = 0xf08d, + fa_thumbs_down = 0xf165, + fa_thumbs_o_down = 0xf088, + fa_thumbs_o_up = 0xf087, + fa_thumbs_up = 0xf164, + fa_ticket = 0xf145, + fa_times = 0xf00d, + fa_times_circle = 0xf057, + fa_times_circle_o = 0xf05c, + fa_times_rectangle = 0xf2d3, + fa_times_rectangle_o = 0xf2d4, + fa_tint = 0xf043, + fa_toggle_down = 0xf150, + fa_toggle_left = 0xf191, + fa_toggle_off = 0xf204, + fa_toggle_on = 0xf205, + fa_toggle_right = 0xf152, + fa_toggle_up = 0xf151, + fa_trademark = 0xf25c, + fa_train = 0xf238, + fa_transgender = 0xf224, + fa_transgender_alt = 0xf225, + fa_trash = 0xf1f8, + fa_trash_o = 0xf014, + fa_tree = 0xf1bb, + fa_trello = 0xf181, + fa_tripadvisor = 0xf262, + fa_trophy = 0xf091, + fa_truck = 0xf0d1, + fa_try = 0xf195, + fa_tty = 0xf1e4, + fa_tumblr = 0xf173, + fa_tumblr_square = 0xf174, + fa_turkish_lira = 0xf195, + fa_tv = 0xf26c, + fa_twitch = 0xf1e8, + fa_twitter = 0xf099, + fa_twitter_square = 0xf081, + fa_umbrella = 0xf0e9, + fa_underline = 0xf0cd, + fa_undo = 0xf0e2, + fa_universal_access = 0xf29a, + fa_university = 0xf19c, + fa_unlink = 0xf127, + fa_unlock = 0xf09c, + fa_unlock_alt = 0xf13e, + fa_unsorted = 0xf0dc, + fa_upload = 0xf093, + fa_usb = 0xf287, + fa_usd = 0xf155, + fa_user = 0xf007, + fa_user_circle = 0xf2bd, + fa_user_circle_o = 0xf2be, + fa_user_md = 0xf0f0, + fa_user_o = 0xf2c0, + fa_user_plus = 0xf234, + fa_user_secret = 0xf21b, + fa_user_times = 0xf235, + fa_users = 0xf0c0, + fa_vcard = 0xf2bb, + fa_vcard_o = 0xf2bc, + fa_venus = 0xf221, + fa_venus_double = 0xf226, + fa_venus_mars = 0xf228, + fa_viacoin = 0xf237, + fa_viadeo = 0xf2a9, + fa_viadeo_square = 0xf2aa, + fa_video_camera = 0xf03d, + fa_vimeo = 0xf27d, + fa_vimeo_square = 0xf194, + fa_vine = 0xf1ca, + fa_vk = 0xf189, + fa_volume_control_phone = 0xf2a0, + fa_volume_down = 0xf027, + fa_volume_off = 0xf026, + fa_volume_up = 0xf028, + fa_warning = 0xf071, + fa_wechat = 0xf1d7, + fa_weibo = 0xf18a, + fa_weixin = 0xf1d7, + fa_whatsapp = 0xf232, + fa_wheelchair = 0xf193, + fa_wheelchair_alt = 0xf29b, + fa_wifi = 0xf1eb, + fa_wikipedia_w = 0xf266, + fa_window_close = 0xf2d3, + fa_window_close_o = 0xf2d4, + fa_window_maximize = 0xf2d0, + fa_window_minimize = 0xf2d1, + fa_window_restore = 0xf2d2, + fa_windows = 0xf17a, + fa_won = 0xf159, + fa_wordpress = 0xf19a, + fa_wpbeginner = 0xf297, + fa_wpexplorer = 0xf2de, + fa_wpforms = 0xf298, + fa_wrench = 0xf0ad, + fa_xing = 0xf168, + fa_xing_square = 0xf169, + fa_y_combinator = 0xf23b, + fa_y_combinator_square = 0xf1d4, + fa_yahoo = 0xf19e, + fa_yc = 0xf23b, + fa_yc_square = 0xf1d4, + fa_yelp = 0xf1e9, + fa_yen = 0xf157, + fa_yoast = 0xf2b1, + fa_youtube = 0xf167, + fa_youtube_play = 0xf16a, + fa_youtube_square = 0xf166, + + // ruleset icons in circles + fa_osu_osu_o = 0xe000, + fa_osu_mania_o = 0xe001, + fa_osu_fruits_o = 0xe002, + fa_osu_taiko_o = 0xe003, + + // ruleset icons without circles + fa_osu_filled_circle = 0xe004, + fa_osu_cross_o = 0xe005, + fa_osu_logo = 0xe006, + fa_osu_chevron_down_o = 0xe007, + fa_osu_edit_o = 0xe033, + fa_osu_left_o = 0xe034, + fa_osu_right_o = 0xe035, + fa_osu_charts = 0xe036, + fa_osu_solo = 0xe037, + fa_osu_multi = 0xe038, + fa_osu_gear = 0xe039, + + // misc icons + fa_osu_bat = 0xe008, + fa_osu_bubble = 0xe009, + fa_osu_bubble_pop = 0xe02e, + fa_osu_dice = 0xe011, + fa_osu_heart1 = 0xe02f, + fa_osu_heart1_break = 0xe030, + fa_osu_hot = 0xe031, + fa_osu_list_search = 0xe032, + + //osu! playstyles + fa_osu_playstyle_tablet = 0xe02a, + fa_osu_playstyle_mouse = 0xe029, + fa_osu_playstyle_keyboard = 0xe02b, + fa_osu_playstyle_touch = 0xe02c, + + // osu! difficulties + fa_osu_easy_osu = 0xe015, + fa_osu_normal_osu = 0xe016, + fa_osu_hard_osu = 0xe017, + fa_osu_insane_osu = 0xe018, + fa_osu_expert_osu = 0xe019, + + // taiko difficulties + fa_osu_easy_taiko = 0xe01a, + fa_osu_normal_taiko = 0xe01b, + fa_osu_hard_taiko = 0xe01c, + fa_osu_insane_taiko = 0xe01d, + fa_osu_expert_taiko = 0xe01e, + + // fruits difficulties + fa_osu_easy_fruits = 0xe01f, + fa_osu_normal_fruits = 0xe020, + fa_osu_hard_fruits = 0xe021, + fa_osu_insane_fruits = 0xe022, + fa_osu_expert_fruits = 0xe023, + + // mania difficulties + fa_osu_easy_mania = 0xe024, + fa_osu_normal_mania = 0xe025, + fa_osu_hard_mania = 0xe026, + fa_osu_insane_mania = 0xe027, + fa_osu_expert_mania = 0xe028, + + // mod icons + fa_osu_mod_perfect = 0xe049, + fa_osu_mod_autopilot = 0xe03a, + fa_osu_mod_auto = 0xe03b, + fa_osu_mod_cinema = 0xe03c, + fa_osu_mod_doubletime = 0xe03d, + fa_osu_mod_easy = 0xe03e, + fa_osu_mod_flashlight = 0xe03f, + fa_osu_mod_halftime = 0xe040, + fa_osu_mod_hardrock = 0xe041, + fa_osu_mod_hidden = 0xe042, + fa_osu_mod_nightcore = 0xe043, + fa_osu_mod_nofail = 0xe044, + fa_osu_mod_relax = 0xe045, + fa_osu_mod_spunout = 0xe046, + fa_osu_mod_suddendeath = 0xe047, + fa_osu_mod_target = 0xe048, + fa_osu_mod_bg = 0xe04a, + } +} diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs index 8c1c78906d..c9389bb9e2 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs @@ -1,63 +1,63 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.MathUtils; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Transforms; - -namespace osu.Game.Graphics.Sprites -{ - public class OsuSpriteText : SpriteText - { - public const float FONT_SIZE = 16; - - public OsuSpriteText() - { - Shadow = true; - TextSize = FONT_SIZE; - } - - protected override Drawable CreateFallbackCharacterDrawable() - { - var tex = GetTextureForCharacter('?'); - - if (tex != null) - { - float adjust = (RNG.NextSingle() - 0.5f) * 2; - return new Sprite - { - Texture = tex, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Scale = new Vector2(1 + adjust * 0.2f), - Rotation = adjust * 15, - Colour = Color4.White, - }; - } - - return base.CreateFallbackCharacterDrawable(); - } - } - - public static class OsuSpriteTextTransformExtensions - { - /// - /// Sets to a new value after a duration. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformTextTo(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None) - where T : OsuSpriteText - => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing); - - /// - /// Sets to a new value after a duration. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformTextTo(this TransformSequence t, string newText, double duration = 0, Easing easing = Easing.None) - where T : OsuSpriteText - => t.Append(o => o.TransformTextTo(newText, duration, easing)); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.MathUtils; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Graphics.Sprites +{ + public class OsuSpriteText : SpriteText + { + public const float FONT_SIZE = 16; + + public OsuSpriteText() + { + Shadow = true; + TextSize = FONT_SIZE; + } + + protected override Drawable CreateFallbackCharacterDrawable() + { + var tex = GetTextureForCharacter('?'); + + if (tex != null) + { + float adjust = (RNG.NextSingle() - 0.5f) * 2; + return new Sprite + { + Texture = tex, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Scale = new Vector2(1 + adjust * 0.2f), + Rotation = adjust * 15, + Colour = Color4.White, + }; + } + + return base.CreateFallbackCharacterDrawable(); + } + } + + public static class OsuSpriteTextTransformExtensions + { + /// + /// Sets to a new value after a duration. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformTextTo(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None) + where T : OsuSpriteText + => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing); + + /// + /// Sets to a new value after a duration. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformTextTo(this TransformSequence t, string newText, double duration = 0, Easing easing = Easing.None) + where T : OsuSpriteText + => t.Append(o => o.TransformTextTo(newText, duration, easing)); + } +} diff --git a/osu.Game/Graphics/Textures/LargeTextureStore.cs b/osu.Game/Graphics/Textures/LargeTextureStore.cs index 17fa7d4e24..4dcbb1220d 100644 --- a/osu.Game/Graphics/Textures/LargeTextureStore.cs +++ b/osu.Game/Graphics/Textures/LargeTextureStore.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Textures; -using osu.Framework.IO.Stores; - -namespace osu.Game.Graphics.Textures -{ - /// - /// A texture store that bypasses atlasing. - /// - public class LargeTextureStore : TextureStore - { - public LargeTextureStore(IResourceStore store = null) : base(store, false) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; + +namespace osu.Game.Graphics.Textures +{ + /// + /// A texture store that bypasses atlasing. + /// + public class LargeTextureStore : TextureStore + { + public LargeTextureStore(IResourceStore store = null) : base(store, false) + { + } + } +} diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 3f348dc19d..19135b8550 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public class BackButton : TwoLayerButton - { - public BackButton() - { - Text = @"Back"; - Icon = FontAwesome.fa_osu_left_o; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Pink; - HoverColour = colours.PinkDark; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class BackButton : TwoLayerButton + { + public BackButton() + { + Text = @"back"; + Icon = FontAwesome.fa_osu_left_o; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Pink; + HoverColour = colours.PinkDark; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index 6381c242fe..16c63a12f4 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -1,138 +1,138 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using System; - -namespace osu.Game.Graphics.UserInterface -{ - public class Bar : Container, IHasAccentColour - { - private readonly Box background; - private readonly Box bar; - - private const int resize_duration = 250; - - private const Easing easing = Easing.InOutCubic; - - private float length; - - /// - /// Length of the bar, ranges from 0 to 1 - /// - public float Length - { - get - { - return length; - } - set - { - length = MathHelper.Clamp(value, 0, 1); - updateBarLength(); - } - } - - public Color4 BackgroundColour - { - get - { - return background.Colour; - } - set - { - background.Colour = value; - } - } - - public Color4 AccentColour - { - get - { - return bar.Colour; - } - set - { - bar.Colour = value; - } - } - - private BarDirection direction = BarDirection.LeftToRight; - public BarDirection Direction - { - get - { - return direction; - } - set - { - direction = value; - updateBarLength(); - } - } - - public Bar() - { - Children = new[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(0, 0, 0, 0) - }, - bar = new Box - { - RelativeSizeAxes = Axes.Both, - Width = 0, - }, - }; - } - - private void updateBarLength() - { - switch (direction) - { - case BarDirection.LeftToRight: - case BarDirection.RightToLeft: - bar.ResizeTo(new Vector2(length, 1), resize_duration, easing); - break; - - case BarDirection.TopToBottom: - case BarDirection.BottomToTop: - bar.ResizeTo(new Vector2(1, length), resize_duration, easing); - break; - } - - switch (direction) - { - case BarDirection.LeftToRight: - case BarDirection.TopToBottom: - bar.Anchor = Anchor.TopLeft; - bar.Origin = Anchor.TopLeft; - break; - - case BarDirection.RightToLeft: - case BarDirection.BottomToTop: - bar.Anchor = Anchor.BottomRight; - bar.Origin = Anchor.BottomRight; - break; - } - } - } - - [Flags] - public enum BarDirection - { - LeftToRight = 1 << 0, - RightToLeft = 1 << 1, - TopToBottom = 1 << 2, - BottomToTop = 1 << 3, - - Vertical = TopToBottom | BottomToTop, - Horizontal = LeftToRight | RightToLeft, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using System; + +namespace osu.Game.Graphics.UserInterface +{ + public class Bar : Container, IHasAccentColour + { + private readonly Box background; + private readonly Box bar; + + private const int resize_duration = 250; + + private const Easing easing = Easing.InOutCubic; + + private float length; + + /// + /// Length of the bar, ranges from 0 to 1 + /// + public float Length + { + get + { + return length; + } + set + { + length = MathHelper.Clamp(value, 0, 1); + updateBarLength(); + } + } + + public Color4 BackgroundColour + { + get + { + return background.Colour; + } + set + { + background.Colour = value; + } + } + + public Color4 AccentColour + { + get + { + return bar.Colour; + } + set + { + bar.Colour = value; + } + } + + private BarDirection direction = BarDirection.LeftToRight; + public BarDirection Direction + { + get + { + return direction; + } + set + { + direction = value; + updateBarLength(); + } + } + + public Bar() + { + Children = new[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0, 0, 0, 0) + }, + bar = new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0, + }, + }; + } + + private void updateBarLength() + { + switch (direction) + { + case BarDirection.LeftToRight: + case BarDirection.RightToLeft: + bar.ResizeTo(new Vector2(length, 1), resize_duration, easing); + break; + + case BarDirection.TopToBottom: + case BarDirection.BottomToTop: + bar.ResizeTo(new Vector2(1, length), resize_duration, easing); + break; + } + + switch (direction) + { + case BarDirection.LeftToRight: + case BarDirection.TopToBottom: + bar.Anchor = Anchor.TopLeft; + bar.Origin = Anchor.TopLeft; + break; + + case BarDirection.RightToLeft: + case BarDirection.BottomToTop: + bar.Anchor = Anchor.BottomRight; + bar.Origin = Anchor.BottomRight; + break; + } + } + } + + [Flags] + public enum BarDirection + { + LeftToRight = 1 << 0, + RightToLeft = 1 << 1, + TopToBottom = 1 << 2, + BottomToTop = 1 << 3, + + Vertical = TopToBottom | BottomToTop, + Horizontal = LeftToRight | RightToLeft, + } +} diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 3bc1598191..d065ecdd5f 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -1,77 +1,77 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Graphics.UserInterface -{ - public class BarGraph : FillFlowContainer - { - /// - /// Manually sets the max value, if null is instead used - /// - public float? MaxValue { get; set; } - - private BarDirection direction = BarDirection.BottomToTop; - public new BarDirection Direction - { - get - { - return direction; - } - set - { - direction = value; - base.Direction = (direction & BarDirection.Horizontal) > 0 ? FillDirection.Vertical : FillDirection.Horizontal; - foreach (var bar in Children) - { - bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); - bar.Direction = direction; - } - } - } - - /// - /// A list of floats that defines the length of each - /// - public IEnumerable Values - { - set - { - List bars = Children.ToList(); - foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) - { - float length = MaxValue ?? value.Max(); - if (length != 0) - length = bar.Value / length; - - float size = value.Count(); - if (size != 0) - size = 1.0f / size; - - if (bar.Bar != null) - { - bar.Bar.Length = length; - bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1); - } - else - { - Add(new Bar - { - RelativeSizeAxes = Axes.Both, - Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1), - Length = length, - Direction = Direction, - }); - } - } - //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards - RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList()); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Graphics.UserInterface +{ + public class BarGraph : FillFlowContainer + { + /// + /// Manually sets the max value, if null is instead used + /// + public float? MaxValue { get; set; } + + private BarDirection direction = BarDirection.BottomToTop; + public new BarDirection Direction + { + get + { + return direction; + } + set + { + direction = value; + base.Direction = (direction & BarDirection.Horizontal) > 0 ? FillDirection.Vertical : FillDirection.Horizontal; + foreach (var bar in Children) + { + bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); + bar.Direction = direction; + } + } + } + + /// + /// A list of floats that defines the length of each + /// + public IEnumerable Values + { + set + { + List bars = Children.ToList(); + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) + { + float length = MaxValue ?? value.Max(); + if (length != 0) + length = bar.Value / length; + + float size = value.Count(); + if (size != 0) + size = 1.0f / size; + + if (bar.Bar != null) + { + bar.Bar.Length = length; + bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1); + } + else + { + Add(new Bar + { + RelativeSizeAxes = Axes.Both, + Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1), + Length = length, + Direction = Direction, + }); + } + } + //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards + RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList()); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index 7f30bd715a..3f59eeca97 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -1,94 +1,94 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using osu.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using System.Linq; - -namespace osu.Game.Graphics.UserInterface -{ - public class BreadcrumbControl : OsuTabControl - { - private const float padding = 10; - - protected override TabItem CreateTabItem(T value) => new BreadcrumbTabItem(value); - - protected override float StripWidth() => base.StripWidth() - (padding + 8); - - public BreadcrumbControl() - { - Height = 26; - TabContainer.Spacing = new Vector2(padding, 0f); - Current.ValueChanged += tab => - { - foreach (var t in TabContainer.Children.OfType()) - { - var tIndex = TabContainer.IndexOf(t); - var tabIndex = TabContainer.IndexOf(TabMap[tab]); - - t.State = tIndex < tabIndex ? Visibility.Hidden : Visibility.Visible; - t.Chevron.FadeTo(tIndex <= tabIndex ? 0f : 1f, 500, Easing.OutQuint); - } - }; - } - - private class BreadcrumbTabItem : OsuTabItem, IStateful - { - public event Action StateChanged; - - public readonly SpriteIcon Chevron; - - //don't allow clicking between transitions and don't make the chevron clickable - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Alpha == 1f && Text.ReceiveMouseInputAt(screenSpacePos); - - public override bool HandleKeyboardInput => State == Visibility.Visible; - public override bool HandleMouseInput => State == Visibility.Visible; - - private Visibility state; - - public Visibility State - { - get { return state; } - set - { - if (value == state) return; - state = value; - - const float transition_duration = 500; - - if (State == Visibility.Visible) - { - this.FadeIn(transition_duration, Easing.OutQuint); - this.ScaleTo(new Vector2(1f), transition_duration, Easing.OutQuint); - } - else - { - this.FadeOut(transition_duration, Easing.OutQuint); - this.ScaleTo(new Vector2(0.8f, 1f), transition_duration, Easing.OutQuint); - } - - StateChanged?.Invoke(State); - } - } - - public BreadcrumbTabItem(T value) : base(value) - { - Text.TextSize = 16; - Padding = new MarginPadding { Right = padding + 8 }; //padding + chevron width - Add(Chevron = new SpriteIcon - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, - Size = new Vector2(12), - Icon = FontAwesome.fa_chevron_right, - Margin = new MarginPadding { Left = padding }, - Alpha = 0f, - }); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using System.Linq; + +namespace osu.Game.Graphics.UserInterface +{ + public class BreadcrumbControl : OsuTabControl + { + private const float padding = 10; + + protected override TabItem CreateTabItem(T value) => new BreadcrumbTabItem(value); + + protected override float StripWidth() => base.StripWidth() - (padding + 8); + + public BreadcrumbControl() + { + Height = 26; + TabContainer.Spacing = new Vector2(padding, 0f); + Current.ValueChanged += tab => + { + foreach (var t in TabContainer.Children.OfType()) + { + var tIndex = TabContainer.IndexOf(t); + var tabIndex = TabContainer.IndexOf(TabMap[tab]); + + t.State = tIndex < tabIndex ? Visibility.Hidden : Visibility.Visible; + t.Chevron.FadeTo(tIndex <= tabIndex ? 0f : 1f, 500, Easing.OutQuint); + } + }; + } + + private class BreadcrumbTabItem : OsuTabItem, IStateful + { + public event Action StateChanged; + + public readonly SpriteIcon Chevron; + + //don't allow clicking between transitions and don't make the chevron clickable + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Alpha == 1f && Text.ReceiveMouseInputAt(screenSpacePos); + + public override bool HandleKeyboardInput => State == Visibility.Visible; + public override bool HandleMouseInput => State == Visibility.Visible; + + private Visibility state; + + public Visibility State + { + get { return state; } + set + { + if (value == state) return; + state = value; + + const float transition_duration = 500; + + if (State == Visibility.Visible) + { + this.FadeIn(transition_duration, Easing.OutQuint); + this.ScaleTo(new Vector2(1f), transition_duration, Easing.OutQuint); + } + else + { + this.FadeOut(transition_duration, Easing.OutQuint); + this.ScaleTo(new Vector2(0.8f, 1f), transition_duration, Easing.OutQuint); + } + + StateChanged?.Invoke(State); + } + } + + public BreadcrumbTabItem(T value) : base(value) + { + Text.TextSize = 16; + Padding = new MarginPadding { Right = padding + 8 }; //padding + chevron width + Add(Chevron = new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, + Size = new Vector2(12), + Icon = FontAwesome.fa_chevron_right, + Margin = new MarginPadding { Left = padding }, + Alpha = 0f, + }); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 99da8755bf..17d22587ad 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -1,284 +1,284 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Containers; -using osu.Framework.Configuration; -using osu.Framework.Input; - -namespace osu.Game.Graphics.UserInterface -{ - public class DialogButton : OsuClickableContainer - { - private const float hover_width = 0.9f; - private const float hover_duration = 500; - private const float glow_fade_duration = 250; - private const float click_duration = 200; - - public readonly BindableBool Selected = new BindableBool(); - - private readonly Container backgroundContainer; - private readonly Container colourContainer; - private readonly Container glowContainer; - private readonly Box leftGlow; - private readonly Box centerGlow; - private readonly Box rightGlow; - private readonly Box background; - private readonly SpriteText spriteText; - private Vector2 hoverSpacing => new Vector2(3f, 0f); - - public DialogButton() - { - RelativeSizeAxes = Axes.X; - - Children = new Drawable[] - { - backgroundContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Width = 1f, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = backgroundColour, - }, - }, - }, - glowContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Width = 1f, - Alpha = 0f, - Children = new Drawable[] - { - leftGlow = new Box - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Width = 0.125f, - }, - centerGlow = new Box - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Width = 0.75f, - }, - rightGlow = new Box - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Width = 0.125f, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Masking = true, - Children = new Drawable[] - { - colourContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Width = 0.8f, - Masking = true, - MaskingSmoothness = 2, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, - }, - Colour = ButtonColour, - Shear = new Vector2(0.2f, 0), - Children = new Drawable[] - { - new Box - { - EdgeSmoothness = new Vector2(2, 0), - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 0, - Children = new[] - { - new Triangles - { - RelativeSizeAxes = Axes.Both, - TriangleScale = 4, - ColourDark = OsuColour.Gray(0.88f), - Shear = new Vector2(-0.2f, 0), - }, - }, - }, - }, - }, - }, - }, - spriteText = new OsuSpriteText - { - Text = Text, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - TextSize = 28, - Font = "Exo2.0-Bold", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.1f), - Colour = Color4.White, - }, - }; - - updateGlow(); - - Selected.ValueChanged += selectionChanged; - } - - private Color4 buttonColour; - public Color4 ButtonColour - { - get - { - return buttonColour; - } - set - { - buttonColour = value; - updateGlow(); - colourContainer.Colour = value; - } - } - - private Color4 backgroundColour = OsuColour.Gray(34); - public Color4 BackgroundColour - { - get - { - return backgroundColour; - } - set - { - backgroundColour = value; - background.Colour = value; - } - } - - private string text; - public string Text - { - get - { - return text; - } - set - { - text = value; - spriteText.Text = Text; - } - } - - private float textSize = 28; - public float TextSize - { - get - { - return textSize; - } - set - { - textSize = value; - spriteText.TextSize = value; - } - } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); - - protected override bool OnClick(InputState state) - { - colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); - flash(); - - this.Delay(click_duration).Schedule(delegate - { - colourContainer.ResizeTo(new Vector2(0.8f, 1f)); - spriteText.Spacing = Vector2.Zero; - glowContainer.FadeOut(); - }); - - return base.OnClick(state); - } - - protected override bool OnHover(InputState state) - { - base.OnHover(state); - - Selected.Value = true; - return true; - } - - protected override void OnHoverLost(InputState state) - { - base.OnHoverLost(state); - Selected.Value = false; - } - - private void selectionChanged(bool isSelected) - { - if (isSelected) - { - spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); - glowContainer.FadeIn(glow_fade_duration, Easing.Out); - } - else - { - colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); - spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); - glowContainer.FadeOut(glow_fade_duration, Easing.Out); - } - } - - private void flash() - { - var flash = new Box - { - RelativeSizeAxes = Axes.Both - }; - - colourContainer.Add(flash); - - flash.Colour = ButtonColour; - flash.Blending = BlendingMode.Additive; - flash.Alpha = 0.3f; - flash.FadeOutFromOne(click_duration); - flash.Expire(); - } - - private void updateGlow() - { - leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); - centerGlow.Colour = ButtonColour; - rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Framework.Configuration; +using osu.Framework.Input; + +namespace osu.Game.Graphics.UserInterface +{ + public class DialogButton : OsuClickableContainer + { + private const float hover_width = 0.9f; + private const float hover_duration = 500; + private const float glow_fade_duration = 250; + private const float click_duration = 200; + + public readonly BindableBool Selected = new BindableBool(); + + private readonly Container backgroundContainer; + private readonly Container colourContainer; + private readonly Container glowContainer; + private readonly Box leftGlow; + private readonly Box centerGlow; + private readonly Box rightGlow; + private readonly Box background; + private readonly SpriteText spriteText; + private Vector2 hoverSpacing => new Vector2(3f, 0f); + + public DialogButton() + { + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + backgroundContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Width = 1f, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = backgroundColour, + }, + }, + }, + glowContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Width = 1f, + Alpha = 0f, + Children = new Drawable[] + { + leftGlow = new Box + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Width = 0.125f, + }, + centerGlow = new Box + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Width = 0.75f, + }, + rightGlow = new Box + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Width = 0.125f, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + colourContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Width = 0.8f, + Masking = true, + MaskingSmoothness = 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Colour = ButtonColour, + Shear = new Vector2(0.2f, 0), + Children = new Drawable[] + { + new Box + { + EdgeSmoothness = new Vector2(2, 0), + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 0, + Children = new[] + { + new Triangles + { + RelativeSizeAxes = Axes.Both, + TriangleScale = 4, + ColourDark = OsuColour.Gray(0.88f), + Shear = new Vector2(-0.2f, 0), + }, + }, + }, + }, + }, + }, + }, + spriteText = new OsuSpriteText + { + Text = Text, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 28, + Font = "Exo2.0-Bold", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.1f), + Colour = Color4.White, + }, + }; + + updateGlow(); + + Selected.ValueChanged += selectionChanged; + } + + private Color4 buttonColour; + public Color4 ButtonColour + { + get + { + return buttonColour; + } + set + { + buttonColour = value; + updateGlow(); + colourContainer.Colour = value; + } + } + + private Color4 backgroundColour = OsuColour.Gray(34); + public Color4 BackgroundColour + { + get + { + return backgroundColour; + } + set + { + backgroundColour = value; + background.Colour = value; + } + } + + private string text; + public string Text + { + get + { + return text; + } + set + { + text = value; + spriteText.Text = Text; + } + } + + private float textSize = 28; + public float TextSize + { + get + { + return textSize; + } + set + { + textSize = value; + spriteText.TextSize = value; + } + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); + + protected override bool OnClick(InputState state) + { + colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); + flash(); + + this.Delay(click_duration).Schedule(delegate + { + colourContainer.ResizeTo(new Vector2(0.8f, 1f)); + spriteText.Spacing = Vector2.Zero; + glowContainer.FadeOut(); + }); + + return base.OnClick(state); + } + + protected override bool OnHover(InputState state) + { + base.OnHover(state); + + Selected.Value = true; + return true; + } + + protected override void OnHoverLost(InputState state) + { + base.OnHoverLost(state); + Selected.Value = false; + } + + private void selectionChanged(bool isSelected) + { + if (isSelected) + { + spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); + colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); + glowContainer.FadeIn(glow_fade_duration, Easing.Out); + } + else + { + colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); + spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); + glowContainer.FadeOut(glow_fade_duration, Easing.Out); + } + } + + private void flash() + { + var flash = new Box + { + RelativeSizeAxes = Axes.Both + }; + + colourContainer.Add(flash); + + flash.Colour = ButtonColour; + flash.Blending = BlendingMode.Additive; + flash.Alpha = 0.3f; + flash.FadeOutFromOne(click_duration); + flash.Expire(); + } + + private void updateGlow() + { + leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); + centerGlow.Colour = ButtonColour; + rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f)); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 33786252ab..6500578de3 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -1,58 +1,58 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using OpenTK.Input; -using osu.Framework.Input; -using System; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// A textbox which holds focus eagerly. - /// - public class FocusedTextBox : OsuTextBox - { - protected override Color4 BackgroundUnfocused => new Color4(10, 10, 10, 255); - protected override Color4 BackgroundFocused => new Color4(10, 10, 10, 255); - - public Action Exit; - - private bool focus; - public bool HoldFocus - { - get { return focus; } - set - { - focus = value; - if (!focus && HasFocus) - GetContainingInputManager().ChangeFocus(null); - } - } - - // We may not be focused yet, but we need to handle keyboard input to be able to request focus - public override bool HandleKeyboardInput => HoldFocus || base.HandleKeyboardInput; - - protected override void OnFocus(InputState state) - { - base.OnFocus(state); - BorderThickness = 0; - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (!args.Repeat && args.Key == Key.Escape) - { - if (Text.Length > 0) - Text = string.Empty; - else - Exit?.Invoke(); - return true; - } - - return base.OnKeyDown(state, args); - } - - public override bool RequestsFocus => HoldFocus; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework.Input; +using System; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A textbox which holds focus eagerly. + /// + public class FocusedTextBox : OsuTextBox + { + protected override Color4 BackgroundUnfocused => new Color4(10, 10, 10, 255); + protected override Color4 BackgroundFocused => new Color4(10, 10, 10, 255); + + public Action Exit; + + private bool focus; + public bool HoldFocus + { + get { return focus; } + set + { + focus = value; + if (!focus && HasFocus) + GetContainingInputManager().ChangeFocus(null); + } + } + + // We may not be focused yet, but we need to handle keyboard input to be able to request focus + public override bool HandleKeyboardInput => HoldFocus || base.HandleKeyboardInput; + + protected override void OnFocus(InputState state) + { + base.OnFocus(state); + BorderThickness = 0; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!args.Repeat && args.Key == Key.Escape) + { + if (Text.Length > 0) + Text = string.Empty; + else + Exit?.Invoke(); + return true; + } + + return base.OnKeyDown(state, args); + } + + public override bool RequestsFocus => HoldFocus; + } +} diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 9fb0594524..1a9d124dfb 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Extensions; -using osu.Framework.Input; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// Adds hover and click sounds to a drawable. - /// Does not draw anything. - /// - public class HoverClickSounds : HoverSounds - { - private SampleChannel sampleClick; - - public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) : base(sampleSet) - { - } - - protected override bool OnClick(InputState state) - { - sampleClick?.Play(); - return base.OnClick(state); - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleClick = audio.Sample.Get($@"UI/generic-select{SampleSet.GetDescription()}"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Extensions; +using osu.Framework.Input; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Adds hover and click sounds to a drawable. + /// Does not draw anything. + /// + public class HoverClickSounds : HoverSounds + { + private SampleChannel sampleClick; + + public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) : base(sampleSet) + { + } + + protected override bool OnClick(InputState state) + { + sampleClick?.Play(); + return base.OnClick(state); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleClick = audio.Sample.Get($@"UI/generic-select{SampleSet.GetDescription()}"); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index fabb344872..dc35c75cd6 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -1,53 +1,53 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// Adds hover sounds to a drawable. - /// Does not draw anything. - /// - public class HoverSounds : CompositeDrawable - { - private SampleChannel sampleHover; - - protected readonly HoverSampleSet SampleSet; - - public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) - { - SampleSet = sampleSet; - RelativeSizeAxes = Axes.Both; - } - - protected override bool OnHover(InputState state) - { - sampleHover?.Play(); - return base.OnHover(state); - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleHover = audio.Sample.Get($@"UI/generic-hover{SampleSet.GetDescription()}"); - } - } - - public enum HoverSampleSet - { - [Description("")] - Loud, - [Description("-soft")] - Normal, - [Description("-softer")] - Soft - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Adds hover sounds to a drawable. + /// Does not draw anything. + /// + public class HoverSounds : CompositeDrawable + { + private SampleChannel sampleHover; + + protected readonly HoverSampleSet SampleSet; + + public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) + { + SampleSet = sampleSet; + RelativeSizeAxes = Axes.Both; + } + + protected override bool OnHover(InputState state) + { + sampleHover?.Play(); + return base.OnHover(state); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleHover = audio.Sample.Get($@"UI/generic-hover{SampleSet.GetDescription()}"); + } + } + + public enum HoverSampleSet + { + [Description("")] + Loud, + [Description("-soft")] + Normal, + [Description("-softer")] + Soft + } +} diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index 5b266d9a59..5a25fe641d 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -1,180 +1,180 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Graphics.UserInterface -{ - public class IconButton : OsuClickableContainer - { - public const float BUTTON_SIZE = 30; - - private Color4? flashColour; - /// - /// The colour that should be flashed when the is clicked. - /// - public Color4 FlashColour - { - get { return flashColour ?? Color4.White; } - set { flashColour = value; } - } - - private Color4? iconColour; - /// - /// The icon colour. This does not affect . - /// - public Color4 IconColour - { - get { return iconColour ?? Color4.White; } - set - { - iconColour = value; - icon.Colour = value; - } - } - - private Color4? iconHoverColour; - /// - /// The icon colour while the is hovered. - /// - public Color4 IconHoverColour - { - get { return iconHoverColour ?? IconColour; } - set { iconHoverColour = value; } - } - - private Color4? hoverColour; - /// - /// The background colour of the while it is hovered. - /// - public Color4 HoverColour - { - get { return hoverColour ?? Color4.White; } - set - { - hoverColour = value; - hover.Colour = value; - } - } - - /// - /// The icon. - /// - public FontAwesome Icon - { - get { return icon.Icon; } - set { icon.Icon = value; } - } - - /// - /// The icon scale. This does not affect . - /// - public Vector2 IconScale - { - get { return icon.Scale; } - set { icon.Scale = value; } - } - - /// - /// The size of the while it is not being pressed. - /// - public Vector2 ButtonSize - { - get { return content.Size; } - set { content.Size = value; } - } - - private readonly Container content; - private readonly SpriteIcon icon; - private readonly Box hover; - - public IconButton() - { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - content = new Container - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(BUTTON_SIZE), - CornerRadius = 5, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.04f), - Type = EdgeEffectType.Shadow, - Radius = 5, - }, - Children = new Drawable[] - { - hover = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - icon = new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(18), - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (hoverColour == null) - HoverColour = colours.Yellow.Opacity(0.6f); - - if (flashColour == null) - FlashColour = colours.Yellow; - - Enabled.ValueChanged += enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint); - } - - protected override bool OnHover(InputState state) - { - hover.FadeIn(500, Easing.OutQuint); - icon.FadeColour(IconHoverColour, 500, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - hover.FadeOut(500, Easing.OutQuint); - icon.FadeColour(IconColour, 500, Easing.OutQuint); - base.OnHoverLost(state); - } - - protected override bool OnClick(InputState state) - { - hover.FlashColour(FlashColour, 800, Easing.OutQuint); - return base.OnClick(state); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - content.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - content.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Graphics.UserInterface +{ + public class IconButton : OsuClickableContainer + { + public const float BUTTON_SIZE = 30; + + private Color4? flashColour; + /// + /// The colour that should be flashed when the is clicked. + /// + public Color4 FlashColour + { + get { return flashColour ?? Color4.White; } + set { flashColour = value; } + } + + private Color4? iconColour; + /// + /// The icon colour. This does not affect . + /// + public Color4 IconColour + { + get { return iconColour ?? Color4.White; } + set + { + iconColour = value; + icon.Colour = value; + } + } + + private Color4? iconHoverColour; + /// + /// The icon colour while the is hovered. + /// + public Color4 IconHoverColour + { + get { return iconHoverColour ?? IconColour; } + set { iconHoverColour = value; } + } + + private Color4? hoverColour; + /// + /// The background colour of the while it is hovered. + /// + public Color4 HoverColour + { + get { return hoverColour ?? Color4.White; } + set + { + hoverColour = value; + hover.Colour = value; + } + } + + /// + /// The icon. + /// + public FontAwesome Icon + { + get { return icon.Icon; } + set { icon.Icon = value; } + } + + /// + /// The icon scale. This does not affect . + /// + public Vector2 IconScale + { + get { return icon.Scale; } + set { icon.Scale = value; } + } + + /// + /// The size of the while it is not being pressed. + /// + public Vector2 ButtonSize + { + get { return content.Size; } + set { content.Size = value; } + } + + private readonly Container content; + private readonly SpriteIcon icon; + private readonly Box hover; + + public IconButton() + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + content = new Container + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(BUTTON_SIZE), + CornerRadius = 5, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.04f), + Type = EdgeEffectType.Shadow, + Radius = 5, + }, + Children = new Drawable[] + { + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + icon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(18), + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (hoverColour == null) + HoverColour = colours.Yellow.Opacity(0.6f); + + if (flashColour == null) + FlashColour = colours.Yellow; + + Enabled.ValueChanged += enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint); + } + + protected override bool OnHover(InputState state) + { + hover.FadeIn(500, Easing.OutQuint); + icon.FadeColour(IconHoverColour, 500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + hover.FadeOut(500, Easing.OutQuint); + icon.FadeColour(IconColour, 500, Easing.OutQuint); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + hover.FlashColour(FlashColour, 800, Easing.OutQuint); + return base.OnClick(state); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + content.ScaleTo(0.75f, 2000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + content.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 366c063a0c..3cb2446acc 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -1,118 +1,118 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Caching; -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; - -namespace osu.Game.Graphics.UserInterface -{ - public class LineGraph : Container - { - /// - /// Manually set the max value, otherwise will be used. - /// - public float? MaxValue { get; set; } - - /// - /// Manually set the min value, otherwise will be used. - /// - public float? MinValue { get; set; } - - public float ActualMaxValue { get; private set; } = float.NaN; - public float ActualMinValue { get; private set; } = float.NaN; - - private const double transform_duration = 1500; - - /// - /// Hold an empty area if values are less. - /// - public int DefaultValueCount; - - private readonly Container maskingContainer; - private readonly Path path; - - private float[] values; - - /// - /// A list of floats decides position of each line node. - /// - public IEnumerable Values - { - get { return values; } - set - { - values = value.ToArray(); - - float max = values.Max(), min = values.Min(); - if (MaxValue > max) max = MaxValue.Value; - if (MinValue < min) min = MinValue.Value; - - ActualMaxValue = max; - ActualMinValue = min; - - pathCached.Invalidate(); - - maskingContainer.Width = 0; - maskingContainer.ResizeWidthTo(1, transform_duration, Easing.OutQuint); - } - } - - public LineGraph() - { - Add(maskingContainer = new Container - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Child = path = new Path { RelativeSizeAxes = Axes.Both, PathWidth = 1 } - }); - } - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - pathCached.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - - private Cached pathCached = new Cached(); - - protected override void Update() - { - base.Update(); - if (!pathCached.IsValid) - { - applyPath(); - pathCached.Validate(); - } - } - - private void applyPath() - { - path.ClearVertices(); - if (values == null) return; - - int count = Math.Max(values.Length, DefaultValueCount); - - for (int i = 0; i < values.Length; i++) - { - float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; - float y = GetYPosition(values[i]) * DrawHeight - 1; - // the -1 is for inner offset in path (actually -PathWidth) - path.AddVertex(new Vector2(x, y)); - } - } - - protected float GetYPosition(float value) - { - if (ActualMaxValue == ActualMinValue) return 0; - return (ActualMaxValue - value) / (ActualMaxValue - ActualMinValue); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Caching; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; + +namespace osu.Game.Graphics.UserInterface +{ + public class LineGraph : Container + { + /// + /// Manually set the max value, otherwise will be used. + /// + public float? MaxValue { get; set; } + + /// + /// Manually set the min value, otherwise will be used. + /// + public float? MinValue { get; set; } + + public float ActualMaxValue { get; private set; } = float.NaN; + public float ActualMinValue { get; private set; } = float.NaN; + + private const double transform_duration = 1500; + + /// + /// Hold an empty area if values are less. + /// + public int DefaultValueCount; + + private readonly Container maskingContainer; + private readonly Path path; + + private float[] values; + + /// + /// A list of floats decides position of each line node. + /// + public IEnumerable Values + { + get { return values; } + set + { + values = value.ToArray(); + + float max = values.Max(), min = values.Min(); + if (MaxValue > max) max = MaxValue.Value; + if (MinValue < min) min = MinValue.Value; + + ActualMaxValue = max; + ActualMinValue = min; + + pathCached.Invalidate(); + + maskingContainer.Width = 0; + maskingContainer.ResizeWidthTo(1, transform_duration, Easing.OutQuint); + } + } + + public LineGraph() + { + Add(maskingContainer = new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Child = path = new Path { RelativeSizeAxes = Axes.Both, PathWidth = 1 } + }); + } + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.DrawSize) > 0) + pathCached.Invalidate(); + + return base.Invalidate(invalidation, source, shallPropagate); + } + + private Cached pathCached = new Cached(); + + protected override void Update() + { + base.Update(); + if (!pathCached.IsValid) + { + applyPath(); + pathCached.Validate(); + } + } + + private void applyPath() + { + path.ClearVertices(); + if (values == null) return; + + int count = Math.Max(values.Length, DefaultValueCount); + + for (int i = 0; i < values.Length; i++) + { + float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; + float y = GetYPosition(values[i]) * DrawHeight - 1; + // the -1 is for inner offset in path (actually -PathWidth) + path.AddVertex(new Vector2(x, y)); + } + } + + protected float GetYPosition(float value) + { + if (ActualMaxValue == ActualMinValue) return 0; + return (ActualMaxValue - value) / (ActualMaxValue - ActualMinValue); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs index 54f818e307..5ea6bce432 100644 --- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Graphics.UserInterface -{ - public class LoadingAnimation : VisibilityContainer - { - private readonly SpriteIcon spinner; - - public LoadingAnimation() - { - Size = new Vector2(20); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Children = new Drawable[] - { - spinner = new SpriteIcon - { - Size = new Vector2(20), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.fa_spinner - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - spinner.Spin(2000, RotationDirection.Clockwise); - } - - private const float transition_duration = 500; - - protected override void PopIn() => this.FadeIn(transition_duration * 5, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class LoadingAnimation : VisibilityContainer + { + private readonly SpriteIcon spinner; + + public LoadingAnimation() + { + Size = new Vector2(20); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Children = new Drawable[] + { + spinner = new SpriteIcon + { + Size = new Vector2(20), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_spinner + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + spinner.Spin(2000, RotationDirection.Clockwise); + } + + private const float transition_duration = 500; + + protected override void PopIn() => this.FadeIn(transition_duration * 5, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint); + } +} diff --git a/osu.Game/Graphics/UserInterface/MenuItemType.cs b/osu.Game/Graphics/UserInterface/MenuItemType.cs index 21c743ecd3..42bade1439 100644 --- a/osu.Game/Graphics/UserInterface/MenuItemType.cs +++ b/osu.Game/Graphics/UserInterface/MenuItemType.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Graphics.UserInterface -{ - public enum MenuItemType - { - Standard, - Highlighted, - Destructive, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Graphics.UserInterface +{ + public enum MenuItemType + { + Standard, + Highlighted, + Destructive, + } +} diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index ed2be25feb..84b7fd9dae 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -1,142 +1,142 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; - -namespace osu.Game.Graphics.UserInterface -{ - public class Nub : CircularContainer, IHasCurrentValue, IHasAccentColour - { - public const float COLLAPSED_SIZE = 20; - public const float EXPANDED_SIZE = 40; - - private const float border_width = 3; - - public Nub() - { - Box fill; - - Size = new Vector2(COLLAPSED_SIZE, 12); - - BorderColour = Color4.White; - BorderThickness = border_width; - - Masking = true; - - Children = new[] - { - fill = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - }; - - Current.ValueChanged += newValue => - { - if (newValue) - fill.FadeIn(200, Easing.OutQuint); - else - fill.FadeTo(0.01f, 200, Easing.OutQuint); //todo: remove once we figure why containers aren't drawing at all times - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = colours.Pink; - GlowingAccentColour = colours.PinkLighter; - GlowColour = colours.PinkDarker; - - EdgeEffect = new EdgeEffectParameters - { - Colour = GlowColour, - Type = EdgeEffectType.Glow, - Radius = 10, - Roundness = 8, - }; - } - - protected override void LoadComplete() - { - FadeEdgeEffectTo(0); - } - - private bool glowing; - public bool Glowing - { - get { return glowing; } - set - { - glowing = value; - - if (value) - { - this.FadeColour(GlowingAccentColour, 500, Easing.OutQuint); - FadeEdgeEffectTo(1, 500, Easing.OutQuint); - } - else - { - FadeEdgeEffectTo(0, 500); - this.FadeColour(AccentColour, 500); - } - } - } - - public bool Expanded - { - set - { - this.ResizeTo(new Vector2(value ? EXPANDED_SIZE : COLLAPSED_SIZE, 12), 500, Easing.OutQuint); - } - } - - public Bindable Current { get; } = new Bindable(); - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - if (!Glowing) - Colour = value; - } - } - - private Color4 glowingAccentColour; - public Color4 GlowingAccentColour - { - get { return glowingAccentColour; } - set - { - glowingAccentColour = value; - if (Glowing) - Colour = value; - } - } - - private Color4 glowColour; - public Color4 GlowColour - { - get { return glowColour; } - set - { - glowColour = value; - - var effect = EdgeEffect; - effect.Colour = value; - EdgeEffect = effect; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterface +{ + public class Nub : CircularContainer, IHasCurrentValue, IHasAccentColour + { + public const float COLLAPSED_SIZE = 20; + public const float EXPANDED_SIZE = 40; + + private const float border_width = 3; + + public Nub() + { + Box fill; + + Size = new Vector2(COLLAPSED_SIZE, 12); + + BorderColour = Color4.White; + BorderThickness = border_width; + + Masking = true; + + Children = new[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }; + + Current.ValueChanged += newValue => + { + if (newValue) + fill.FadeIn(200, Easing.OutQuint); + else + fill.FadeTo(0.01f, 200, Easing.OutQuint); //todo: remove once we figure why containers aren't drawing at all times + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Pink; + GlowingAccentColour = colours.PinkLighter; + GlowColour = colours.PinkDarker; + + EdgeEffect = new EdgeEffectParameters + { + Colour = GlowColour, + Type = EdgeEffectType.Glow, + Radius = 10, + Roundness = 8, + }; + } + + protected override void LoadComplete() + { + FadeEdgeEffectTo(0); + } + + private bool glowing; + public bool Glowing + { + get { return glowing; } + set + { + glowing = value; + + if (value) + { + this.FadeColour(GlowingAccentColour, 500, Easing.OutQuint); + FadeEdgeEffectTo(1, 500, Easing.OutQuint); + } + else + { + FadeEdgeEffectTo(0, 500); + this.FadeColour(AccentColour, 500); + } + } + } + + public bool Expanded + { + set + { + this.ResizeTo(new Vector2(value ? EXPANDED_SIZE : COLLAPSED_SIZE, 12), 500, Easing.OutQuint); + } + } + + public Bindable Current { get; } = new Bindable(); + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + if (!Glowing) + Colour = value; + } + } + + private Color4 glowingAccentColour; + public Color4 GlowingAccentColour + { + get { return glowingAccentColour; } + set + { + glowingAccentColour = value; + if (Glowing) + Colour = value; + } + } + + private Color4 glowColour; + public Color4 GlowColour + { + get { return glowColour; } + set + { + glowColour = value; + + var effect = EdgeEffect; + effect.Colour = value; + EdgeEffect = effect; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index be487bc062..bf3805a44d 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.UserInterface; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// A button with added default sound effects. - /// - public class OsuButton : Button - { - public OsuButton() - { - Add(new HoverClickSounds(HoverSampleSet.Loud)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A button with added default sound effects. + /// + public class OsuButton : Button + { + public OsuButton() + { + Add(new HoverClickSounds(HoverSampleSet.Loud)); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index f06313c261..ea337d5f42 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -1,119 +1,119 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuCheckbox : Checkbox - { - private Bindable bindable; - - public Bindable Bindable - { - set - { - bindable = value; - Current.BindTo(bindable); - } - } - - public Color4 CheckedColor { get; set; } = Color4.Cyan; - public Color4 UncheckedColor { get; set; } = Color4.White; - public int FadeDuration { get; set; } - - public string LabelText - { - get { return labelSpriteText?.Text; } - set - { - if (labelSpriteText != null) - labelSpriteText.Text = value; - } - } - - public MarginPadding LabelPadding - { - get { return labelSpriteText?.Padding ?? new MarginPadding(); } - set - { - if (labelSpriteText != null) - labelSpriteText.Padding = value; - } - } - - protected readonly Nub Nub; - - private readonly SpriteText labelSpriteText; - private SampleChannel sampleChecked; - private SampleChannel sampleUnchecked; - - public OsuCheckbox() - { - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - - Children = new Drawable[] - { - labelSpriteText = new OsuSpriteText(), - Nub = new Nub - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 5 }, - }, - new HoverClickSounds() - }; - - Nub.Current.BindTo(Current); - - Current.DisabledChanged += disabled => - { - Alpha = disabled ? 0.3f : 1; - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.ValueChanged += newValue => - { - if (newValue) - sampleChecked?.Play(); - else - sampleUnchecked?.Play(); - }; - } - - protected override bool OnHover(InputState state) - { - Nub.Glowing = true; - Nub.Expanded = true; - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - Nub.Glowing = false; - Nub.Expanded = false; - base.OnHoverLost(state); - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleChecked = audio.Sample.Get(@"UI/check-on"); - sampleUnchecked = audio.Sample.Get(@"UI/check-off"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuCheckbox : Checkbox + { + private Bindable bindable; + + public Bindable Bindable + { + set + { + bindable = value; + Current.BindTo(bindable); + } + } + + public Color4 CheckedColor { get; set; } = Color4.Cyan; + public Color4 UncheckedColor { get; set; } = Color4.White; + public int FadeDuration { get; set; } + + public string LabelText + { + get { return labelSpriteText?.Text; } + set + { + if (labelSpriteText != null) + labelSpriteText.Text = value; + } + } + + public MarginPadding LabelPadding + { + get { return labelSpriteText?.Padding ?? new MarginPadding(); } + set + { + if (labelSpriteText != null) + labelSpriteText.Padding = value; + } + } + + protected readonly Nub Nub; + + private readonly SpriteText labelSpriteText; + private SampleChannel sampleChecked; + private SampleChannel sampleUnchecked; + + public OsuCheckbox() + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + labelSpriteText = new OsuSpriteText(), + Nub = new Nub + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 5 }, + }, + new HoverClickSounds() + }; + + Nub.Current.BindTo(Current); + + Current.DisabledChanged += disabled => + { + Alpha = disabled ? 0.3f : 1; + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.ValueChanged += newValue => + { + if (newValue) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + }; + } + + protected override bool OnHover(InputState state) + { + Nub.Glowing = true; + Nub.Expanded = true; + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Nub.Glowing = false; + Nub.Expanded = false; + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleChecked = audio.Sample.Get(@"UI/check-on"); + sampleUnchecked = audio.Sample.Get(@"UI/check-off"); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 5335f20bf9..9b697f291f 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuContextMenu : OsuMenu - { - private const int fade_duration = 250; - - public OsuContextMenu() - : base(Direction.Vertical) - { - MaskingContainer.CornerRadius = 5; - MaskingContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.1f), - Radius = 4, - }; - - ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.ContextMenuGray; - } - - protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint); - protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuContextMenu : OsuMenu + { + private const int fade_duration = 250; + + public OsuContextMenu() + : base(Direction.Vertical) + { + MaskingContainer.CornerRadius = 5; + MaskingContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.1f), + Radius = 4, + }; + + ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.ContextMenuGray; + } + + protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint); + protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint); + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index b230aa4260..830bde9dac 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -1,255 +1,255 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; -using OpenTK; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuDropdown : Dropdown, IHasAccentColour - { - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - updateAccentColour(); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (accentColour == default(Color4)) - accentColour = colours.PinkDarker; - updateAccentColour(); - } - - private void updateAccentColour() - { - var header = Header as IHasAccentColour; - if (header != null) header.AccentColour = accentColour; - - var menu = Menu as IHasAccentColour; - if (menu != null) menu.AccentColour = accentColour; - } - - protected override DropdownHeader CreateHeader() => new OsuDropdownHeader(); - - protected override DropdownMenu CreateMenu() => new OsuDropdownMenu(); - - #region OsuDropdownMenu - protected class OsuDropdownMenu : DropdownMenu, IHasAccentColour - { - // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring - public OsuDropdownMenu() - { - CornerRadius = 4; - BackgroundColour = Color4.Black.Opacity(0.5f); - - // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring - ItemsContainer.Padding = new MarginPadding(5); - } - - // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring - protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); - protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); - - // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring - protected override void UpdateSize(Vector2 newSize) - { - if (Direction == Direction.Vertical) - { - Width = newSize.X; - this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint); - } - else - { - Height = newSize.Y; - this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint); - } - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - foreach (var c in Children.OfType()) - c.AccentColour = value; - } - } - - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour }; - - #region DrawableOsuDropdownMenuItem - public class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour - { - private Color4? accentColour; - public Color4 AccentColour - { - get { return accentColour ?? nonAccentSelectedColour; } - set - { - accentColour = value; - updateColours(); - } - } - - private void updateColours() - { - BackgroundColourHover = accentColour ?? nonAccentHoverColour; - BackgroundColourSelected = accentColour ?? nonAccentSelectedColour; - UpdateBackgroundColour(); - UpdateForegroundColour(); - } - - private Color4 nonAccentHoverColour; - private Color4 nonAccentSelectedColour; - - public DrawableOsuDropdownMenuItem(MenuItem item) - : base(item) - { - Foreground.Padding = new MarginPadding(2); - - Masking = true; - CornerRadius = 6; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = Color4.Transparent; - - nonAccentHoverColour = colours.PinkDarker; - nonAccentSelectedColour = Color4.Black.Opacity(0.5f); - updateColours(); - - AddInternal(new HoverClickSounds(HoverSampleSet.Soft)); - } - - protected override void UpdateForegroundColour() - { - base.UpdateForegroundColour(); - - var content = Foreground.Children.FirstOrDefault() as Content; - if (content != null) content.Chevron.Alpha = IsHovered ? 1 : 0; - } - - protected override Drawable CreateContent() => new Content(); - - protected new class Content : FillFlowContainer, IHasText - { - public string Text - { - get { return Label.Text; } - set { Label.Text = value; } - } - - public readonly OsuSpriteText Label; - public readonly SpriteIcon Chevron; - - public Content() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Horizontal; - - Children = new Drawable[] - { - Chevron = new SpriteIcon - { - AlwaysPresent = true, - Icon = FontAwesome.fa_chevron_right, - Colour = Color4.Black, - Alpha = 0.5f, - Size = new Vector2(8), - Margin = new MarginPadding { Left = 3, Right = 3 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - Label = new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - }; - } - } - } - #endregion - } - #endregion - - public class OsuDropdownHeader : DropdownHeader, IHasAccentColour - { - protected readonly SpriteText Text; - protected override string Label - { - get { return Text.Text; } - set { Text.Text = value; } - } - - protected readonly SpriteIcon Icon; - - private Color4 accentColour; - public virtual Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - BackgroundColourHover = accentColour; - } - } - - public OsuDropdownHeader() - { - Foreground.Padding = new MarginPadding(4); - - AutoSizeAxes = Axes.None; - Margin = new MarginPadding { Bottom = 4 }; - CornerRadius = 4; - Height = 40; - - Foreground.Children = new Drawable[] - { - Text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - Icon = new SpriteIcon - { - Icon = FontAwesome.fa_chevron_down, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 4 }, - Size = new Vector2(20), - }, - }; - - AddInternal(new HoverClickSounds()); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = Color4.Black.Opacity(0.5f); - BackgroundColourHover = colours.PinkDarker; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuDropdown : Dropdown, IHasAccentColour + { + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + updateAccentColour(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == default(Color4)) + accentColour = colours.PinkDarker; + updateAccentColour(); + } + + private void updateAccentColour() + { + var header = Header as IHasAccentColour; + if (header != null) header.AccentColour = accentColour; + + var menu = Menu as IHasAccentColour; + if (menu != null) menu.AccentColour = accentColour; + } + + protected override DropdownHeader CreateHeader() => new OsuDropdownHeader(); + + protected override DropdownMenu CreateMenu() => new OsuDropdownMenu(); + + #region OsuDropdownMenu + protected class OsuDropdownMenu : DropdownMenu, IHasAccentColour + { + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring + public OsuDropdownMenu() + { + CornerRadius = 4; + BackgroundColour = Color4.Black.Opacity(0.5f); + + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring + ItemsContainer.Padding = new MarginPadding(5); + } + + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring + protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); + protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); + + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring + protected override void UpdateSize(Vector2 newSize) + { + if (Direction == Direction.Vertical) + { + Width = newSize.X; + this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint); + } + else + { + Height = newSize.Y; + this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint); + } + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + foreach (var c in Children.OfType()) + c.AccentColour = value; + } + } + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour }; + + #region DrawableOsuDropdownMenuItem + public class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour + { + private Color4? accentColour; + public Color4 AccentColour + { + get { return accentColour ?? nonAccentSelectedColour; } + set + { + accentColour = value; + updateColours(); + } + } + + private void updateColours() + { + BackgroundColourHover = accentColour ?? nonAccentHoverColour; + BackgroundColourSelected = accentColour ?? nonAccentSelectedColour; + UpdateBackgroundColour(); + UpdateForegroundColour(); + } + + private Color4 nonAccentHoverColour; + private Color4 nonAccentSelectedColour; + + public DrawableOsuDropdownMenuItem(MenuItem item) + : base(item) + { + Foreground.Padding = new MarginPadding(2); + + Masking = true; + CornerRadius = 6; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = Color4.Transparent; + + nonAccentHoverColour = colours.PinkDarker; + nonAccentSelectedColour = Color4.Black.Opacity(0.5f); + updateColours(); + + AddInternal(new HoverClickSounds(HoverSampleSet.Soft)); + } + + protected override void UpdateForegroundColour() + { + base.UpdateForegroundColour(); + + var content = Foreground.Children.FirstOrDefault() as Content; + if (content != null) content.Chevron.Alpha = IsHovered ? 1 : 0; + } + + protected override Drawable CreateContent() => new Content(); + + protected new class Content : FillFlowContainer, IHasText + { + public string Text + { + get { return Label.Text; } + set { Label.Text = value; } + } + + public readonly OsuSpriteText Label; + public readonly SpriteIcon Chevron; + + public Content() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Horizontal; + + Children = new Drawable[] + { + Chevron = new SpriteIcon + { + AlwaysPresent = true, + Icon = FontAwesome.fa_chevron_right, + Colour = Color4.Black, + Alpha = 0.5f, + Size = new Vector2(8), + Margin = new MarginPadding { Left = 3, Right = 3 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }, + Label = new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }, + }; + } + } + } + #endregion + } + #endregion + + public class OsuDropdownHeader : DropdownHeader, IHasAccentColour + { + protected readonly SpriteText Text; + protected override string Label + { + get { return Text.Text; } + set { Text.Text = value; } + } + + protected readonly SpriteIcon Icon; + + private Color4 accentColour; + public virtual Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + BackgroundColourHover = accentColour; + } + } + + public OsuDropdownHeader() + { + Foreground.Padding = new MarginPadding(4); + + AutoSizeAxes = Axes.None; + Margin = new MarginPadding { Bottom = 4 }; + CornerRadius = 4; + Height = 40; + + Foreground.Children = new Drawable[] + { + Text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + Icon = new SpriteIcon + { + Icon = FontAwesome.fa_chevron_down, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 4 }, + Size = new Vector2(20), + }, + }; + + AddInternal(new HoverClickSounds()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = Color4.Black.Opacity(0.5f); + BackgroundColourHover = colours.PinkDarker; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs index b0711a6667..502f468ec9 100644 --- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs @@ -1,32 +1,32 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.ComponentModel; -using System.Reflection; -using System.Collections.Generic; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuEnumDropdown : OsuDropdown - { - public OsuEnumDropdown() - { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument"); - - List> items = new List>(); - foreach (var val in (T[])Enum.GetValues(typeof(T))) - { - var field = typeof(T).GetField(Enum.GetName(typeof(T), val)); - items.Add( - new KeyValuePair( - field.GetCustomAttribute()?.Description ?? Enum.GetName(typeof(T), val), - val - ) - ); - } - Items = items; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.ComponentModel; +using System.Reflection; +using System.Collections.Generic; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuEnumDropdown : OsuDropdown + { + public OsuEnumDropdown() + { + if (!typeof(T).IsEnum) + throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument"); + + List> items = new List>(); + foreach (var val in (T[])Enum.GetValues(typeof(T))) + { + var field = typeof(T).GetField(Enum.GetName(typeof(T), val)); + items.Add( + new KeyValuePair( + field.GetCustomAttribute()?.Description ?? Enum.GetName(typeof(T), val), + val + ) + ); + } + Items = items; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 968c0eed98..41aeb534f0 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -1,170 +1,170 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; -using OpenTK; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuMenu : Menu - { - public OsuMenu(Direction direction, bool topLevelMenu = false) - : base(direction, topLevelMenu) - { - BackgroundColour = Color4.Black.Opacity(0.5f); - - MaskingContainer.CornerRadius = 4; - ItemsContainer.Padding = new MarginPadding(5); - } - - protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); - protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); - - protected override void UpdateSize(Vector2 newSize) - { - if (Direction == Direction.Vertical) - { - Width = newSize.X; - this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint); - } - else - { - Height = newSize.Y; - this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint); - } - } - - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item); - - protected override Menu CreateSubMenu() => new OsuMenu(Direction.Vertical) - { - Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight - }; - - protected class DrawableOsuMenuItem : DrawableMenuItem - { - private const int margin_horizontal = 17; - private const int text_size = 17; - private const int transition_length = 80; - public const int MARGIN_VERTICAL = 4; - - private SampleChannel sampleClick; - private SampleChannel sampleHover; - - private TextContainer text; - - public DrawableOsuMenuItem(MenuItem item) - : base(item) - { - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleHover = audio.Sample.Get(@"UI/generic-hover"); - sampleClick = audio.Sample.Get(@"UI/generic-select"); - - BackgroundColour = Color4.Transparent; - BackgroundColourHover = OsuColour.FromHex(@"172023"); - - updateTextColour(); - } - - private void updateTextColour() - { - switch ((Item as OsuMenuItem)?.Type) - { - default: - case MenuItemType.Standard: - text.Colour = Color4.White; - break; - case MenuItemType.Destructive: - text.Colour = Color4.Red; - break; - case MenuItemType.Highlighted: - text.Colour = OsuColour.FromHex(@"ffcc22"); - break; - } - } - - protected override bool OnHover(InputState state) - { - sampleHover.Play(); - text.BoldText.FadeIn(transition_length, Easing.OutQuint); - text.NormalText.FadeOut(transition_length, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - text.BoldText.FadeOut(transition_length, Easing.OutQuint); - text.NormalText.FadeIn(transition_length, Easing.OutQuint); - base.OnHoverLost(state); - } - - protected override bool OnClick(InputState state) - { - sampleClick.Play(); - return base.OnClick(state); - } - - protected sealed override Drawable CreateContent() => text = CreateTextContainer(); - protected virtual TextContainer CreateTextContainer() => new TextContainer(); - - protected class TextContainer : Container, IHasText - { - public string Text - { - get { return NormalText.Text; } - set - { - NormalText.Text = value; - BoldText.Text = value; - } - } - - public readonly SpriteText NormalText; - public readonly SpriteText BoldText; - - public TextContainer() - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - NormalText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = text_size, - Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, - }, - BoldText = new OsuSpriteText - { - AlwaysPresent = true, - Alpha = 0, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = text_size, - Font = @"Exo2.0-Bold", - Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, - } - }; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuMenu : Menu + { + public OsuMenu(Direction direction, bool topLevelMenu = false) + : base(direction, topLevelMenu) + { + BackgroundColour = Color4.Black.Opacity(0.5f); + + MaskingContainer.CornerRadius = 4; + ItemsContainer.Padding = new MarginPadding(5); + } + + protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); + protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); + + protected override void UpdateSize(Vector2 newSize) + { + if (Direction == Direction.Vertical) + { + Width = newSize.X; + this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint); + } + else + { + Height = newSize.Y; + this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint); + } + } + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item); + + protected override Menu CreateSubMenu() => new OsuMenu(Direction.Vertical) + { + Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight + }; + + protected class DrawableOsuMenuItem : DrawableMenuItem + { + private const int margin_horizontal = 17; + private const int text_size = 17; + private const int transition_length = 80; + public const int MARGIN_VERTICAL = 4; + + private SampleChannel sampleClick; + private SampleChannel sampleHover; + + private TextContainer text; + + public DrawableOsuMenuItem(MenuItem item) + : base(item) + { + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleHover = audio.Sample.Get(@"UI/generic-hover"); + sampleClick = audio.Sample.Get(@"UI/generic-select"); + + BackgroundColour = Color4.Transparent; + BackgroundColourHover = OsuColour.FromHex(@"172023"); + + updateTextColour(); + } + + private void updateTextColour() + { + switch ((Item as OsuMenuItem)?.Type) + { + default: + case MenuItemType.Standard: + text.Colour = Color4.White; + break; + case MenuItemType.Destructive: + text.Colour = Color4.Red; + break; + case MenuItemType.Highlighted: + text.Colour = OsuColour.FromHex(@"ffcc22"); + break; + } + } + + protected override bool OnHover(InputState state) + { + sampleHover.Play(); + text.BoldText.FadeIn(transition_length, Easing.OutQuint); + text.NormalText.FadeOut(transition_length, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + text.BoldText.FadeOut(transition_length, Easing.OutQuint); + text.NormalText.FadeIn(transition_length, Easing.OutQuint); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + sampleClick.Play(); + return base.OnClick(state); + } + + protected sealed override Drawable CreateContent() => text = CreateTextContainer(); + protected virtual TextContainer CreateTextContainer() => new TextContainer(); + + protected class TextContainer : Container, IHasText + { + public string Text + { + get { return NormalText.Text; } + set + { + NormalText.Text = value; + BoldText.Text = value; + } + } + + public readonly SpriteText NormalText; + public readonly SpriteText BoldText; + + public TextContainer() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + NormalText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = text_size, + Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, + }, + BoldText = new OsuSpriteText + { + AlwaysPresent = true, + Alpha = 0, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = text_size, + Font = @"Exo2.0-Bold", + Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, + } + }; + } + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs index 9ccbea2479..02452523a8 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics.UserInterface; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuMenuItem : MenuItem - { - public readonly MenuItemType Type; - - public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : base(text) - { - Type = type; - } - - public OsuMenuItem(string text, MenuItemType type, Action action) - : base(text, action) - { - Type = type; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuMenuItem : MenuItem + { + public readonly MenuItemType Type; + + public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : base(text) + { + Type = type; + } + + public OsuMenuItem(string text, MenuItemType type, Action action) + : base(text, action) + { + Type = type; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index 8c519e5371..d34d2b2a7c 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -1,119 +1,119 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Platform; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuPasswordTextBox : OsuTextBox - { - protected override Drawable GetDrawableCharacter(char c) => new PasswordMaskChar(CalculatedTextSize); - - public override bool AllowClipboardExport => false; - - private readonly CapsWarning warning; - - private GameHost host; - - public OsuPasswordTextBox() - { - Add(warning = new CapsWarning - { - Size = new Vector2(20), - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Margin = new MarginPadding { Right = 10 }, - Alpha = 0, - }); - } - - [BackgroundDependencyLoader] - private void load(GameHost host) - { - this.host = host; - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Key == Key.CapsLock) - updateCapsWarning(host.CapsLockEnabled); - return base.OnKeyDown(state, args); - } - - protected override void OnFocus(InputState state) - { - updateCapsWarning(host.CapsLockEnabled); - base.OnFocus(state); - } - - protected override void OnFocusLost(InputState state) - { - updateCapsWarning(false); - base.OnFocusLost(state); - } - - private void updateCapsWarning(bool visible) => warning.FadeTo(visible ? 1 : 0, 250, Easing.OutQuint); - - public class PasswordMaskChar : Container - { - private readonly CircularContainer circle; - - public PasswordMaskChar(float size) - { - Size = new Vector2(size / 2, size); - Children = new[] - { - circle = new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.8f, 0), - Children = new[] - { - new Box - { - Colour = Color4.White, - RelativeSizeAxes = Axes.Both, - } - }, - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - circle.FadeIn(500, Easing.OutQuint); - circle.ResizeTo(new Vector2(0.8f), 500, Easing.OutQuint); - } - } - - private class CapsWarning : SpriteIcon, IHasTooltip - { - public string TooltipText => @"Caps lock is active"; - - public CapsWarning() - { - Icon = FontAwesome.fa_warning; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - Colour = colour.YellowLight; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Platform; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuPasswordTextBox : OsuTextBox + { + protected override Drawable GetDrawableCharacter(char c) => new PasswordMaskChar(CalculatedTextSize); + + public override bool AllowClipboardExport => false; + + private readonly CapsWarning warning; + + private GameHost host; + + public OsuPasswordTextBox() + { + Add(warning = new CapsWarning + { + Size = new Vector2(20), + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, + Alpha = 0, + }); + } + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + this.host = host; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Key == Key.CapsLock) + updateCapsWarning(host.CapsLockEnabled); + return base.OnKeyDown(state, args); + } + + protected override void OnFocus(InputState state) + { + updateCapsWarning(host.CapsLockEnabled); + base.OnFocus(state); + } + + protected override void OnFocusLost(InputState state) + { + updateCapsWarning(false); + base.OnFocusLost(state); + } + + private void updateCapsWarning(bool visible) => warning.FadeTo(visible ? 1 : 0, 250, Easing.OutQuint); + + public class PasswordMaskChar : Container + { + private readonly CircularContainer circle; + + public PasswordMaskChar(float size) + { + Size = new Vector2(size / 2, size); + Children = new[] + { + circle = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f, 0), + Children = new[] + { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + } + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + circle.FadeIn(500, Easing.OutQuint); + circle.ResizeTo(new Vector2(0.8f), 500, Easing.OutQuint); + } + } + + private class CapsWarning : SpriteIcon, IHasTooltip + { + public string TooltipText => @"Caps lock is active"; + + public CapsWarning() + { + Icon = FontAwesome.fa_warning; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + Colour = colour.YellowLight; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 8f375d9885..7604009aab 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -1,218 +1,218 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Globalization; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuSliderBar : SliderBar, IHasTooltip, IHasAccentColour - where T : struct, IEquatable, IComparable, IConvertible - { - /// - /// Maximum number of decimal digits to be displayed in the tooltip. - /// - private const int max_decimal_digits = 5; - - private SampleChannel sample; - private double lastSampleTime; - private T lastSampleValue; - - protected readonly Nub Nub; - private readonly Box leftBox; - private readonly Box rightBox; - - public virtual string TooltipText - { - get - { - var bindableDouble = CurrentNumber as BindableNumber; - var bindableFloat = CurrentNumber as BindableNumber; - var floatValue = bindableDouble?.Value ?? bindableFloat?.Value; - var floatPrecision = bindableDouble?.Precision ?? bindableFloat?.Precision; - - if (floatValue != null) - { - var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue; - var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue; - - if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1)) - return floatValue.Value.ToString("P0"); - - var decimalPrecision = normalise((decimal)floatPrecision, max_decimal_digits); - - // Find the number of significant digits (we could have less than 5 after normalize()) - var significantDigits = findPrecision(decimalPrecision); - - return floatValue.Value.ToString($"N{significantDigits}"); - } - - var bindableInt = CurrentNumber as BindableNumber; - if (bindableInt != null) - return bindableInt.Value.ToString("N0"); - - return Current.Value.ToString(CultureInfo.InvariantCulture); - } - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - leftBox.Colour = value; - rightBox.Colour = value; - } - } - - public OsuSliderBar() - { - Height = 12; - RangePadding = 20; - Children = new Drawable[] - { - leftBox = new Box - { - Height = 2, - EdgeSmoothness = new Vector2(0, 0.5f), - Position = new Vector2(2, 0), - RelativeSizeAxes = Axes.None, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - rightBox = new Box - { - Height = 2, - EdgeSmoothness = new Vector2(0, 0.5f), - Position = new Vector2(-2, 0), - RelativeSizeAxes = Axes.None, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Alpha = 0.5f, - }, - Nub = new Nub - { - Origin = Anchor.TopCentre, - Expanded = true, - }, - new HoverClickSounds() - }; - - Current.DisabledChanged += disabled => - { - Alpha = disabled ? 0.3f : 1; - }; - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { - sample = audio.Sample.Get(@"UI/sliderbar-notch"); - AccentColour = colours.Pink; - } - - protected override bool OnHover(InputState state) - { - Nub.Glowing = true; - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - Nub.Glowing = false; - base.OnHoverLost(state); - } - - protected override void OnUserChange() - { - base.OnUserChange(); - playSample(); - } - - private void playSample() - { - if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) - return; - - if (Current.Value.Equals(lastSampleValue)) - return; - - lastSampleValue = Current.Value; - - lastSampleTime = Clock.CurrentTime; - sample.Frequency.Value = 1 + NormalizedValue * 0.2f; - - if (NormalizedValue == 0) - sample.Frequency.Value -= 0.4f; - else if (NormalizedValue == 1) - sample.Frequency.Value += 0.4f; - - sample.Play(); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - Nub.Current.Value = true; - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - Nub.Current.Value = false; - return base.OnMouseUp(state, args); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - leftBox.Scale = new Vector2(MathHelper.Clamp( - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - rightBox.Scale = new Vector2(MathHelper.Clamp( - DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - } - - protected override void UpdateValue(float value) - { - Nub.MoveToX(RangePadding + UsableWidth * value, 250, Easing.OutQuint); - } - - /// - /// Removes all non-significant digits, keeping at most a requested number of decimal digits. - /// - /// The decimal to normalize. - /// The maximum number of decimal digits to keep. The final result may have fewer decimal digits than this value. - /// The normalised decimal. - private decimal normalise(decimal d, int sd) - => decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); - - /// - /// Finds the number of digits after the decimal. - /// - /// The value to find the number of decimal digits for. - /// The number decimal digits. - private int findPrecision(decimal d) - { - int precision = 0; - while (d != Math.Round(d)) - { - d *= 10; - precision++; - } - - return precision; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Globalization; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuSliderBar : SliderBar, IHasTooltip, IHasAccentColour + where T : struct, IEquatable, IComparable, IConvertible + { + /// + /// Maximum number of decimal digits to be displayed in the tooltip. + /// + private const int max_decimal_digits = 5; + + private SampleChannel sample; + private double lastSampleTime; + private T lastSampleValue; + + protected readonly Nub Nub; + private readonly Box leftBox; + private readonly Box rightBox; + + public virtual string TooltipText + { + get + { + var bindableDouble = CurrentNumber as BindableNumber; + var bindableFloat = CurrentNumber as BindableNumber; + var floatValue = bindableDouble?.Value ?? bindableFloat?.Value; + var floatPrecision = bindableDouble?.Precision ?? bindableFloat?.Precision; + + if (floatValue != null) + { + var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue; + var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue; + + if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1)) + return floatValue.Value.ToString("P0"); + + var decimalPrecision = normalise((decimal)floatPrecision, max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + var significantDigits = findPrecision(decimalPrecision); + + return floatValue.Value.ToString($"N{significantDigits}"); + } + + var bindableInt = CurrentNumber as BindableNumber; + if (bindableInt != null) + return bindableInt.Value.ToString("N0"); + + return Current.Value.ToString(CultureInfo.InvariantCulture); + } + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + leftBox.Colour = value; + rightBox.Colour = value; + } + } + + public OsuSliderBar() + { + Height = 12; + RangePadding = 20; + Children = new Drawable[] + { + leftBox = new Box + { + Height = 2, + EdgeSmoothness = new Vector2(0, 0.5f), + Position = new Vector2(2, 0), + RelativeSizeAxes = Axes.None, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + rightBox = new Box + { + Height = 2, + EdgeSmoothness = new Vector2(0, 0.5f), + Position = new Vector2(-2, 0), + RelativeSizeAxes = Axes.None, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Alpha = 0.5f, + }, + Nub = new Nub + { + Origin = Anchor.TopCentre, + Expanded = true, + }, + new HoverClickSounds() + }; + + Current.DisabledChanged += disabled => + { + Alpha = disabled ? 0.3f : 1; + }; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + sample = audio.Sample.Get(@"UI/sliderbar-notch"); + AccentColour = colours.Pink; + } + + protected override bool OnHover(InputState state) + { + Nub.Glowing = true; + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Nub.Glowing = false; + base.OnHoverLost(state); + } + + protected override void OnUserChange() + { + base.OnUserChange(); + playSample(); + } + + private void playSample() + { + if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) + return; + + if (Current.Value.Equals(lastSampleValue)) + return; + + lastSampleValue = Current.Value; + + lastSampleTime = Clock.CurrentTime; + sample.Frequency.Value = 1 + NormalizedValue * 0.2f; + + if (NormalizedValue == 0) + sample.Frequency.Value -= 0.4f; + else if (NormalizedValue == 1) + sample.Frequency.Value += 0.4f; + + sample.Play(); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + Nub.Current.Value = true; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + Nub.Current.Value = false; + return base.OnMouseUp(state, args); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + leftBox.Scale = new Vector2(MathHelper.Clamp( + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); + rightBox.Scale = new Vector2(MathHelper.Clamp( + DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); + } + + protected override void UpdateValue(float value) + { + Nub.MoveToX(RangePadding + UsableWidth * value, 250, Easing.OutQuint); + } + + /// + /// Removes all non-significant digits, keeping at most a requested number of decimal digits. + /// + /// The decimal to normalize. + /// The maximum number of decimal digits to keep. The final result may have fewer decimal digits than this value. + /// The normalised decimal. + private decimal normalise(decimal d, int sd) + => decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); + + /// + /// Finds the number of digits after the decimal. + /// + /// The value to find the number of decimal digits for. + /// The number decimal digits. + private int findPrecision(decimal d) + { + int precision = 0; + while (d != Math.Round(d)) + { + d *= 10; + precision++; + } + + return precision; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 8b692f32bc..fc14a9c6ba 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -1,286 +1,286 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuTabControl : TabControl - { - private readonly Box strip; - - protected override Dropdown CreateDropdown() => new OsuTabDropdown(); - - protected override TabItem CreateTabItem(T value) => new OsuTabItem(value); - - protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; - protected virtual float StripHeight() => 1; - - private static bool isEnumType => typeof(T).IsEnum; - - public OsuTabControl() - { - TabContainer.Spacing = new Vector2(10f, 0f); - - Add(strip = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Height = StripHeight(), - Colour = Color4.White.Opacity(0), - }); - - if (isEnumType) - foreach (var val in (T[])Enum.GetValues(typeof(T))) - AddItem(val); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (accentColour == default(Color4)) - AccentColour = colours.Blue; - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - var dropdown = Dropdown as IHasAccentColour; - if (dropdown != null) - dropdown.AccentColour = value; - foreach (var i in TabContainer.Children.OfType()) - i.AccentColour = value; - } - } - - public Color4 StripColour - { - get => strip.Colour; - set => strip.Colour = value; - } - - protected override TabFillFlowContainer CreateTabFlow() => new OsuTabFillFlowContainer - { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.Both, - Depth = -1, - Masking = true - }; - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // dont bother calculating if the strip is invisible - if (strip.Colour.MaxAlpha > 0) - strip.Width = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); - } - - public class OsuTabItem : TabItem, IHasAccentColour - { - protected readonly SpriteText Text; - protected readonly Box Bar; - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - if (!Active) - Text.Colour = value; - } - } - - private const float transition_length = 500; - - private void fadeActive() - { - Bar.FadeIn(transition_length, Easing.OutQuint); - Text.FadeColour(Color4.White, transition_length, Easing.OutQuint); - } - - private void fadeInactive() - { - Bar.FadeOut(transition_length, Easing.OutQuint); - Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); - } - - protected override bool OnHover(InputState state) - { - if (!Active) - fadeActive(); - return true; - } - - protected override void OnHoverLost(InputState state) - { - if (!Active) - fadeInactive(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (accentColour == default(Color4)) - AccentColour = colours.Blue; - } - - public OsuTabItem(T value) : base(value) - { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - - Children = new Drawable[] - { - Text = new OsuSpriteText - { - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Text = (value as Enum)?.GetDescription() ?? value.ToString(), - TextSize = 14, - Font = @"Exo2.0-Bold", // Font should only turn bold when active? - }, - Bar = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Alpha = 0, - Colour = Color4.White, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - }, - new HoverClickSounds() - }; - } - - protected override void OnActivated() => fadeActive(); - - protected override void OnDeactivated() => fadeInactive(); - } - - // todo: this needs to go - private class OsuTabDropdown : OsuDropdown - { - public OsuTabDropdown() - { - RelativeSizeAxes = Axes.X; - } - - protected override DropdownMenu CreateMenu() => new OsuTabDropdownMenu(); - - protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }; - - private class OsuTabDropdownMenu : OsuDropdownMenu - { - public OsuTabDropdownMenu() - { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - - BackgroundColour = Color4.Black.Opacity(0.7f); - MaxHeight = 400; - } - - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item) { AccentColour = AccentColour }; - - private class DrawableOsuTabDropdownMenuItem : DrawableOsuDropdownMenuItem - { - public DrawableOsuTabDropdownMenuItem(MenuItem item) - : base(item) - { - ForegroundColourHover = Color4.Black; - } - } - } - - protected class OsuTabDropdownHeader : OsuDropdownHeader - { - public override Color4 AccentColour - { - get - { - return base.AccentColour; - } - - set - { - base.AccentColour = value; - Foreground.Colour = value; - } - } - - public OsuTabDropdownHeader() - { - RelativeSizeAxes = Axes.None; - AutoSizeAxes = Axes.X; - - BackgroundColour = Color4.Black.Opacity(0.5f); - - Background.Height = 0.5f; - Background.CornerRadius = 5; - Background.Masking = true; - - Foreground.RelativeSizeAxes = Axes.None; - Foreground.AutoSizeAxes = Axes.X; - Foreground.RelativeSizeAxes = Axes.Y; - Foreground.Margin = new MarginPadding(5); - - Foreground.Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.fa_ellipsis_h, - Size = new Vector2(14), - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - }; - - Padding = new MarginPadding { Left = 5, Right = 5 }; - } - - protected override bool OnHover(InputState state) - { - Foreground.Colour = BackgroundColour; - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - Foreground.Colour = BackgroundColourHover; - base.OnHoverLost(state); - } - } - } - - private class OsuTabFillFlowContainer : TabFillFlowContainer - { - protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuTabControl : TabControl + { + private readonly Box strip; + + protected override Dropdown CreateDropdown() => new OsuTabDropdown(); + + protected override TabItem CreateTabItem(T value) => new OsuTabItem(value); + + protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; + protected virtual float StripHeight() => 1; + + private static bool isEnumType => typeof(T).IsEnum; + + public OsuTabControl() + { + TabContainer.Spacing = new Vector2(10f, 0f); + + Add(strip = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = StripHeight(), + Colour = Color4.White.Opacity(0), + }); + + if (isEnumType) + foreach (var val in (T[])Enum.GetValues(typeof(T))) + AddItem(val); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == default(Color4)) + AccentColour = colours.Blue; + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + var dropdown = Dropdown as IHasAccentColour; + if (dropdown != null) + dropdown.AccentColour = value; + foreach (var i in TabContainer.Children.OfType()) + i.AccentColour = value; + } + } + + public Color4 StripColour + { + get => strip.Colour; + set => strip.Colour = value; + } + + protected override TabFillFlowContainer CreateTabFlow() => new OsuTabFillFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.Both, + Depth = -1, + Masking = true + }; + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // dont bother calculating if the strip is invisible + if (strip.Colour.MaxAlpha > 0) + strip.Width = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); + } + + public class OsuTabItem : TabItem, IHasAccentColour + { + protected readonly SpriteText Text; + protected readonly Box Bar; + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + if (!Active) + Text.Colour = value; + } + } + + private const float transition_length = 500; + + private void fadeActive() + { + Bar.FadeIn(transition_length, Easing.OutQuint); + Text.FadeColour(Color4.White, transition_length, Easing.OutQuint); + } + + private void fadeInactive() + { + Bar.FadeOut(transition_length, Easing.OutQuint); + Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); + } + + protected override bool OnHover(InputState state) + { + if (!Active) + fadeActive(); + return true; + } + + protected override void OnHoverLost(InputState state) + { + if (!Active) + fadeInactive(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == default(Color4)) + AccentColour = colours.Blue; + } + + public OsuTabItem(T value) : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + Text = new OsuSpriteText + { + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Text = (value as Enum)?.GetDescription() ?? value.ToString(), + TextSize = 14, + Font = @"Exo2.0-Bold", // Font should only turn bold when active? + }, + Bar = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = 0, + Colour = Color4.White, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + }, + new HoverClickSounds() + }; + } + + protected override void OnActivated() => fadeActive(); + + protected override void OnDeactivated() => fadeInactive(); + } + + // todo: this needs to go + private class OsuTabDropdown : OsuDropdown + { + public OsuTabDropdown() + { + RelativeSizeAxes = Axes.X; + } + + protected override DropdownMenu CreateMenu() => new OsuTabDropdownMenu(); + + protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }; + + private class OsuTabDropdownMenu : OsuDropdownMenu + { + public OsuTabDropdownMenu() + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + + BackgroundColour = Color4.Black.Opacity(0.7f); + MaxHeight = 400; + } + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item) { AccentColour = AccentColour }; + + private class DrawableOsuTabDropdownMenuItem : DrawableOsuDropdownMenuItem + { + public DrawableOsuTabDropdownMenuItem(MenuItem item) + : base(item) + { + ForegroundColourHover = Color4.Black; + } + } + } + + protected class OsuTabDropdownHeader : OsuDropdownHeader + { + public override Color4 AccentColour + { + get + { + return base.AccentColour; + } + + set + { + base.AccentColour = value; + Foreground.Colour = value; + } + } + + public OsuTabDropdownHeader() + { + RelativeSizeAxes = Axes.None; + AutoSizeAxes = Axes.X; + + BackgroundColour = Color4.Black.Opacity(0.5f); + + Background.Height = 0.5f; + Background.CornerRadius = 5; + Background.Masking = true; + + Foreground.RelativeSizeAxes = Axes.None; + Foreground.AutoSizeAxes = Axes.X; + Foreground.RelativeSizeAxes = Axes.Y; + Foreground.Margin = new MarginPadding(5); + + Foreground.Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_ellipsis_h, + Size = new Vector2(14), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + }; + + Padding = new MarginPadding { Left = 5, Right = 5 }; + } + + protected override bool OnHover(InputState state) + { + Foreground.Colour = BackgroundColour; + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Foreground.Colour = BackgroundColourHover; + base.OnHoverLost(state); + } + } + } + + private class OsuTabFillFlowContainer : TabFillFlowContainer + { + protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 3fd7760802..13740c935f 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -1,136 +1,136 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// A Checkbox styled to be placed in line with an - /// - public class OsuTabControlCheckbox : Checkbox - { - private readonly Box box; - private readonly SpriteText text; - private readonly SpriteIcon icon; - - private Color4? accentColour; - public Color4 AccentColour - { - get { return accentColour.GetValueOrDefault(); } - set - { - accentColour = value; - - if (Current) - { - text.Colour = AccentColour; - icon.Colour = AccentColour; - } - } - } - - public string Text - { - get { return text.Text; } - set { text.Text = value; } - } - - private const float transition_length = 500; - - private void fadeIn() - { - box.FadeIn(transition_length, Easing.OutQuint); - text.FadeColour(Color4.White, transition_length, Easing.OutQuint); - } - - private void fadeOut() - { - box.FadeOut(transition_length, Easing.OutQuint); - text.FadeColour(AccentColour, transition_length, Easing.OutQuint); - } - - protected override bool OnHover(InputState state) - { - fadeIn(); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - if (!Current) - fadeOut(); - - base.OnHoverLost(state); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (accentColour == null) - AccentColour = colours.Blue; - } - - public OsuTabControlCheckbox() - { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5, Bottom = 5, }, - Spacing = new Vector2(5f, 0f), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - text = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - icon = new SpriteIcon - { - Size = new Vector2(14), - Icon = FontAwesome.fa_circle_o, - Shadow = true, - }, - }, - }, - box = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Alpha = 0, - Colour = Color4.White, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - } - }; - - Current.ValueChanged += v => - { - if (v) - { - fadeIn(); - icon.Icon = FontAwesome.fa_check_circle_o; - } - else - { - fadeOut(); - icon.Icon = FontAwesome.fa_circle_o; - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A Checkbox styled to be placed in line with an + /// + public class OsuTabControlCheckbox : Checkbox + { + private readonly Box box; + private readonly SpriteText text; + private readonly SpriteIcon icon; + + private Color4? accentColour; + public Color4 AccentColour + { + get { return accentColour.GetValueOrDefault(); } + set + { + accentColour = value; + + if (Current) + { + text.Colour = AccentColour; + icon.Colour = AccentColour; + } + } + } + + public string Text + { + get { return text.Text; } + set { text.Text = value; } + } + + private const float transition_length = 500; + + private void fadeIn() + { + box.FadeIn(transition_length, Easing.OutQuint); + text.FadeColour(Color4.White, transition_length, Easing.OutQuint); + } + + private void fadeOut() + { + box.FadeOut(transition_length, Easing.OutQuint); + text.FadeColour(AccentColour, transition_length, Easing.OutQuint); + } + + protected override bool OnHover(InputState state) + { + fadeIn(); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if (!Current) + fadeOut(); + + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == null) + AccentColour = colours.Blue; + } + + public OsuTabControlCheckbox() + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5, Bottom = 5, }, + Spacing = new Vector2(5f, 0f), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + text = new OsuSpriteText + { + TextSize = 14, + Font = @"Exo2.0-Bold", + }, + icon = new SpriteIcon + { + Size = new Vector2(14), + Icon = FontAwesome.fa_circle_o, + Shadow = true, + }, + }, + }, + box = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = 0, + Colour = Color4.White, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + } + }; + + Current.ValueChanged += v => + { + if (v) + { + fadeIn(); + icon.Icon = FontAwesome.fa_check_circle_o; + } + else + { + fadeOut(); + icon.Icon = FontAwesome.fa_circle_o; + } + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 31093f1429..6021af2028 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -1,63 +1,63 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; - -namespace osu.Game.Graphics.UserInterface -{ - public class OsuTextBox : TextBox - { - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f); - protected override Color4 BackgroundCommit => BorderColour; - - protected override float LeftRightPadding => 10; - - protected override SpriteText CreatePlaceholder() => new OsuSpriteText - { - Font = @"Exo2.0-MediumItalic", - Colour = new Color4(180, 180, 180, 255), - Margin = new MarginPadding { Left = 2 }, - }; - - public OsuTextBox() - { - Height = 40; - TextContainer.Height = 0.5f; - CornerRadius = 5; - - Current.DisabledChanged += disabled => - { - Alpha = disabled ? 0.3f : 1; - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - BorderColour = colour.Yellow; - } - - protected override void OnFocus(InputState state) - { - BorderThickness = 3; - base.OnFocus(state); - } - - protected override void OnFocusLost(InputState state) - { - BorderThickness = 0; - - base.OnFocusLost(state); - } - - protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), TextSize = CalculatedTextSize }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuTextBox : TextBox + { + protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f); + protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f); + protected override Color4 BackgroundCommit => BorderColour; + + protected override float LeftRightPadding => 10; + + protected override SpriteText CreatePlaceholder() => new OsuSpriteText + { + Font = @"Exo2.0-MediumItalic", + Colour = new Color4(180, 180, 180, 255), + Margin = new MarginPadding { Left = 2 }, + }; + + public OsuTextBox() + { + Height = 40; + TextContainer.Height = 0.5f; + CornerRadius = 5; + + Current.DisabledChanged += disabled => + { + Alpha = disabled ? 0.3f : 1; + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + BorderColour = colour.Yellow; + } + + protected override void OnFocus(InputState state) + { + BorderThickness = 3; + base.OnFocus(state); + } + + protected override void OnFocusLost(InputState state) + { + BorderThickness = 0; + + base.OnFocusLost(state); + } + + protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), TextSize = CalculatedTextSize }; + } +} diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index 57ee334f6b..3a8c72725e 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -1,98 +1,98 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Graphics.UserInterface -{ - public class PageTabControl : OsuTabControl - { - protected override TabItem CreateTabItem(T value) => new PageTabItem(value); - - public PageTabControl() - { - Height = 30; - } - - public class PageTabItem : TabItem - { - private const float transition_duration = 100; - - private readonly Box box; - - protected readonly SpriteText Text; - - public PageTabItem(T value) : base(value) - { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - - Children = new Drawable[] - { - Text = new OsuSpriteText - { - Margin = new MarginPadding { Top = 8, Bottom = 8 }, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Text = (value as Enum)?.GetDescription() ?? value.ToString(), - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - box = new Box - { - RelativeSizeAxes = Axes.X, - Height = 5, - Scale = new Vector2(1f, 0f), - Colour = Color4.White, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - }, - new HoverClickSounds() - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - box.Colour = colours.Yellow; - } - - protected override bool OnHover(InputState state) - { - if (!Active) - slideActive(); - return true; - } - - protected override void OnHoverLost(InputState state) - { - if (!Active) - slideInactive(); - } - - private void slideActive() - { - box.ScaleTo(new Vector2(1f), transition_duration); - } - - private void slideInactive() - { - box.ScaleTo(new Vector2(1f, 0f), transition_duration); - } - - protected override void OnActivated() => slideActive(); - - protected override void OnDeactivated() => slideInactive(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + public class PageTabControl : OsuTabControl + { + protected override TabItem CreateTabItem(T value) => new PageTabItem(value); + + public PageTabControl() + { + Height = 30; + } + + public class PageTabItem : TabItem + { + private const float transition_duration = 100; + + private readonly Box box; + + protected readonly SpriteText Text; + + public PageTabItem(T value) : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + Text = new OsuSpriteText + { + Margin = new MarginPadding { Top = 8, Bottom = 8 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Text = (value as Enum)?.GetDescription() ?? value.ToString(), + TextSize = 14, + Font = @"Exo2.0-Bold", + }, + box = new Box + { + RelativeSizeAxes = Axes.X, + Height = 5, + Scale = new Vector2(1f, 0f), + Colour = Color4.White, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + }, + new HoverClickSounds() + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + box.Colour = colours.Yellow; + } + + protected override bool OnHover(InputState state) + { + if (!Active) + slideActive(); + return true; + } + + protected override void OnHoverLost(InputState state) + { + if (!Active) + slideInactive(); + } + + private void slideActive() + { + box.ScaleTo(new Vector2(1f), transition_duration); + } + + private void slideInactive() + { + box.ScaleTo(new Vector2(1f, 0f), transition_duration); + } + + protected override void OnActivated() => slideActive(); + + protected override void OnDeactivated() => slideInactive(); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 376f7a46ee..ef3fc156e7 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// Used as an accuracy counter. Represented visually as a percentage. - /// - public class PercentageCounter : RollingCounter - { - protected override double RollingDuration => 750; - - private float epsilon => 1e-10f; - - public void SetFraction(float numerator, float denominator) - { - Current.Value = Math.Abs(denominator) < epsilon ? 1.0f : numerator / denominator; - } - - public PercentageCounter() - { - DisplayedCountSpriteText.FixedWidth = true; - Current.Value = DisplayedCount = 1.0f; - } - - protected override string FormatCount(double count) - { - return $@"{count:P2}"; - } - - protected override double GetProportionalDuration(double currentValue, double newValue) - { - return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; - } - - public override void Increment(double amount) - { - Current.Value = Current + amount; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Used as an accuracy counter. Represented visually as a percentage. + /// + public class PercentageCounter : RollingCounter + { + protected override double RollingDuration => 750; + + private float epsilon => 1e-10f; + + public void SetFraction(float numerator, float denominator) + { + Current.Value = Math.Abs(denominator) < epsilon ? 1.0f : numerator / denominator; + } + + public PercentageCounter() + { + DisplayedCountSpriteText.FixedWidth = true; + Current.Value = DisplayedCount = 1.0f; + } + + protected override string FormatCount(double count) + { + return $@"{count:P2}"; + } + + protected override double GetProportionalDuration(double currentValue, double newValue) + { + return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; + } + + public override void Increment(double amount) + { + Current.Value = Current + amount; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index 6021cf08be..beb1d0055e 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -1,67 +1,67 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using OpenTK.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public class ProgressBar : SliderBar - { - public Action OnSeek; - - private readonly Box fill; - private readonly Box background; - - public Color4 FillColour - { - set { fill.FadeColour(value, 150, Easing.OutQuint); } - } - - public Color4 BackgroundColour - { - set - { - background.Alpha = 1; - background.Colour = value; - } - } - - public double EndTime - { - set { CurrentNumber.MaxValue = value; } - } - - public double CurrentTime - { - set { CurrentNumber.Value = value; } - } - - public ProgressBar() - { - CurrentNumber.MinValue = 0; - CurrentNumber.MaxValue = 1; - RelativeSizeAxes = Axes.X; - - Children = new Drawable[] - { - background = new Box - { - Alpha = 0, - RelativeSizeAxes = Axes.Both - }, - fill = new Box { RelativeSizeAxes = Axes.Y } - }; - } - - protected override void UpdateValue(float value) - { - fill.Width = value * UsableWidth; - } - - protected override void OnUserChange() => OnSeek?.Invoke(Current); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class ProgressBar : SliderBar + { + public Action OnSeek; + + private readonly Box fill; + private readonly Box background; + + public Color4 FillColour + { + set { fill.FadeColour(value, 150, Easing.OutQuint); } + } + + public Color4 BackgroundColour + { + set + { + background.Alpha = 1; + background.Colour = value; + } + } + + public double EndTime + { + set { CurrentNumber.MaxValue = value; } + } + + public double CurrentTime + { + set { CurrentNumber.Value = value; } + } + + public ProgressBar() + { + CurrentNumber.MinValue = 0; + CurrentNumber.MaxValue = 1; + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + background = new Box + { + Alpha = 0, + RelativeSizeAxes = Axes.Both + }, + fill = new Box { RelativeSizeAxes = Axes.Y } + }; + } + + protected override void UpdateValue(float value) + { + fill.Width = value * UsableWidth; + } + + protected override void OnUserChange() => OnSeek?.Invoke(Current); + } +} diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 3eeba02154..f84404a911 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -1,182 +1,182 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using System; -using System.Collections.Generic; -using OpenTK.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public abstract class RollingCounter : Container, IHasAccentColour - where T : struct, IEquatable - { - /// - /// The current value. - /// - public Bindable Current = new Bindable(); - - protected SpriteText DisplayedCountSpriteText; - - /// - /// If true, the roll-up duration will be proportional to change in value. - /// - protected virtual bool IsRollingProportional => false; - - /// - /// If IsRollingProportional = false, duration in milliseconds for the counter roll-up animation for each - /// element; else duration in milliseconds for the counter roll-up animation in total. - /// - protected virtual double RollingDuration => 0; - - /// - /// Easing for the counter rollover animation. - /// - protected virtual Easing RollingEasing => Easing.OutQuint; - - private T displayedCount; - - /// - /// Value shown at the current moment. - /// - public virtual T DisplayedCount - { - get - { - return displayedCount; - } - - set - { - if (EqualityComparer.Default.Equals(displayedCount, value)) - return; - displayedCount = value; - DisplayedCountSpriteText.Text = FormatCount(value); - } - } - - public abstract void Increment(T amount); - - private float textSize; - - public float TextSize - { - get { return textSize; } - set - { - textSize = value; - DisplayedCountSpriteText.TextSize = value; - } - } - - public Color4 AccentColour - { - get { return DisplayedCountSpriteText.Colour; } - set { DisplayedCountSpriteText.Colour = value; } - } - - /// - /// Skeleton of a numeric counter which value rolls over time. - /// - protected RollingCounter() - { - Children = new Drawable[] - { - DisplayedCountSpriteText = new OsuSpriteText - { - Font = @"Venera" - }, - }; - - TextSize = 40; - AutoSizeAxes = Axes.Both; - - DisplayedCount = Current; - - Current.ValueChanged += newValue => - { - if (IsLoaded) TransformCount(displayedCount, newValue); - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - DisplayedCountSpriteText.Text = FormatCount(Current); - } - - /// - /// Sets count value, bypassing rollover animation. - /// - /// New count value. - public virtual void SetCountWithoutRolling(T count) - { - Current.Value = count; - StopRolling(); - } - - /// - /// Stops rollover animation, forcing the displayed count to be the actual count. - /// - public virtual void StopRolling() - { - FinishTransforms(false, nameof(DisplayedCount)); - DisplayedCount = Current; - } - - /// - /// Resets count to default value. - /// - public virtual void ResetCount() - { - SetCountWithoutRolling(default(T)); - } - - /// - /// Calculates the duration of the roll-up animation by using the difference between the current visible value - /// and the new final value. - /// - /// - /// To be used in conjunction with IsRollingProportional = true. - /// Unless a derived class needs to have a proportional rolling, it is not necessary to override this function. - /// - /// Current visible value. - /// New final value. - /// Calculated rollover duration in milliseconds. - protected virtual double GetProportionalDuration(T currentValue, T newValue) - { - return RollingDuration; - } - - /// - /// Used to format counts. - /// - /// Count to format. - /// Count formatted as a string. - protected virtual string FormatCount(T count) - { - return count.ToString(); - } - - /// - /// Called when the count is updated to add a transformer that changes the value of the visible count (i.e. - /// implement the rollover animation). - /// - /// Count value before modification. - /// Expected count value after modification. - protected virtual void TransformCount(T currentValue, T newValue) - { - double rollingTotalDuration = - IsRollingProportional - ? GetProportionalDuration(currentValue, newValue) - : RollingDuration; - - this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using System; +using System.Collections.Generic; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public abstract class RollingCounter : Container, IHasAccentColour + where T : struct, IEquatable + { + /// + /// The current value. + /// + public Bindable Current = new Bindable(); + + protected SpriteText DisplayedCountSpriteText; + + /// + /// If true, the roll-up duration will be proportional to change in value. + /// + protected virtual bool IsRollingProportional => false; + + /// + /// If IsRollingProportional = false, duration in milliseconds for the counter roll-up animation for each + /// element; else duration in milliseconds for the counter roll-up animation in total. + /// + protected virtual double RollingDuration => 0; + + /// + /// Easing for the counter rollover animation. + /// + protected virtual Easing RollingEasing => Easing.OutQuint; + + private T displayedCount; + + /// + /// Value shown at the current moment. + /// + public virtual T DisplayedCount + { + get + { + return displayedCount; + } + + set + { + if (EqualityComparer.Default.Equals(displayedCount, value)) + return; + displayedCount = value; + DisplayedCountSpriteText.Text = FormatCount(value); + } + } + + public abstract void Increment(T amount); + + private float textSize; + + public float TextSize + { + get { return textSize; } + set + { + textSize = value; + DisplayedCountSpriteText.TextSize = value; + } + } + + public Color4 AccentColour + { + get { return DisplayedCountSpriteText.Colour; } + set { DisplayedCountSpriteText.Colour = value; } + } + + /// + /// Skeleton of a numeric counter which value rolls over time. + /// + protected RollingCounter() + { + Children = new Drawable[] + { + DisplayedCountSpriteText = new OsuSpriteText + { + Font = @"Venera" + }, + }; + + TextSize = 40; + AutoSizeAxes = Axes.Both; + + DisplayedCount = Current; + + Current.ValueChanged += newValue => + { + if (IsLoaded) TransformCount(displayedCount, newValue); + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + DisplayedCountSpriteText.Text = FormatCount(Current); + } + + /// + /// Sets count value, bypassing rollover animation. + /// + /// New count value. + public virtual void SetCountWithoutRolling(T count) + { + Current.Value = count; + StopRolling(); + } + + /// + /// Stops rollover animation, forcing the displayed count to be the actual count. + /// + public virtual void StopRolling() + { + FinishTransforms(false, nameof(DisplayedCount)); + DisplayedCount = Current; + } + + /// + /// Resets count to default value. + /// + public virtual void ResetCount() + { + SetCountWithoutRolling(default(T)); + } + + /// + /// Calculates the duration of the roll-up animation by using the difference between the current visible value + /// and the new final value. + /// + /// + /// To be used in conjunction with IsRollingProportional = true. + /// Unless a derived class needs to have a proportional rolling, it is not necessary to override this function. + /// + /// Current visible value. + /// New final value. + /// Calculated rollover duration in milliseconds. + protected virtual double GetProportionalDuration(T currentValue, T newValue) + { + return RollingDuration; + } + + /// + /// Used to format counts. + /// + /// Count to format. + /// Count formatted as a string. + protected virtual string FormatCount(T count) + { + return count.ToString(); + } + + /// + /// Called when the count is updated to add a transformer that changes the value of the visible count (i.e. + /// implement the rollover animation). + /// + /// Count value before modification. + /// Expected count value after modification. + protected virtual void TransformCount(T currentValue, T newValue) + { + double rollingTotalDuration = + IsRollingProportional + ? GetProportionalDuration(currentValue, newValue) + : RollingDuration; + + this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 8fd9f41211..df26f81629 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -1,54 +1,54 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public class ScoreCounter : RollingCounter - { - protected override double RollingDuration => 1000; - protected override Easing RollingEasing => Easing.Out; - - public bool UseCommaSeparator; - - /// - /// How many leading zeroes the counter has. - /// - public uint LeadingZeroes - { - get; - protected set; - } - - /// - /// Displays score. - /// - /// How many leading zeroes the counter will have. - public ScoreCounter(uint leading = 0) - { - DisplayedCountSpriteText.FixedWidth = true; - LeadingZeroes = leading; - } - - protected override double GetProportionalDuration(double currentValue, double newValue) - { - return currentValue > newValue ? currentValue - newValue : newValue - currentValue; - } - - protected override string FormatCount(double count) - { - string format = new string('0', (int)LeadingZeroes); - if (UseCommaSeparator) - for (int i = format.Length - 3; i > 0; i -= 3) - format = format.Insert(i, @","); - - return ((long)count).ToString(format); - } - - public override void Increment(double amount) - { - Current.Value = Current + amount; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class ScoreCounter : RollingCounter + { + protected override double RollingDuration => 1000; + protected override Easing RollingEasing => Easing.Out; + + public bool UseCommaSeparator; + + /// + /// How many leading zeroes the counter has. + /// + public uint LeadingZeroes + { + get; + protected set; + } + + /// + /// Displays score. + /// + /// How many leading zeroes the counter will have. + public ScoreCounter(uint leading = 0) + { + DisplayedCountSpriteText.FixedWidth = true; + LeadingZeroes = leading; + } + + protected override double GetProportionalDuration(double currentValue, double newValue) + { + return currentValue > newValue ? currentValue - newValue : newValue - currentValue; + } + + protected override string FormatCount(double count) + { + string format = new string('0', (int)LeadingZeroes); + if (UseCommaSeparator) + for (int i = format.Length - 3; i > 0; i -= 3) + format = format.Insert(i, @","); + + return ((long)count).ToString(format); + } + + public override void Increment(double amount) + { + Current.Value = Current + amount; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 28d33bbacd..e50539e120 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -1,73 +1,73 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Input; -using OpenTK; -using OpenTK.Input; - -namespace osu.Game.Graphics.UserInterface -{ - public class SearchTextBox : FocusedTextBox - { - protected virtual bool AllowCommit => false; - - public override bool HandleLeftRightArrows => false; - - public SearchTextBox() - { - Height = 35; - AddRange(new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.fa_search, - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Margin = new MarginPadding { Right = 10 }, - Size = new Vector2(20), - } - }); - - PlaceholderText = "type to search"; - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (HandlePendingText(state)) return true; - - if (!state.Keyboard.ControlPressed && !state.Keyboard.ShiftPressed) - { - switch (args.Key) - { - case Key.Left: - case Key.Right: - case Key.Up: - case Key.Down: - return false; - } - } - - if (!AllowCommit) - { - switch (args.Key) - { - case Key.KeypadEnter: - case Key.Enter: - return false; - } - } - - if (state.Keyboard.ShiftPressed) - { - switch (args.Key) - { - case Key.Delete: - return false; - } - } - - return base.OnKeyDown(state, args); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Input; +using OpenTK; +using OpenTK.Input; + +namespace osu.Game.Graphics.UserInterface +{ + public class SearchTextBox : FocusedTextBox + { + protected virtual bool AllowCommit => false; + + public override bool HandleLeftRightArrows => false; + + public SearchTextBox() + { + Height = 35; + AddRange(new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_search, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, + Size = new Vector2(20), + } + }); + + PlaceholderText = "type to search"; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (HandlePendingText(state)) return true; + + if (!state.Keyboard.ControlPressed && !state.Keyboard.ShiftPressed) + { + switch (args.Key) + { + case Key.Left: + case Key.Right: + case Key.Up: + case Key.Down: + return false; + } + } + + if (!AllowCommit) + { + switch (args.Key) + { + case Key.KeypadEnter: + case Key.Enter: + return false; + } + } + + if (state.Keyboard.ShiftPressed) + { + switch (args.Key) + { + case Key.Delete: + return false; + } + } + + return base.OnKeyDown(state, args); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index cf09609516..cf55240e71 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// Used as an accuracy counter. Represented visually as a percentage. - /// - public class SimpleComboCounter : RollingCounter - { - protected override double RollingDuration => 750; - - public SimpleComboCounter() - { - Current.Value = DisplayedCount = 0; - } - - protected override string FormatCount(int count) - { - return $@"{count}x"; - } - - protected override double GetProportionalDuration(int currentValue, int newValue) - { - return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; - } - - public override void Increment(int amount) - { - Current.Value = Current + amount; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Used as an accuracy counter. Represented visually as a percentage. + /// + public class SimpleComboCounter : RollingCounter + { + protected override double RollingDuration => 750; + + public SimpleComboCounter() + { + Current.Value = DisplayedCount = 0; + } + + protected override string FormatCount(int count) + { + return $@"{count}x"; + } + + protected override double GetProportionalDuration(int currentValue, int newValue) + { + return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; + } + + public override void Increment(int amount) + { + Current.Value = Current + amount; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index cefa4b3e28..621b5dcf11 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -1,154 +1,154 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.MathUtils; -using System; -using System.Linq; - -namespace osu.Game.Graphics.UserInterface -{ - public class StarCounter : Container - { - private readonly Container stars; - - /// - /// Maximum amount of stars displayed. - /// - /// - /// This does not limit the counter value, but the amount of stars displayed. - /// - public int StarCount { get; } - - private double animationDelay => 80; - - private double scalingDuration => 1000; - private Easing scalingEasing => Easing.OutElasticHalf; - private float minStarScale => 0.4f; - - private double fadingDuration => 100; - private float minStarAlpha => 0.5f; - - private const float star_size = 20; - private const float star_spacing = 4; - - private float countStars; - - /// - /// Amount of stars represented. - /// - public float CountStars - { - get - { - return countStars; - } - - set - { - if (countStars == value) return; - - if (IsLoaded) - transformCount(value); - countStars = value; - } - } - - /// - /// Shows a float count as stars. Used as star difficulty display. - /// - /// Maximum amount of stars to display. - public StarCounter(int starCount = 10) - { - StarCount = Math.Max(starCount, 0); - - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - stars = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(star_spacing), - ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => new Star { Alpha = minStarAlpha }) - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // Animate initial state from zero. - ReplayAnimation(); - } - - public void ResetCount() - { - countStars = 0; - StopAnimation(); - } - - public void ReplayAnimation() - { - var t = countStars; - ResetCount(); - CountStars = t; - } - - public void StopAnimation() - { - int i = 0; - foreach (var star in stars.Children) - { - star.ClearTransforms(true); - star.FadeTo(i < countStars ? 1.0f : minStarAlpha); - star.Icon.ScaleTo(getStarScale(i, countStars)); - i++; - } - } - - private float getStarScale(int i, float value) - { - if (value <= i) - return minStarScale; - - return i + 1 <= value ? 1.0f : Interpolation.ValueAt(value, minStarScale, 1.0f, i, i + 1); - } - - private void transformCount(float newValue) - { - int i = 0; - foreach (var star in stars.Children) - { - star.ClearTransforms(true); - - var delay = (countStars <= newValue ? Math.Max(i - countStars, 0) : Math.Max(countStars - 1 - i, 0)) * animationDelay; - star.Delay(delay).FadeTo(i < newValue ? 1.0f : minStarAlpha, fadingDuration); - star.Icon.Delay(delay).ScaleTo(getStarScale(i, newValue), scalingDuration, scalingEasing); - - i++; - } - } - - private class Star : Container - { - public readonly SpriteIcon Icon; - public Star() - { - Size = new Vector2(star_size); - - Child = Icon = new SpriteIcon - { - Size = new Vector2(star_size), - Icon = FontAwesome.fa_star, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using System; +using System.Linq; + +namespace osu.Game.Graphics.UserInterface +{ + public class StarCounter : Container + { + private readonly Container stars; + + /// + /// Maximum amount of stars displayed. + /// + /// + /// This does not limit the counter value, but the amount of stars displayed. + /// + public int StarCount { get; } + + private double animationDelay => 80; + + private double scalingDuration => 1000; + private Easing scalingEasing => Easing.OutElasticHalf; + private float minStarScale => 0.4f; + + private double fadingDuration => 100; + private float minStarAlpha => 0.5f; + + private const float star_size = 20; + private const float star_spacing = 4; + + private float countStars; + + /// + /// Amount of stars represented. + /// + public float CountStars + { + get + { + return countStars; + } + + set + { + if (countStars == value) return; + + if (IsLoaded) + transformCount(value); + countStars = value; + } + } + + /// + /// Shows a float count as stars. Used as star difficulty display. + /// + /// Maximum amount of stars to display. + public StarCounter(int starCount = 10) + { + StarCount = Math.Max(starCount, 0); + + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + stars = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(star_spacing), + ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => new Star { Alpha = minStarAlpha }) + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Animate initial state from zero. + ReplayAnimation(); + } + + public void ResetCount() + { + countStars = 0; + StopAnimation(); + } + + public void ReplayAnimation() + { + var t = countStars; + ResetCount(); + CountStars = t; + } + + public void StopAnimation() + { + int i = 0; + foreach (var star in stars.Children) + { + star.ClearTransforms(true); + star.FadeTo(i < countStars ? 1.0f : minStarAlpha); + star.Icon.ScaleTo(getStarScale(i, countStars)); + i++; + } + } + + private float getStarScale(int i, float value) + { + if (value <= i) + return minStarScale; + + return i + 1 <= value ? 1.0f : Interpolation.ValueAt(value, minStarScale, 1.0f, i, i + 1); + } + + private void transformCount(float newValue) + { + int i = 0; + foreach (var star in stars.Children) + { + star.ClearTransforms(true); + + var delay = (countStars <= newValue ? Math.Max(i - countStars, 0) : Math.Max(countStars - 1 - i, 0)) * animationDelay; + star.Delay(delay).FadeTo(i < newValue ? 1.0f : minStarAlpha, fadingDuration); + star.Icon.Delay(delay).ScaleTo(getStarScale(i, newValue), scalingDuration, scalingEasing); + + i++; + } + } + + private class Star : Container + { + public readonly SpriteIcon Icon; + public Star() + { + Size = new Vector2(star_size); + + Child = Icon = new SpriteIcon + { + Size = new Vector2(star_size), + Icon = FontAwesome.fa_star, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs index c87d81641f..a6492ddd63 100644 --- a/osu.Game/Graphics/UserInterface/TriangleButton.cs +++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs @@ -1,108 +1,108 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Graphics.UserInterface -{ - /// - /// A button with moving triangles in the background. - /// - public class TriangleButton : OsuButton, IFilterable - { - private Box hover; - - protected Triangles Triangles; - - public TriangleButton() - { - Height = 40; - } - - protected override SpriteText CreateText() => new OsuSpriteText - { - Depth = -1, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Font = @"Exo2.0-Bold", - }; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.BlueDark; - - Content.Masking = true; - Content.CornerRadius = 5; - - AddRange(new Drawable[] - { - Triangles = new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourDark = colours.BlueDarker, - ColourLight = colours.Blue, - }, - hover = new Box - { - RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, - Colour = Color4.White.Opacity(0.1f), - Alpha = 0, - }, - }); - - Enabled.ValueChanged += enabled_ValueChanged; - Enabled.TriggerChange(); - } - - private void enabled_ValueChanged(bool enabled) - { - this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint); - } - - protected override bool OnHover(InputState state) - { - hover.FadeIn(200); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - hover.FadeOut(200); - base.OnHoverLost(state); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - Content.ScaleTo(0.9f, 4000, Easing.OutQuint); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - Content.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); - } - - public IEnumerable FilterTerms => new[] { Text }; - - public bool MatchingFilter - { - set - { - this.FadeTo(value ? 1 : 0); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A button with moving triangles in the background. + /// + public class TriangleButton : OsuButton, IFilterable + { + private Box hover; + + protected Triangles Triangles; + + public TriangleButton() + { + Height = 40; + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Font = @"Exo2.0-Bold", + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.BlueDark; + + Content.Masking = true; + Content.CornerRadius = 5; + + AddRange(new Drawable[] + { + Triangles = new Triangles + { + RelativeSizeAxes = Axes.Both, + ColourDark = colours.BlueDarker, + ColourLight = colours.Blue, + }, + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Blending = BlendingMode.Additive, + Colour = Color4.White.Opacity(0.1f), + Alpha = 0, + }, + }); + + Enabled.ValueChanged += enabled_ValueChanged; + Enabled.TriggerChange(); + } + + private void enabled_ValueChanged(bool enabled) + { + this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + } + + protected override bool OnHover(InputState state) + { + hover.FadeIn(200); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + hover.FadeOut(200); + base.OnHoverLost(state); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + Content.ScaleTo(0.9f, 4000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + Content.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + + public IEnumerable FilterTerms => new[] { Text }; + + public bool MatchingFilter + { + set + { + this.FadeTo(value ? 1 : 0); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 751f4809dc..f490306acf 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -1,254 +1,254 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Containers; -using osu.Game.Beatmaps.ControlPoints; -using osu.Framework.Audio.Track; -using System; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Graphics.UserInterface -{ - public class TwoLayerButton : OsuClickableContainer - { - private readonly BouncingIcon bouncingIcon; - - public Box IconLayer; - public Box TextLayer; - - private const int transform_time = 600; - private const int pulse_length = 250; - - private const float shear = 0.1f; - - public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50); - public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50); - private readonly SpriteText text; - - public Color4 HoverColour; - private readonly Container c1; - private readonly Container c2; - - public Color4 BackgroundColour - { - set - { - TextLayer.Colour = value; - IconLayer.Colour = value; - } - } - - public override Anchor Origin - { - get - { - return base.Origin; - } - - set - { - base.Origin = value; - c1.Origin = c1.Anchor = (value & Anchor.x2) > 0 ? Anchor.TopLeft : Anchor.TopRight; - c2.Origin = c2.Anchor = (value & Anchor.x2) > 0 ? Anchor.TopRight : Anchor.TopLeft; - - X = (value & Anchor.x2) > 0 ? SIZE_RETRACTED.X * shear * 0.5f : 0; - - Remove(c1); - Remove(c2); - c1.Depth = (value & Anchor.x2) > 0 ? 0 : 1; - c2.Depth = (value & Anchor.x2) > 0 ? 1 : 0; - Add(c1); - Add(c2); - } - } - - public TwoLayerButton() - { - Size = SIZE_RETRACTED; - - Children = new Drawable[] - { - c2 = new Container - { - RelativeSizeAxes = Axes.Both, - Width = 0.4f, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Shear = new Vector2(shear, 0), - Masking = true, - MaskingSmoothness = 2, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Offset = new Vector2(2, 0), - Radius = 2, - }, - Children = new[] - { - IconLayer = new Box - { - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(2, 0), - }, - } - }, - bouncingIcon = new BouncingIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - }, - c1 = new Container - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Width = 0.6f, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Shear = new Vector2(shear, 0), - Masking = true, - MaskingSmoothness = 2, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Offset = new Vector2(2, 0), - Radius = 2, - }, - Children = new[] - { - TextLayer = new Box - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(2, 0), - }, - } - }, - text = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - } - }, - }; - } - - public FontAwesome Icon - { - set - { - bouncingIcon.Icon = value; - } - } - - public string Text - { - set - { - text.Text = value; - } - } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => IconLayer.ReceiveMouseInputAt(screenSpacePos) || TextLayer.ReceiveMouseInputAt(screenSpacePos); - - protected override bool OnHover(InputState state) - { - this.ResizeTo(SIZE_EXTENDED, transform_time, Easing.OutElastic); - IconLayer.FadeColour(HoverColour, transform_time, Easing.OutElastic); - - bouncingIcon.ScaleTo(1.1f, transform_time, Easing.OutElastic); - - return true; - } - - protected override void OnHoverLost(InputState state) - { - this.ResizeTo(SIZE_RETRACTED, transform_time, Easing.OutElastic); - IconLayer.FadeColour(TextLayer.Colour, transform_time, Easing.OutElastic); - - bouncingIcon.ScaleTo(1, transform_time, Easing.OutElastic); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - return true; - } - - protected override bool OnClick(InputState state) - { - var flash = new Box - { - RelativeSizeAxes = Axes.Both, - Shear = new Vector2(shear, 0), - Colour = Color4.White.Opacity(0.5f), - }; - Add(flash); - - flash.Alpha = 1; - flash.FadeOut(500, Easing.OutQuint); - flash.Expire(); - - return base.OnClick(state); - } - - private class BouncingIcon : BeatSyncedContainer - { - private const double beat_in_time = 60; - - private readonly SpriteIcon icon; - - public FontAwesome Icon { set { icon.Icon = value; } } - - public BouncingIcon() - { - EarlyActivationMilliseconds = beat_in_time; - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(25), - } - }; - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) - { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - - var beatLength = timingPoint.BeatLength; - - float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum); - - if (beatIndex < 0) return; - - icon.ScaleTo(1 - 0.1f * amplitudeAdjust, beat_in_time, Easing.Out) - .Then() - .ScaleTo(1, beatLength * 2, Easing.OutQuint); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Beatmaps.ControlPoints; +using osu.Framework.Audio.Track; +using System; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Graphics.UserInterface +{ + public class TwoLayerButton : OsuClickableContainer + { + private readonly BouncingIcon bouncingIcon; + + public Box IconLayer; + public Box TextLayer; + + private const int transform_time = 600; + private const int pulse_length = 250; + + private const float shear = 0.1f; + + public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50); + public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50); + private readonly SpriteText text; + + public Color4 HoverColour; + private readonly Container c1; + private readonly Container c2; + + public Color4 BackgroundColour + { + set + { + TextLayer.Colour = value; + IconLayer.Colour = value; + } + } + + public override Anchor Origin + { + get + { + return base.Origin; + } + + set + { + base.Origin = value; + c1.Origin = c1.Anchor = (value & Anchor.x2) > 0 ? Anchor.TopLeft : Anchor.TopRight; + c2.Origin = c2.Anchor = (value & Anchor.x2) > 0 ? Anchor.TopRight : Anchor.TopLeft; + + X = (value & Anchor.x2) > 0 ? SIZE_RETRACTED.X * shear * 0.5f : 0; + + Remove(c1); + Remove(c2); + c1.Depth = (value & Anchor.x2) > 0 ? 0 : 1; + c2.Depth = (value & Anchor.x2) > 0 ? 1 : 0; + Add(c1); + Add(c2); + } + } + + public TwoLayerButton() + { + Size = SIZE_RETRACTED; + + Children = new Drawable[] + { + c2 = new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.4f, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(shear, 0), + Masking = true, + MaskingSmoothness = 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Offset = new Vector2(2, 0), + Radius = 2, + }, + Children = new[] + { + IconLayer = new Box + { + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(2, 0), + }, + } + }, + bouncingIcon = new BouncingIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + c1 = new Container + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.6f, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(shear, 0), + Masking = true, + MaskingSmoothness = 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Offset = new Vector2(2, 0), + Radius = 2, + }, + Children = new[] + { + TextLayer = new Box + { + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(2, 0), + }, + } + }, + text = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + } + }, + }; + } + + public FontAwesome Icon + { + set + { + bouncingIcon.Icon = value; + } + } + + public string Text + { + set + { + text.Text = value; + } + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => IconLayer.ReceiveMouseInputAt(screenSpacePos) || TextLayer.ReceiveMouseInputAt(screenSpacePos); + + protected override bool OnHover(InputState state) + { + this.ResizeTo(SIZE_EXTENDED, transform_time, Easing.OutElastic); + IconLayer.FadeColour(HoverColour, transform_time, Easing.OutElastic); + + bouncingIcon.ScaleTo(1.1f, transform_time, Easing.OutElastic); + + return true; + } + + protected override void OnHoverLost(InputState state) + { + this.ResizeTo(SIZE_RETRACTED, transform_time, Easing.OutElastic); + IconLayer.FadeColour(TextLayer.Colour, transform_time, Easing.OutElastic); + + bouncingIcon.ScaleTo(1, transform_time, Easing.OutElastic); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + return true; + } + + protected override bool OnClick(InputState state) + { + var flash = new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(shear, 0), + Colour = Color4.White.Opacity(0.5f), + }; + Add(flash); + + flash.Alpha = 1; + flash.FadeOut(500, Easing.OutQuint); + flash.Expire(); + + return base.OnClick(state); + } + + private class BouncingIcon : BeatSyncedContainer + { + private const double beat_in_time = 60; + + private readonly SpriteIcon icon; + + public FontAwesome Icon { set { icon.Icon = value; } } + + public BouncingIcon() + { + EarlyActivationMilliseconds = beat_in_time; + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(25), + } + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + var beatLength = timingPoint.BeatLength; + + float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum); + + if (beatIndex < 0) return; + + icon.ScaleTo(1 - 0.1f * amplitudeAdjust, beat_in_time, Easing.Out) + .Then() + .ScaleTo(1, beatLength * 2, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index 351a6dff39..d14080de5b 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -1,47 +1,47 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using osu.Framework.IO.Stores; - -namespace osu.Game.IO.Archives -{ - public abstract class ArchiveReader : IDisposable, IResourceStore - { - /// - /// Opens a stream for reading a specific file from this archive. - /// - public abstract Stream GetStream(string name); - - public abstract void Dispose(); - - /// - /// The name of this archive (usually the containing filename). - /// - public readonly string Name; - - protected ArchiveReader(string name) - { - Name = name; - } - - public abstract IEnumerable Filenames { get; } - - public virtual byte[] Get(string name) - { - using (Stream input = GetStream(name)) - { - if (input == null) - return null; - - byte[] buffer = new byte[input.Length]; - input.Read(buffer, 0, buffer.Length); - return buffer; - } - } - - public abstract Stream GetUnderlyingStream(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using osu.Framework.IO.Stores; + +namespace osu.Game.IO.Archives +{ + public abstract class ArchiveReader : IDisposable, IResourceStore + { + /// + /// Opens a stream for reading a specific file from this archive. + /// + public abstract Stream GetStream(string name); + + public abstract void Dispose(); + + /// + /// The name of this archive (usually the containing filename). + /// + public readonly string Name; + + protected ArchiveReader(string name) + { + Name = name; + } + + public abstract IEnumerable Filenames { get; } + + public virtual byte[] Get(string name) + { + using (Stream input = GetStream(name)) + { + if (input == null) + return null; + + byte[] buffer = new byte[input.Length]; + input.Read(buffer, 0, buffer.Length); + return buffer; + } + } + + public abstract Stream GetUnderlyingStream(); + } +} diff --git a/osu.Game/IO/Archives/LegacyFilesystemReader.cs b/osu.Game/IO/Archives/LegacyFilesystemReader.cs index d6d80783db..7871f1439a 100644 --- a/osu.Game/IO/Archives/LegacyFilesystemReader.cs +++ b/osu.Game/IO/Archives/LegacyFilesystemReader.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using osu.Framework.IO.File; - -namespace osu.Game.IO.Archives -{ - /// - /// Reads an extracted legacy beatmap from disk. - /// - public class LegacyFilesystemReader : ArchiveReader - { - private readonly string path; - - public LegacyFilesystemReader(string path) : base(Path.GetFileName(path)) - { - this.path = path; - } - - public override Stream GetStream(string name) => File.OpenRead(Path.Combine(path, name)); - - public override void Dispose() - { - // no-op - } - - public override IEnumerable Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => FileSafety.GetRelativePath(f, path)).ToArray(); - - public override Stream GetUnderlyingStream() => null; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using osu.Framework.IO.File; + +namespace osu.Game.IO.Archives +{ + /// + /// Reads an extracted legacy beatmap from disk. + /// + public class LegacyFilesystemReader : ArchiveReader + { + private readonly string path; + + public LegacyFilesystemReader(string path) : base(Path.GetFileName(path)) + { + this.path = path; + } + + public override Stream GetStream(string name) => File.OpenRead(Path.Combine(path, name)); + + public override void Dispose() + { + // no-op + } + + public override IEnumerable Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => FileSafety.GetRelativePath(f, path)).ToArray(); + + public override Stream GetUnderlyingStream() => null; + } +} diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index dbf236e835..2ea35835fa 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -1,50 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using SharpCompress.Archives.Zip; - -namespace osu.Game.IO.Archives -{ - public sealed class ZipArchiveReader : ArchiveReader - { - private readonly Stream archiveStream; - private readonly ZipArchive archive; - - public ZipArchiveReader(Stream archiveStream, string name = null) - : base(name) - { - this.archiveStream = archiveStream; - archive = ZipArchive.Open(archiveStream); - } - - public override Stream GetStream(string name) - { - ZipArchiveEntry entry = archive.Entries.SingleOrDefault(e => e.Key == name); - if (entry == null) - throw new FileNotFoundException(); - - // allow seeking - MemoryStream copy = new MemoryStream(); - - using (Stream s = entry.OpenEntryStream()) - s.CopyTo(copy); - - copy.Position = 0; - - return copy; - } - - public override void Dispose() - { - archive.Dispose(); - archiveStream.Dispose(); - } - - public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ToArray(); - - public override Stream GetUnderlyingStream() => archiveStream; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SharpCompress.Archives.Zip; + +namespace osu.Game.IO.Archives +{ + public sealed class ZipArchiveReader : ArchiveReader + { + private readonly Stream archiveStream; + private readonly ZipArchive archive; + + public ZipArchiveReader(Stream archiveStream, string name = null) + : base(name) + { + this.archiveStream = archiveStream; + archive = ZipArchive.Open(archiveStream); + } + + public override Stream GetStream(string name) + { + ZipArchiveEntry entry = archive.Entries.SingleOrDefault(e => e.Key == name); + if (entry == null) + throw new FileNotFoundException(); + + // allow seeking + MemoryStream copy = new MemoryStream(); + + using (Stream s = entry.OpenEntryStream()) + s.CopyTo(copy); + + copy.Position = 0; + + return copy; + } + + public override void Dispose() + { + archive.Dispose(); + archiveStream.Dispose(); + } + + public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ToArray(); + + public override Stream GetUnderlyingStream() => archiveStream; + } +} diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index 94a3d455d4..1804be90a0 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel.DataAnnotations.Schema; -using System.IO; - -namespace osu.Game.IO -{ - public class FileInfo - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int ID { get; set; } - - public string Hash { get; set; } - - public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash); - - public int ReferenceCount { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations.Schema; +using System.IO; + +namespace osu.Game.IO +{ + public class FileInfo + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public string Hash { get; set; } + + public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash); + + public int ReferenceCount { get; set; } + } +} diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index ab81ba4851..050fccb98a 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -1,114 +1,114 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using System.Linq; -using osu.Framework.Extensions; -using osu.Framework.IO.Stores; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Database; - -namespace osu.Game.IO -{ - /// - /// Handles the Store and retrieval of Files/FileSets to the database backing - /// - public class FileStore : DatabaseBackedStore - { - public readonly IResourceStore Store; - - public new Storage Storage => base.Storage; - - public FileStore(IDatabaseContextFactory contextFactory, Storage storage) : base(contextFactory, storage.GetStorageForDirectory(@"files")) - { - Store = new StorageBackedResourceStore(Storage); - } - - public FileInfo Add(Stream data, bool reference = true) - { - using (var usage = ContextFactory.GetForWrite()) - { - string hash = data.ComputeSHA2Hash(); - - var existing = usage.Context.FileInfo.FirstOrDefault(f => f.Hash == hash); - - var info = existing ?? new FileInfo { Hash = hash }; - - string path = info.StoragePath; - - // we may be re-adding a file to fix missing store entries. - if (!Storage.Exists(path)) - { - data.Seek(0, SeekOrigin.Begin); - - using (var output = Storage.GetStream(path, FileAccess.Write)) - data.CopyTo(output); - - data.Seek(0, SeekOrigin.Begin); - } - - if (reference || existing == null) - Reference(info); - - return info; - } - } - - public void Reference(params FileInfo[] files) - { - if (files.Length == 0) return; - - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - foreach (var f in files.GroupBy(f => f.ID)) - { - var refetch = context.Find(f.First().ID) ?? f.First(); - refetch.ReferenceCount += f.Count(); - context.FileInfo.Update(refetch); - } - } - } - - public void Dereference(params FileInfo[] files) - { - if (files.Length == 0) return; - - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - foreach (var f in files.GroupBy(f => f.ID)) - { - var refetch = context.FileInfo.Find(f.Key); - refetch.ReferenceCount -= f.Count(); - context.FileInfo.Update(refetch); - } - } - } - - public override void Cleanup() - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1)) - { - try - { - Storage.Delete(f.StoragePath); - context.FileInfo.Remove(f); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete beatmap {f}"); - } - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Linq; +using osu.Framework.Extensions; +using osu.Framework.IO.Stores; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.IO +{ + /// + /// Handles the Store and retrieval of Files/FileSets to the database backing + /// + public class FileStore : DatabaseBackedStore + { + public readonly IResourceStore Store; + + public new Storage Storage => base.Storage; + + public FileStore(IDatabaseContextFactory contextFactory, Storage storage) : base(contextFactory, storage.GetStorageForDirectory(@"files")) + { + Store = new StorageBackedResourceStore(Storage); + } + + public FileInfo Add(Stream data, bool reference = true) + { + using (var usage = ContextFactory.GetForWrite()) + { + string hash = data.ComputeSHA2Hash(); + + var existing = usage.Context.FileInfo.FirstOrDefault(f => f.Hash == hash); + + var info = existing ?? new FileInfo { Hash = hash }; + + string path = info.StoragePath; + + // we may be re-adding a file to fix missing store entries. + if (!Storage.Exists(path)) + { + data.Seek(0, SeekOrigin.Begin); + + using (var output = Storage.GetStream(path, FileAccess.Write)) + data.CopyTo(output); + + data.Seek(0, SeekOrigin.Begin); + } + + if (reference || existing == null) + Reference(info); + + return info; + } + } + + public void Reference(params FileInfo[] files) + { + if (files.Length == 0) return; + + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + foreach (var f in files.GroupBy(f => f.ID)) + { + var refetch = context.Find(f.First().ID) ?? f.First(); + refetch.ReferenceCount += f.Count(); + context.FileInfo.Update(refetch); + } + } + } + + public void Dereference(params FileInfo[] files) + { + if (files.Length == 0) return; + + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + foreach (var f in files.GroupBy(f => f.ID)) + { + var refetch = context.FileInfo.Find(f.Key); + refetch.ReferenceCount -= f.Count(); + context.FileInfo.Update(refetch); + } + } + } + + public override void Cleanup() + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1)) + { + try + { + Storage.Delete(f.StoragePath); + context.FileInfo.Remove(f); + } + catch (Exception e) + { + Logger.Error(e, $@"Could not delete beatmap {f}"); + } + } + } + } + } +} diff --git a/osu.Game/IO/Legacy/ILegacySerializable.cs b/osu.Game/IO/Legacy/ILegacySerializable.cs index 43c12a631a..1bf33d8bd7 100644 --- a/osu.Game/IO/Legacy/ILegacySerializable.cs +++ b/osu.Game/IO/Legacy/ILegacySerializable.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.IO.Legacy -{ - public interface ILegacySerializable - { - void ReadFromStream(SerializationReader sr); - void WriteToStream(SerializationWriter sw); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.IO.Legacy +{ + public interface ILegacySerializable + { + void ReadFromStream(SerializationReader sr); + void WriteToStream(SerializationWriter sw); + } +} diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 75cbef80a5..8fd18b9640 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -1,277 +1,277 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; - -namespace osu.Game.IO.Legacy -{ - /// SerializationReader. Extends BinaryReader to add additional data types, - /// handle null strings and simplify use with ISerializable. - public class SerializationReader : BinaryReader - { - private readonly Stream stream; - - public SerializationReader(Stream s) - : base(s, Encoding.UTF8) - { - stream = s; - } - - public int RemainingBytes => (int)(stream.Length - stream.Position); - - /// Static method to take a SerializationInfo object (an input to an ISerializable constructor) - /// and produce a SerializationReader from which serialized objects can be read . - public static SerializationReader GetReader(SerializationInfo info) - { - byte[] byteArray = (byte[])info.GetValue("X", typeof(byte[])); - MemoryStream ms = new MemoryStream(byteArray); - return new SerializationReader(ms); - } - - /// Reads a string from the buffer. Overrides the base implementation so it can cope with nulls. - public override string ReadString() - { - if (0 == ReadByte()) return null; - return base.ReadString(); - } - - /// Reads a byte array from the buffer, handling nulls and the array length. - public byte[] ReadByteArray() - { - int len = ReadInt32(); - if (len > 0) return ReadBytes(len); - if (len < 0) return null; - return Array.Empty(); - } - - /// Reads a char array from the buffer, handling nulls and the array length. - public char[] ReadCharArray() - { - int len = ReadInt32(); - if (len > 0) return ReadChars(len); - if (len < 0) return null; - return Array.Empty(); - } - - /// Reads a DateTime from the buffer. - public DateTime ReadDateTime() - { - long ticks = ReadInt64(); - if (ticks < 0) throw new IOException("Bad ticks count read!"); - return new DateTime(ticks, DateTimeKind.Utc); - } - - /// Reads a generic list from the buffer. - public IList ReadBList(bool skipErrors = false) where T : ILegacySerializable, new() - { - int count = ReadInt32(); - if (count < 0) return null; - IList d = new List(count); - - SerializationReader sr = new SerializationReader(BaseStream); - - for (int i = 0; i < count; i++) - { - T obj = new T(); - try - { - obj.ReadFromStream(sr); - } - catch (Exception) - { - if (skipErrors) - continue; - throw; - } - - d.Add(obj); - } - - return d; - } - - /// Reads a generic list from the buffer. - public IList ReadList() - { - int count = ReadInt32(); - if (count < 0) return null; - IList d = new List(count); - for (int i = 0; i < count; i++) d.Add((T)ReadObject()); - return d; - } - - /// Reads a generic Dictionary from the buffer. - public IDictionary ReadDictionary() - { - int count = ReadInt32(); - if (count < 0) return null; - IDictionary d = new Dictionary(); - for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject(); - return d; - } - - /// Reads an object which was added to the buffer by WriteObject. - public object ReadObject() - { - ObjType t = (ObjType)ReadByte(); - switch (t) - { - case ObjType.boolType: - return ReadBoolean(); - case ObjType.byteType: - return ReadByte(); - case ObjType.uint16Type: - return ReadUInt16(); - case ObjType.uint32Type: - return ReadUInt32(); - case ObjType.uint64Type: - return ReadUInt64(); - case ObjType.sbyteType: - return ReadSByte(); - case ObjType.int16Type: - return ReadInt16(); - case ObjType.int32Type: - return ReadInt32(); - case ObjType.int64Type: - return ReadInt64(); - case ObjType.charType: - return ReadChar(); - case ObjType.stringType: - return base.ReadString(); - case ObjType.singleType: - return ReadSingle(); - case ObjType.doubleType: - return ReadDouble(); - case ObjType.decimalType: - return ReadDecimal(); - case ObjType.dateTimeType: - return ReadDateTime(); - case ObjType.byteArrayType: - return ReadByteArray(); - case ObjType.charArrayType: - return ReadCharArray(); - case ObjType.otherType: - return DynamicDeserializer.Deserialize(BaseStream); - default: - return null; - } - } - - public class DynamicDeserializer - { - private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder; - private static BinaryFormatter formatter; - - private static void initialize() - { - versionBinder = new VersionConfigToNamespaceAssemblyObjectBinder(); - formatter = new BinaryFormatter - { -// AssemblyFormat = FormatterAssemblyStyle.Simple, - Binder = versionBinder - }; - } - - public static object Deserialize(Stream stream) - { - if (formatter == null) - initialize(); - - Debug.Assert(formatter != null, "formatter != null"); - - // ReSharper disable once PossibleNullReferenceException - return formatter.Deserialize(stream); - } - - #region Nested type: VersionConfigToNamespaceAssemblyObjectBinder - - public sealed class VersionConfigToNamespaceAssemblyObjectBinder : SerializationBinder - { - private readonly Dictionary cache = new Dictionary(); - - public override Type BindToType(string assemblyName, string typeName) - { - Type typeToDeserialize; - - if (cache.TryGetValue(assemblyName + typeName, out typeToDeserialize)) - return typeToDeserialize; - - List tmpTypes = new List(); - Type genType = null; - - if (typeName.Contains("System.Collections.Generic") && typeName.Contains("[[")) - { - string[] splitTyps = typeName.Split('['); - - foreach (string typ in splitTyps) - { - if (typ.Contains("Version")) - { - string asmTmp = typ.Substring(typ.IndexOf(',') + 1); - string asmName = asmTmp.Remove(asmTmp.IndexOf(']')).Trim(); - string typName = typ.Remove(typ.IndexOf(',')); - tmpTypes.Add(BindToType(asmName, typName)); - } - else if (typ.Contains("Generic")) - { - genType = BindToType(assemblyName, typ); - } - } - if (genType != null && tmpTypes.Count > 0) - { - return genType.MakeGenericType(tmpTypes.ToArray()); - } - } - - string toAssemblyName = assemblyName.Split(',')[0]; - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (Assembly a in assemblies) - { - if (a.FullName.Split(',')[0] == toAssemblyName) - { - typeToDeserialize = a.GetType(typeName); - break; - } - } - - cache.Add(assemblyName + typeName, typeToDeserialize); - - return typeToDeserialize; - } - } - - #endregion - } - } - - public enum ObjType : byte - { - nullType, - boolType, - byteType, - uint16Type, - uint32Type, - uint64Type, - sbyteType, - int16Type, - int32Type, - int64Type, - charType, - stringType, - singleType, - doubleType, - decimalType, - dateTimeType, - byteArrayType, - charArrayType, - otherType, - ILegacySerializableType - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; + +namespace osu.Game.IO.Legacy +{ + /// SerializationReader. Extends BinaryReader to add additional data types, + /// handle null strings and simplify use with ISerializable. + public class SerializationReader : BinaryReader + { + private readonly Stream stream; + + public SerializationReader(Stream s) + : base(s, Encoding.UTF8) + { + stream = s; + } + + public int RemainingBytes => (int)(stream.Length - stream.Position); + + /// Static method to take a SerializationInfo object (an input to an ISerializable constructor) + /// and produce a SerializationReader from which serialized objects can be read . + public static SerializationReader GetReader(SerializationInfo info) + { + byte[] byteArray = (byte[])info.GetValue("X", typeof(byte[])); + MemoryStream ms = new MemoryStream(byteArray); + return new SerializationReader(ms); + } + + /// Reads a string from the buffer. Overrides the base implementation so it can cope with nulls. + public override string ReadString() + { + if (0 == ReadByte()) return null; + return base.ReadString(); + } + + /// Reads a byte array from the buffer, handling nulls and the array length. + public byte[] ReadByteArray() + { + int len = ReadInt32(); + if (len > 0) return ReadBytes(len); + if (len < 0) return null; + return Array.Empty(); + } + + /// Reads a char array from the buffer, handling nulls and the array length. + public char[] ReadCharArray() + { + int len = ReadInt32(); + if (len > 0) return ReadChars(len); + if (len < 0) return null; + return Array.Empty(); + } + + /// Reads a DateTime from the buffer. + public DateTime ReadDateTime() + { + long ticks = ReadInt64(); + if (ticks < 0) throw new IOException("Bad ticks count read!"); + return new DateTime(ticks, DateTimeKind.Utc); + } + + /// Reads a generic list from the buffer. + public IList ReadBList(bool skipErrors = false) where T : ILegacySerializable, new() + { + int count = ReadInt32(); + if (count < 0) return null; + IList d = new List(count); + + SerializationReader sr = new SerializationReader(BaseStream); + + for (int i = 0; i < count; i++) + { + T obj = new T(); + try + { + obj.ReadFromStream(sr); + } + catch (Exception) + { + if (skipErrors) + continue; + throw; + } + + d.Add(obj); + } + + return d; + } + + /// Reads a generic list from the buffer. + public IList ReadList() + { + int count = ReadInt32(); + if (count < 0) return null; + IList d = new List(count); + for (int i = 0; i < count; i++) d.Add((T)ReadObject()); + return d; + } + + /// Reads a generic Dictionary from the buffer. + public IDictionary ReadDictionary() + { + int count = ReadInt32(); + if (count < 0) return null; + IDictionary d = new Dictionary(); + for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject(); + return d; + } + + /// Reads an object which was added to the buffer by WriteObject. + public object ReadObject() + { + ObjType t = (ObjType)ReadByte(); + switch (t) + { + case ObjType.boolType: + return ReadBoolean(); + case ObjType.byteType: + return ReadByte(); + case ObjType.uint16Type: + return ReadUInt16(); + case ObjType.uint32Type: + return ReadUInt32(); + case ObjType.uint64Type: + return ReadUInt64(); + case ObjType.sbyteType: + return ReadSByte(); + case ObjType.int16Type: + return ReadInt16(); + case ObjType.int32Type: + return ReadInt32(); + case ObjType.int64Type: + return ReadInt64(); + case ObjType.charType: + return ReadChar(); + case ObjType.stringType: + return base.ReadString(); + case ObjType.singleType: + return ReadSingle(); + case ObjType.doubleType: + return ReadDouble(); + case ObjType.decimalType: + return ReadDecimal(); + case ObjType.dateTimeType: + return ReadDateTime(); + case ObjType.byteArrayType: + return ReadByteArray(); + case ObjType.charArrayType: + return ReadCharArray(); + case ObjType.otherType: + return DynamicDeserializer.Deserialize(BaseStream); + default: + return null; + } + } + + public class DynamicDeserializer + { + private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder; + private static BinaryFormatter formatter; + + private static void initialize() + { + versionBinder = new VersionConfigToNamespaceAssemblyObjectBinder(); + formatter = new BinaryFormatter + { +// AssemblyFormat = FormatterAssemblyStyle.Simple, + Binder = versionBinder + }; + } + + public static object Deserialize(Stream stream) + { + if (formatter == null) + initialize(); + + Debug.Assert(formatter != null, "formatter != null"); + + // ReSharper disable once PossibleNullReferenceException + return formatter.Deserialize(stream); + } + + #region Nested type: VersionConfigToNamespaceAssemblyObjectBinder + + public sealed class VersionConfigToNamespaceAssemblyObjectBinder : SerializationBinder + { + private readonly Dictionary cache = new Dictionary(); + + public override Type BindToType(string assemblyName, string typeName) + { + Type typeToDeserialize; + + if (cache.TryGetValue(assemblyName + typeName, out typeToDeserialize)) + return typeToDeserialize; + + List tmpTypes = new List(); + Type genType = null; + + if (typeName.Contains("System.Collections.Generic") && typeName.Contains("[[")) + { + string[] splitTyps = typeName.Split('['); + + foreach (string typ in splitTyps) + { + if (typ.Contains("Version")) + { + string asmTmp = typ.Substring(typ.IndexOf(',') + 1); + string asmName = asmTmp.Remove(asmTmp.IndexOf(']')).Trim(); + string typName = typ.Remove(typ.IndexOf(',')); + tmpTypes.Add(BindToType(asmName, typName)); + } + else if (typ.Contains("Generic")) + { + genType = BindToType(assemblyName, typ); + } + } + if (genType != null && tmpTypes.Count > 0) + { + return genType.MakeGenericType(tmpTypes.ToArray()); + } + } + + string toAssemblyName = assemblyName.Split(',')[0]; + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly a in assemblies) + { + if (a.FullName.Split(',')[0] == toAssemblyName) + { + typeToDeserialize = a.GetType(typeName); + break; + } + } + + cache.Add(assemblyName + typeName, typeToDeserialize); + + return typeToDeserialize; + } + } + + #endregion + } + } + + public enum ObjType : byte + { + nullType, + boolType, + byteType, + uint16Type, + uint32Type, + uint64Type, + sbyteType, + int16Type, + int32Type, + int64Type, + charType, + stringType, + singleType, + doubleType, + decimalType, + dateTimeType, + byteArrayType, + charArrayType, + otherType, + ILegacySerializableType + } +} diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index f972796383..85728794af 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -1,262 +1,262 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -// ReSharper disable ConditionIsAlwaysTrueOrFalse (we're allowing nulls to be passed to the writer where the underlying class doesn't). -// ReSharper disable HeuristicUnreachableCode - -namespace osu.Game.IO.Legacy -{ - /// SerializationWriter. Extends BinaryWriter to add additional data types, - /// handle null strings and simplify use with ISerializable. - public class SerializationWriter : BinaryWriter - { - public SerializationWriter(Stream s) - : base(s, Encoding.UTF8) - { - } - - /// Static method to initialise the writer with a suitable MemoryStream. - public static SerializationWriter GetWriter() - { - MemoryStream ms = new MemoryStream(1024); - return new SerializationWriter(ms); - } - - /// Writes a string to the buffer. Overrides the base implementation so it can cope with nulls - public override void Write(string str) - { - if (str == null) - { - Write((byte)ObjType.nullType); - } - else - { - Write((byte)ObjType.stringType); - base.Write(str); - } - } - - /// Writes a byte array to the buffer. Overrides the base implementation to - /// send the length of the array which is needed when it is retrieved - public override void Write(byte[] b) - { - if (b == null) - { - Write(-1); - } - else - { - int len = b.Length; - Write(len); - if (len > 0) base.Write(b); - } - } - - /// Writes a char array to the buffer. Overrides the base implementation to - /// sends the length of the array which is needed when it is read. - public override void Write(char[] c) - { - if (c == null) - { - Write(-1); - } - else - { - int len = c.Length; - Write(len); - if (len > 0) base.Write(c); - } - } - - /// - /// Writes DateTime to the buffer. - /// - /// - public void Write(DateTime dt) - { - Write(dt.ToUniversalTime().Ticks); - } - - /// Writes a generic ICollection (such as an IList(T)) to the buffer. - public void Write(List c) where T : ILegacySerializable - { - if (c == null) - { - Write(-1); - } - else - { - int count = c.Count; - Write(count); - for (int i = 0; i < count; i++) - c[i].WriteToStream(this); - } - } - - /// Writes a generic IDictionary to the buffer. - public void Write(IDictionary d) - { - if (d == null) - { - Write(-1); - } - else - { - Write(d.Count); - foreach (KeyValuePair kvp in d) - { - WriteObject(kvp.Key); - WriteObject(kvp.Value); - } - } - } - - /// Writes an arbitrary object to the buffer. Useful where we have something of type "object" - /// and don't know how to treat it. This works out the best method to use to write to the buffer. - public void WriteObject(object obj) - { - if (obj == null) - { - Write((byte)ObjType.nullType); - } - else - { - switch (obj.GetType().Name) - { - case "Boolean": - Write((byte)ObjType.boolType); - Write((bool)obj); - break; - - case "Byte": - Write((byte)ObjType.byteType); - Write((byte)obj); - break; - - case "UInt16": - Write((byte)ObjType.uint16Type); - Write((ushort)obj); - break; - - case "UInt32": - Write((byte)ObjType.uint32Type); - Write((uint)obj); - break; - - case "UInt64": - Write((byte)ObjType.uint64Type); - Write((ulong)obj); - break; - - case "SByte": - Write((byte)ObjType.sbyteType); - Write((sbyte)obj); - break; - - case "Int16": - Write((byte)ObjType.int16Type); - Write((short)obj); - break; - - case "Int32": - Write((byte)ObjType.int32Type); - Write((int)obj); - break; - - case "Int64": - Write((byte)ObjType.int64Type); - Write((long)obj); - break; - - case "Char": - Write((byte)ObjType.charType); - base.Write((char)obj); - break; - - case "String": - Write((byte)ObjType.stringType); - base.Write((string)obj); - break; - - case "Single": - Write((byte)ObjType.singleType); - Write((float)obj); - break; - - case "Double": - Write((byte)ObjType.doubleType); - Write((double)obj); - break; - - case "Decimal": - Write((byte)ObjType.decimalType); - Write((decimal)obj); - break; - - case "DateTime": - Write((byte)ObjType.dateTimeType); - Write((DateTime)obj); - break; - - case "Byte[]": - Write((byte)ObjType.byteArrayType); - base.Write((byte[])obj); - break; - - case "Char[]": - Write((byte)ObjType.charArrayType); - base.Write((char[])obj); - break; - - default: - Write((byte)ObjType.otherType); - BinaryFormatter b = new BinaryFormatter - { -// AssemblyFormat = FormatterAssemblyStyle.Simple, - TypeFormat = FormatterTypeStyle.TypesWhenNeeded - }; - b.Serialize(BaseStream, obj); - break; - } // switch - } // if obj==null - } // WriteObject - - /// Adds the SerializationWriter buffer to the SerializationInfo at the end of GetObjectData(). - public void AddToInfo(SerializationInfo info) - { - byte[] b = ((MemoryStream)BaseStream).ToArray(); - info.AddValue("X", b, typeof(byte[])); - } - - public void WriteRawBytes(byte[] b) - { - base.Write(b); - } - - public void WriteByteArray(byte[] b) - { - if (b == null) - { - Write(-1); - } - else - { - int len = b.Length; - Write(len); - if (len > 0) base.Write(b); - } - } - - public void WriteUtf8(string str) - { - WriteRawBytes(Encoding.UTF8.GetBytes(str)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +// ReSharper disable ConditionIsAlwaysTrueOrFalse (we're allowing nulls to be passed to the writer where the underlying class doesn't). +// ReSharper disable HeuristicUnreachableCode + +namespace osu.Game.IO.Legacy +{ + /// SerializationWriter. Extends BinaryWriter to add additional data types, + /// handle null strings and simplify use with ISerializable. + public class SerializationWriter : BinaryWriter + { + public SerializationWriter(Stream s) + : base(s, Encoding.UTF8) + { + } + + /// Static method to initialise the writer with a suitable MemoryStream. + public static SerializationWriter GetWriter() + { + MemoryStream ms = new MemoryStream(1024); + return new SerializationWriter(ms); + } + + /// Writes a string to the buffer. Overrides the base implementation so it can cope with nulls + public override void Write(string str) + { + if (str == null) + { + Write((byte)ObjType.nullType); + } + else + { + Write((byte)ObjType.stringType); + base.Write(str); + } + } + + /// Writes a byte array to the buffer. Overrides the base implementation to + /// send the length of the array which is needed when it is retrieved + public override void Write(byte[] b) + { + if (b == null) + { + Write(-1); + } + else + { + int len = b.Length; + Write(len); + if (len > 0) base.Write(b); + } + } + + /// Writes a char array to the buffer. Overrides the base implementation to + /// sends the length of the array which is needed when it is read. + public override void Write(char[] c) + { + if (c == null) + { + Write(-1); + } + else + { + int len = c.Length; + Write(len); + if (len > 0) base.Write(c); + } + } + + /// + /// Writes DateTime to the buffer. + /// + /// + public void Write(DateTime dt) + { + Write(dt.ToUniversalTime().Ticks); + } + + /// Writes a generic ICollection (such as an IList(T)) to the buffer. + public void Write(List c) where T : ILegacySerializable + { + if (c == null) + { + Write(-1); + } + else + { + int count = c.Count; + Write(count); + for (int i = 0; i < count; i++) + c[i].WriteToStream(this); + } + } + + /// Writes a generic IDictionary to the buffer. + public void Write(IDictionary d) + { + if (d == null) + { + Write(-1); + } + else + { + Write(d.Count); + foreach (KeyValuePair kvp in d) + { + WriteObject(kvp.Key); + WriteObject(kvp.Value); + } + } + } + + /// Writes an arbitrary object to the buffer. Useful where we have something of type "object" + /// and don't know how to treat it. This works out the best method to use to write to the buffer. + public void WriteObject(object obj) + { + if (obj == null) + { + Write((byte)ObjType.nullType); + } + else + { + switch (obj.GetType().Name) + { + case "Boolean": + Write((byte)ObjType.boolType); + Write((bool)obj); + break; + + case "Byte": + Write((byte)ObjType.byteType); + Write((byte)obj); + break; + + case "UInt16": + Write((byte)ObjType.uint16Type); + Write((ushort)obj); + break; + + case "UInt32": + Write((byte)ObjType.uint32Type); + Write((uint)obj); + break; + + case "UInt64": + Write((byte)ObjType.uint64Type); + Write((ulong)obj); + break; + + case "SByte": + Write((byte)ObjType.sbyteType); + Write((sbyte)obj); + break; + + case "Int16": + Write((byte)ObjType.int16Type); + Write((short)obj); + break; + + case "Int32": + Write((byte)ObjType.int32Type); + Write((int)obj); + break; + + case "Int64": + Write((byte)ObjType.int64Type); + Write((long)obj); + break; + + case "Char": + Write((byte)ObjType.charType); + base.Write((char)obj); + break; + + case "String": + Write((byte)ObjType.stringType); + base.Write((string)obj); + break; + + case "Single": + Write((byte)ObjType.singleType); + Write((float)obj); + break; + + case "Double": + Write((byte)ObjType.doubleType); + Write((double)obj); + break; + + case "Decimal": + Write((byte)ObjType.decimalType); + Write((decimal)obj); + break; + + case "DateTime": + Write((byte)ObjType.dateTimeType); + Write((DateTime)obj); + break; + + case "Byte[]": + Write((byte)ObjType.byteArrayType); + base.Write((byte[])obj); + break; + + case "Char[]": + Write((byte)ObjType.charArrayType); + base.Write((char[])obj); + break; + + default: + Write((byte)ObjType.otherType); + BinaryFormatter b = new BinaryFormatter + { +// AssemblyFormat = FormatterAssemblyStyle.Simple, + TypeFormat = FormatterTypeStyle.TypesWhenNeeded + }; + b.Serialize(BaseStream, obj); + break; + } // switch + } // if obj==null + } // WriteObject + + /// Adds the SerializationWriter buffer to the SerializationInfo at the end of GetObjectData(). + public void AddToInfo(SerializationInfo info) + { + byte[] b = ((MemoryStream)BaseStream).ToArray(); + info.AddValue("X", b, typeof(byte[])); + } + + public void WriteRawBytes(byte[] b) + { + base.Write(b); + } + + public void WriteByteArray(byte[] b) + { + if (b == null) + { + Write(-1); + } + else + { + int len = b.Length; + Write(len); + if (len > 0) base.Write(b); + } + } + + public void WriteUtf8(string str) + { + WriteRawBytes(Encoding.UTF8.GetBytes(str)); + } + } +} diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index d1cf34629d..42d47cc1fb 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -1,100 +1,100 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace osu.Game.IO.Serialization.Converters -{ - /// - /// A type of that serializes a alongside - /// a lookup table for the types contained. The lookup table is used in deserialization to - /// reconstruct the objects with their original types. - /// - /// The type of objects contained in the this attribute is attached to. - public class TypedListConverter : JsonConverter - { - private readonly bool requiresTypeVersion; - - /// - /// Constructs a new . - /// - // ReSharper disable once UnusedMember.Global - public TypedListConverter() - { - } - - /// - /// Constructs a new . - /// - /// Whether the version of the type should be serialized. - // ReSharper disable once UnusedMember.Global (Used in Beatmap) - public TypedListConverter(bool requiresTypeVersion) - { - this.requiresTypeVersion = requiresTypeVersion; - } - - public override bool CanConvert(Type objectType) => objectType == typeof(List); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var list = new List(); - - var obj = JObject.Load(reader); - var lookupTable = serializer.Deserialize>(obj["lookup_table"].CreateReader()); - - foreach (var tok in obj["items"]) - { - var itemReader = tok.CreateReader(); - - var typeName = lookupTable[(int)tok["type"]]; - var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); - serializer.Populate(itemReader, instance); - - list.Add(instance); - } - - return list; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var list = (List)value; - - var lookupTable = new List(); - var objects = new List(); - foreach (var item in list) - { - var type = item.GetType(); - var assemblyName = type.Assembly.GetName(); - - var typeString = $"{type.FullName}, {assemblyName.Name}"; - if (requiresTypeVersion) - typeString += $", {assemblyName.Version}"; - - int typeId = lookupTable.IndexOf(typeString); - if (typeId == -1) - { - lookupTable.Add(typeString); - typeId = lookupTable.Count - 1; - } - - var itemObject = JObject.FromObject(item, serializer); - itemObject.AddFirst(new JProperty("type", typeId)); - objects.Add(itemObject); - } - - writer.WriteStartObject(); - - writer.WritePropertyName("lookup_table"); - serializer.Serialize(writer, lookupTable); - - writer.WritePropertyName("items"); - serializer.Serialize(writer, objects); - - writer.WriteEndObject(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.IO.Serialization.Converters +{ + /// + /// A type of that serializes a alongside + /// a lookup table for the types contained. The lookup table is used in deserialization to + /// reconstruct the objects with their original types. + /// + /// The type of objects contained in the this attribute is attached to. + public class TypedListConverter : JsonConverter + { + private readonly bool requiresTypeVersion; + + /// + /// Constructs a new . + /// + // ReSharper disable once UnusedMember.Global + public TypedListConverter() + { + } + + /// + /// Constructs a new . + /// + /// Whether the version of the type should be serialized. + // ReSharper disable once UnusedMember.Global (Used in Beatmap) + public TypedListConverter(bool requiresTypeVersion) + { + this.requiresTypeVersion = requiresTypeVersion; + } + + public override bool CanConvert(Type objectType) => objectType == typeof(List); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var list = new List(); + + var obj = JObject.Load(reader); + var lookupTable = serializer.Deserialize>(obj["lookup_table"].CreateReader()); + + foreach (var tok in obj["items"]) + { + var itemReader = tok.CreateReader(); + + var typeName = lookupTable[(int)tok["type"]]; + var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); + serializer.Populate(itemReader, instance); + + list.Add(instance); + } + + return list; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var list = (List)value; + + var lookupTable = new List(); + var objects = new List(); + foreach (var item in list) + { + var type = item.GetType(); + var assemblyName = type.Assembly.GetName(); + + var typeString = $"{type.FullName}, {assemblyName.Name}"; + if (requiresTypeVersion) + typeString += $", {assemblyName.Version}"; + + int typeId = lookupTable.IndexOf(typeString); + if (typeId == -1) + { + lookupTable.Add(typeString); + typeId = lookupTable.Count - 1; + } + + var itemObject = JObject.FromObject(item, serializer); + itemObject.AddFirst(new JProperty("type", typeId)); + objects.Add(itemObject); + } + + writer.WriteStartObject(); + + writer.WritePropertyName("lookup_table"); + serializer.Serialize(writer, lookupTable); + + writer.WritePropertyName("items"); + serializer.Serialize(writer, objects); + + writer.WriteEndObject(); + } + } +} diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs index 2cad4bc0fb..ba2158df28 100644 --- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs +++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OpenTK; - -namespace osu.Game.IO.Serialization.Converters -{ - /// - /// A type of that serializes only the X and Y coordinates of a . - /// - public class Vector2Converter : JsonConverter - { - public override bool CanConvert(Type objectType) => objectType == typeof(Vector2); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var obj = JObject.Load(reader); - return new Vector2((float)obj["x"], (float)obj["y"]); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var vector = (Vector2)value; - - writer.WriteStartObject(); - - writer.WritePropertyName("x"); - writer.WriteValue(vector.X); - writer.WritePropertyName("y"); - writer.WriteValue(vector.Y); - - writer.WriteEndObject(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OpenTK; + +namespace osu.Game.IO.Serialization.Converters +{ + /// + /// A type of that serializes only the X and Y coordinates of a . + /// + public class Vector2Converter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(Vector2); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JObject.Load(reader); + return new Vector2((float)obj["x"], (float)obj["y"]); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var vector = (Vector2)value; + + writer.WriteStartObject(); + + writer.WritePropertyName("x"); + writer.WriteValue(vector.X); + writer.WritePropertyName("y"); + writer.WriteValue(vector.Y); + + writer.WriteEndObject(); + } + } +} diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 9e3aaafc81..c9727725ef 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -1,37 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; -using osu.Game.IO.Serialization.Converters; - -namespace osu.Game.IO.Serialization -{ - public interface IJsonSerializable - { - } - - public static class JsonSerializableExtensions - { - public static string Serialize(this IJsonSerializable obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); - - public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); - - public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); - - public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj)); - - /// - /// Creates the default that should be used for all s. - /// - /// - public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, - Formatting = Formatting.Indented, - ObjectCreationHandling = ObjectCreationHandling.Replace, - DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, - Converters = new JsonConverter[] { new Vector2Converter() }, - ContractResolver = new KeyContractResolver() - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.IO.Serialization.Converters; + +namespace osu.Game.IO.Serialization +{ + public interface IJsonSerializable + { + } + + public static class JsonSerializableExtensions + { + public static string Serialize(this IJsonSerializable obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); + + public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); + + public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); + + public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj)); + + /// + /// Creates the default that should be used for all s. + /// + /// + public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + Formatting = Formatting.Indented, + ObjectCreationHandling = ObjectCreationHandling.Replace, + DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, + Converters = new JsonConverter[] { new Vector2Converter() }, + ContractResolver = new KeyContractResolver() + }; + } +} diff --git a/osu.Game/IO/Serialization/KeyContractResolver.cs b/osu.Game/IO/Serialization/KeyContractResolver.cs index a8d1313091..e2b470ab4a 100644 --- a/osu.Game/IO/Serialization/KeyContractResolver.cs +++ b/osu.Game/IO/Serialization/KeyContractResolver.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Humanizer; -using Newtonsoft.Json.Serialization; - -namespace osu.Game.IO.Serialization -{ - public class KeyContractResolver : DefaultContractResolver - { - protected override string ResolvePropertyName(string propertyName) - { - return propertyName.Underscore(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Humanizer; +using Newtonsoft.Json.Serialization; + +namespace osu.Game.IO.Serialization +{ + public class KeyContractResolver : DefaultContractResolver + { + protected override string ResolvePropertyName(string propertyName) + { + return propertyName.Underscore(); + } + } +} diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index 9d7bf17c77..6783b9712c 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -1,49 +1,49 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using osu.Framework.Platform; -using osu.Game.Database; - -namespace osu.Game.IPC -{ - public class ArchiveImportIPCChannel : IpcChannel - { - private readonly ICanAcceptFiles importer; - - public ArchiveImportIPCChannel(IIpcHost host, ICanAcceptFiles importer = null) - : base(host) - { - this.importer = importer; - MessageReceived += msg => - { - Debug.Assert(importer != null); - ImportAsync(msg.Path).ContinueWith(t => - { - if (t.Exception != null) throw t.Exception; - }, TaskContinuationOptions.OnlyOnFaulted); - }; - } - - public async Task ImportAsync(string path) - { - if (importer == null) - { - //we want to contact a remote osu! to handle the import. - await SendMessageAsync(new ArchiveImportMessage { Path = path }); - return; - } - - if (importer.HandledExtensions.Contains(Path.GetExtension(path))) - importer.Import(path); - } - } - - public class ArchiveImportMessage - { - public string Path; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.IPC +{ + public class ArchiveImportIPCChannel : IpcChannel + { + private readonly ICanAcceptFiles importer; + + public ArchiveImportIPCChannel(IIpcHost host, ICanAcceptFiles importer = null) + : base(host) + { + this.importer = importer; + MessageReceived += msg => + { + Debug.Assert(importer != null); + ImportAsync(msg.Path).ContinueWith(t => + { + if (t.Exception != null) throw t.Exception; + }, TaskContinuationOptions.OnlyOnFaulted); + }; + } + + public async Task ImportAsync(string path) + { + if (importer == null) + { + //we want to contact a remote osu! to handle the import. + await SendMessageAsync(new ArchiveImportMessage { Path = path }); + return; + } + + if (importer.HandledExtensions.Contains(Path.GetExtension(path))) + importer.Import(path); + } + } + + public class ArchiveImportMessage + { + public string Path; + } +} diff --git a/osu.Game/IPC/ScoreIPCChannel.cs b/osu.Game/IPC/ScoreIPCChannel.cs index 7f93656549..a66b8ce1f3 100644 --- a/osu.Game/IPC/ScoreIPCChannel.cs +++ b/osu.Game/IPC/ScoreIPCChannel.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Diagnostics; -using System.Threading.Tasks; -using osu.Framework.Platform; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.IPC -{ - public class ScoreIPCChannel : IpcChannel - { - private readonly ScoreStore scores; - - public ScoreIPCChannel(IIpcHost host, ScoreStore scores = null) - : base(host) - { - this.scores = scores; - MessageReceived += msg => - { - Debug.Assert(scores != null); - ImportAsync(msg.Path).ContinueWith(t => - { - if (t.Exception != null) throw t.Exception; - }, TaskContinuationOptions.OnlyOnFaulted); - }; - } - - public async Task ImportAsync(string path) - { - if (scores == null) - { - //we want to contact a remote osu! to handle the import. - await SendMessageAsync(new ScoreImportMessage { Path = path }); - return; - } - - scores.ReadReplayFile(path); - } - } - - public class ScoreImportMessage - { - public string Path; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Diagnostics; +using System.Threading.Tasks; +using osu.Framework.Platform; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.IPC +{ + public class ScoreIPCChannel : IpcChannel + { + private readonly ScoreStore scores; + + public ScoreIPCChannel(IIpcHost host, ScoreStore scores = null) + : base(host) + { + this.scores = scores; + MessageReceived += msg => + { + Debug.Assert(scores != null); + ImportAsync(msg.Path).ContinueWith(t => + { + if (t.Exception != null) throw t.Exception; + }, TaskContinuationOptions.OnlyOnFaulted); + }; + } + + public async Task ImportAsync(string path) + { + if (scores == null) + { + //we want to contact a remote osu! to handle the import. + await SendMessageAsync(new ScoreImportMessage { Path = path }); + return; + } + + scores.ReadReplayFile(path); + } + } + + public class ScoreImportMessage + { + public string Path; + } +} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs index d20adda8ed..7a230f32a0 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel.DataAnnotations.Schema; -using osu.Framework.Input.Bindings; -using osu.Game.Database; - -namespace osu.Game.Input.Bindings -{ - [Table("KeyBinding")] - public class DatabasedKeyBinding : KeyBinding, IHasPrimaryKey - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int ID { get; set; } - - public int? RulesetID { get; set; } - - public int? Variant { get; set; } - - [Column("Keys")] - public string KeysString - { - get { return KeyCombination.ToString(); } - private set { KeyCombination = value; } - } - - [Column("Action")] - public int IntAction - { - get { return (int)Action; } - set { Action = value; } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations.Schema; +using osu.Framework.Input.Bindings; +using osu.Game.Database; + +namespace osu.Game.Input.Bindings +{ + [Table("KeyBinding")] + public class DatabasedKeyBinding : KeyBinding, IHasPrimaryKey + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public int? RulesetID { get; set; } + + public int? Variant { get; set; } + + [Column("Keys")] + public string KeysString + { + get { return KeyCombination.ToString(); } + private set { KeyCombination = value; } + } + + [Column("Action")] + public int IntAction + { + get { return (int)Action; } + set { Action = value; } + } + } +} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 2b53f77d62..85a8ac7aa3 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -1,66 +1,66 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets; -using System.Linq; - -namespace osu.Game.Input.Bindings -{ - /// - /// A KeyBindingInputManager with a database backing for custom overrides. - /// - /// The type of the custom action. - public class DatabasedKeyBindingContainer : KeyBindingContainer - where T : struct - { - private readonly RulesetInfo ruleset; - - private readonly int? variant; - - private KeyBindingStore store; - - public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); - - /// - /// Create a new instance. - /// - /// A reference to identify the current . Used to lookup mappings. Null for global mappings. - /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. - /// Specify how to deal with multiple matches of s and s. - public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) - : base(simultaneousMode) - { - this.ruleset = ruleset; - this.variant = variant; - - if (ruleset != null && variant == null) - throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); - } - - [BackgroundDependencyLoader] - private void load(KeyBindingStore keyBindings) - { - store = keyBindings; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - store.KeyBindingChanged += ReloadMappings; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (store != null) - store.KeyBindingChanged -= ReloadMappings; - } - - protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets; +using System.Linq; + +namespace osu.Game.Input.Bindings +{ + /// + /// A KeyBindingInputManager with a database backing for custom overrides. + /// + /// The type of the custom action. + public class DatabasedKeyBindingContainer : KeyBindingContainer + where T : struct + { + private readonly RulesetInfo ruleset; + + private readonly int? variant; + + private KeyBindingStore store; + + public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); + + /// + /// Create a new instance. + /// + /// A reference to identify the current . Used to lookup mappings. Null for global mappings. + /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. + /// Specify how to deal with multiple matches of s and s. + public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) + : base(simultaneousMode) + { + this.ruleset = ruleset; + this.variant = variant; + + if (ruleset != null && variant == null) + throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); + } + + [BackgroundDependencyLoader] + private void load(KeyBindingStore keyBindings) + { + store = keyBindings; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + store.KeyBindingChanged += ReloadMappings; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (store != null) + store.KeyBindingChanged -= ReloadMappings; + } + + protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList(); + } +} diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 9e74a935ea..dd8f00f6cd 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -1,81 +1,81 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; - -namespace osu.Game.Input.Bindings -{ - public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalInput - { - private readonly Drawable handler; - - public GlobalActionContainer(OsuGameBase game) - { - if (game is IKeyBindingHandler) - handler = game; - } - - public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings); - - public IEnumerable GlobalKeyBindings => new[] - { - new KeyBinding(InputKey.F8, GlobalAction.ToggleChat), - new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial), - new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot), - - new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), - new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), - new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), - new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), - }; - - public IEnumerable InGameKeyBindings => new[] - { - new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), - new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry) - }; - - protected override IEnumerable KeyBindingInputQueue => - handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); - } - - public enum GlobalAction - { - [Description("Toggle chat overlay")] - ToggleChat, - [Description("Toggle social overlay")] - ToggleSocial, - [Description("Reset input settings")] - ResetInputSettings, - [Description("Toggle toolbar")] - ToggleToolbar, - [Description("Toggle settings")] - ToggleSettings, - [Description("Toggle osu!direct")] - ToggleDirect, - [Description("Increase Volume")] - IncreaseVolume, - [Description("Decrease Volume")] - DecreaseVolume, - [Description("Toggle mute")] - ToggleMute, - - // In-Game Keybindings - [Description("Skip Cutscene")] - SkipCutscene, - [Description("Quick Retry (Hold)")] - QuickRetry, - - [Description("Take screenshot")] - TakeScreenshot - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; + +namespace osu.Game.Input.Bindings +{ + public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalInput + { + private readonly Drawable handler; + + public GlobalActionContainer(OsuGameBase game) + { + if (game is IKeyBindingHandler) + handler = game; + } + + public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings); + + public IEnumerable GlobalKeyBindings => new[] + { + new KeyBinding(InputKey.F8, GlobalAction.ToggleChat), + new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial), + new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot), + + new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), + new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), + new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), + new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume), + new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume), + new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), + }; + + public IEnumerable InGameKeyBindings => new[] + { + new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), + new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry) + }; + + protected override IEnumerable KeyBindingInputQueue => + handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); + } + + public enum GlobalAction + { + [Description("Toggle chat overlay")] + ToggleChat, + [Description("Toggle social overlay")] + ToggleSocial, + [Description("Reset input settings")] + ResetInputSettings, + [Description("Toggle toolbar")] + ToggleToolbar, + [Description("Toggle settings")] + ToggleSettings, + [Description("Toggle osu!direct")] + ToggleDirect, + [Description("Increase Volume")] + IncreaseVolume, + [Description("Decrease Volume")] + DecreaseVolume, + [Description("Toggle mute")] + ToggleMute, + + // In-Game Keybindings + [Description("Skip Cutscene")] + SkipCutscene, + [Description("Quick Retry (Hold)")] + QuickRetry, + + [Description("Take screenshot")] + TakeScreenshot + } +} diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index c431af0219..5454dd0c9f 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -1,48 +1,48 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Input; -using osu.Framework.Input.Handlers; -using osu.Framework.Platform; -using OpenTK; - -namespace osu.Game.Input.Handlers -{ - public abstract class ReplayInputHandler : InputHandler - { - /// - /// A function that converts coordinates from gamefield to screen space. - /// - public Func GamefieldToScreenSpace { protected get; set; } - - /// - /// Update the current frame based on an incoming time value. - /// There are cases where we return a "must-use" time value that is different from the input. - /// This is to ensure accurate playback of replay data. - /// - /// The time which we should use for finding the current frame. - /// The usable time value. If null, we should not advance time as we do not have enough data. - public abstract double? SetFrameFromTime(double time); - - public override bool Initialize(GameHost host) => true; - - public override bool IsActive => true; - - public override int Priority => 0; - - public class ReplayState : InputState - where T : struct - { - public List PressedActions; - - public override InputState Clone() - { - var clone = (ReplayState)base.Clone(); - clone.PressedActions = new List(PressedActions); - return clone; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Input; +using osu.Framework.Input.Handlers; +using osu.Framework.Platform; +using OpenTK; + +namespace osu.Game.Input.Handlers +{ + public abstract class ReplayInputHandler : InputHandler + { + /// + /// A function that converts coordinates from gamefield to screen space. + /// + public Func GamefieldToScreenSpace { protected get; set; } + + /// + /// Update the current frame based on an incoming time value. + /// There are cases where we return a "must-use" time value that is different from the input. + /// This is to ensure accurate playback of replay data. + /// + /// The time which we should use for finding the current frame. + /// The usable time value. If null, we should not advance time as we do not have enough data. + public abstract double? SetFrameFromTime(double time); + + public override bool Initialize(GameHost host) => true; + + public override bool IsActive => true; + + public override int Priority => 0; + + public class ReplayState : InputState + where T : struct + { + public List PressedActions; + + public override InputState Clone() + { + var clone = (ReplayState)base.Clone(); + clone.PressedActions = new List(PressedActions); + return clone; + } + } + } +} diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 33cb0911a8..29fdabbbc2 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -1,86 +1,86 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Input.Bindings; -using osu.Framework.Platform; -using osu.Game.Database; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets; - -namespace osu.Game.Input -{ - public class KeyBindingStore : DatabaseBackedStore - { - public event Action KeyBindingChanged; - - public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) - : base(contextFactory, storage) - { - using (ContextFactory.GetForWrite()) - { - foreach (var info in rulesets.AvailableRulesets) - { - var ruleset = info.CreateInstance(); - foreach (var variant in ruleset.AvailableVariants) - insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); - } - } - } - - public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); - - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) - { - using (var usage = ContextFactory.GetForWrite()) - { - // compare counts in database vs defaults - foreach (var group in defaults.GroupBy(k => k.Action)) - { - int count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); - int aimCount = group.Count(); - - if (aimCount <= count) - continue; - - foreach (var insertable in group.Skip(count).Take(aimCount - count)) - // insert any defaults which are missing. - usage.Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding - { - KeyCombination = insertable.KeyCombination, - Action = insertable.Action, - RulesetID = rulesetId, - Variant = variant - }); - } - } - } - - /// - /// Retrieve s for a specified ruleset/variant content. - /// - /// The ruleset's internal ID. - /// An optional variant. - /// - public List Query(int? rulesetId = null, int? variant = null) => - ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); - - public void Update(KeyBinding keyBinding) - { - using (ContextFactory.GetForWrite()) - { - var dbKeyBinding = (DatabasedKeyBinding)keyBinding; - Refresh(ref dbKeyBinding); - - if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) - return; - - dbKeyBinding.KeyCombination = keyBinding.KeyCombination; - } - - KeyBindingChanged?.Invoke(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input.Bindings; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets; + +namespace osu.Game.Input +{ + public class KeyBindingStore : DatabaseBackedStore + { + public event Action KeyBindingChanged; + + public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null) + : base(contextFactory, storage) + { + using (ContextFactory.GetForWrite()) + { + foreach (var info in rulesets.AvailableRulesets) + { + var ruleset = info.CreateInstance(); + foreach (var variant in ruleset.AvailableVariants) + insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); + } + } + } + + public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); + + private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) + { + using (var usage = ContextFactory.GetForWrite()) + { + // compare counts in database vs defaults + foreach (var group in defaults.GroupBy(k => k.Action)) + { + int count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); + int aimCount = group.Count(); + + if (aimCount <= count) + continue; + + foreach (var insertable in group.Skip(count).Take(aimCount - count)) + // insert any defaults which are missing. + usage.Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding + { + KeyCombination = insertable.KeyCombination, + Action = insertable.Action, + RulesetID = rulesetId, + Variant = variant + }); + } + } + } + + /// + /// Retrieve s for a specified ruleset/variant content. + /// + /// The ruleset's internal ID. + /// An optional variant. + /// + public List Query(int? rulesetId = null, int? variant = null) => + ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + + public void Update(KeyBinding keyBinding) + { + using (ContextFactory.GetForWrite()) + { + var dbKeyBinding = (DatabasedKeyBinding)keyBinding; + Refresh(ref dbKeyBinding); + + if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination)) + return; + + dbKeyBinding.KeyCombination = keyBinding.KeyCombination; + } + + KeyBindingChanged?.Invoke(); + } + } +} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs index 0820041643..c751530bf4 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -1,293 +1,293 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171019041408_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash"); - - b.HasIndex("MetadataID"); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171019041408_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash"); + + b.HasIndex("MetadataID"); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs index 922aa85f18..9b6881f98c 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -1,311 +1,311 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class InitialCreate : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "BeatmapDifficulty", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ApproachRate = table.Column(type: "REAL", nullable: false), - CircleSize = table.Column(type: "REAL", nullable: false), - DrainRate = table.Column(type: "REAL", nullable: false), - OverallDifficulty = table.Column(type: "REAL", nullable: false), - SliderMultiplier = table.Column(type: "REAL", nullable: false), - SliderTickRate = table.Column(type: "REAL", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapMetadata", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Artist = table.Column(type: "TEXT", nullable: true), - ArtistUnicode = table.Column(type: "TEXT", nullable: true), - AudioFile = table.Column(type: "TEXT", nullable: true), - Author = table.Column(type: "TEXT", nullable: true), - BackgroundFile = table.Column(type: "TEXT", nullable: true), - PreviewTime = table.Column(type: "INTEGER", nullable: false), - Source = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - TitleUnicode = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "FileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Hash = table.Column(type: "TEXT", nullable: true), - ReferenceCount = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_FileInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "KeyBinding", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Action = table.Column(type: "INTEGER", nullable: false), - Keys = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: true), - Variant = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_KeyBinding", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "RulesetInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Available = table.Column(type: "INTEGER", nullable: false), - InstantiationInfo = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_RulesetInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapSetInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), - Protected = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "BeatmapInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AudioLeadIn = table.Column(type: "INTEGER", nullable: false), - BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), - BeatDivisor = table.Column(type: "INTEGER", nullable: false), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - Countdown = table.Column(type: "INTEGER", nullable: false), - DistanceSpacing = table.Column(type: "REAL", nullable: false), - GridSize = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Hidden = table.Column(type: "INTEGER", nullable: false), - LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), - MD5Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: false), - SpecialStyle = table.Column(type: "INTEGER", nullable: false), - StackLeniency = table.Column(type: "REAL", nullable: false), - StarDifficulty = table.Column(type: "REAL", nullable: false), - StoredBookmarks = table.Column(type: "TEXT", nullable: true), - TimelineZoom = table.Column(type: "REAL", nullable: false), - Version = table.Column(type: "TEXT", nullable: true), - WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", - column: x => x.BaseDifficultyID, - principalTable: "BeatmapDifficulty", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_BeatmapInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "BeatmapSetFileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - FileInfoID = table.Column(type: "INTEGER", nullable: false), - Filename = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_BaseDifficultyID", - table: "BeatmapInfo", - column: "BaseDifficultyID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_BeatmapSetInfoID", - table: "BeatmapInfo", - column: "BeatmapSetInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MetadataID", - table: "BeatmapInfo", - column: "MetadataID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_RulesetID", - table: "BeatmapInfo", - column: "RulesetID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", - table: "BeatmapSetFileInfo", - column: "BeatmapSetInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetFileInfo_FileInfoID", - table: "BeatmapSetFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_DeletePending", - table: "BeatmapSetInfo", - column: "DeletePending"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_MetadataID", - table: "BeatmapSetInfo", - column: "MetadataID"); - - migrationBuilder.CreateIndex( - name: "IX_FileInfo_Hash", - table: "FileInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_FileInfo_ReferenceCount", - table: "FileInfo", - column: "ReferenceCount"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Action", - table: "KeyBinding", - column: "Action"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding", - column: "Variant"); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_Available", - table: "RulesetInfo", - column: "Available"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BeatmapInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetFileInfo"); - - migrationBuilder.DropTable( - name: "KeyBinding"); - - migrationBuilder.DropTable( - name: "BeatmapDifficulty"); - - migrationBuilder.DropTable( - name: "RulesetInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetInfo"); - - migrationBuilder.DropTable( - name: "FileInfo"); - - migrationBuilder.DropTable( - name: "BeatmapMetadata"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BeatmapDifficulty", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApproachRate = table.Column(type: "REAL", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapMetadata", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "FileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Hash = table.Column(type: "TEXT", nullable: true), + ReferenceCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FileInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "KeyBinding", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Action = table.Column(type: "INTEGER", nullable: false), + Keys = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_KeyBinding", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "RulesetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Available = table.Column(type: "INTEGER", nullable: false), + InstantiationInfo = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RulesetInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", + column: x => x.BaseDifficultyID, + principalTable: "BeatmapDifficulty", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BeatmapSetFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BaseDifficultyID", + table: "BeatmapInfo", + column: "BaseDifficultyID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_BeatmapSetInfoID", + table: "BeatmapInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MetadataID", + table: "BeatmapInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_RulesetID", + table: "BeatmapInfo", + column: "RulesetID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", + table: "BeatmapSetFileInfo", + column: "BeatmapSetInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetFileInfo_FileInfoID", + table: "BeatmapSetFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_DeletePending", + table: "BeatmapSetInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_MetadataID", + table: "BeatmapSetInfo", + column: "MetadataID"); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_Hash", + table: "FileInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_FileInfo_ReferenceCount", + table: "FileInfo", + column: "ReferenceCount"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Action", + table: "KeyBinding", + column: "Action"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_Available", + table: "RulesetInfo", + column: "Available"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BeatmapInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetFileInfo"); + + migrationBuilder.DropTable( + name: "KeyBinding"); + + migrationBuilder.DropTable( + name: "BeatmapDifficulty"); + + migrationBuilder.DropTable( + name: "RulesetInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetInfo"); + + migrationBuilder.DropTable( + name: "FileInfo"); + + migrationBuilder.DropTable( + name: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs index 478f27e82c..4cd234f2ef 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -1,299 +1,299 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171025071459_AddMissingIndexRules")] - partial class AddMissingIndexRules - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171025071459_AddMissingIndexRules")] + partial class AddMissingIndexRules + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs index ad8884a4bf..c9fc59c5a2 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs @@ -1,80 +1,80 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddMissingIndexRules : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", - table: "BeatmapSetInfo", - column: "OnlineBeatmapSetID", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", - table: "BeatmapSetInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo"); - - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_Hash", - table: "BeatmapSetInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_Hash", - table: "BeatmapInfo", - column: "Hash"); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MD5Hash", - table: "BeatmapInfo", - column: "MD5Hash"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddMissingIndexRules : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo", + column: "OnlineBeatmapSetID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapSetInfo_OnlineBeatmapSetID", + table: "BeatmapSetInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs index b2f81a729a..006acf12cd 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -1,302 +1,302 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171119065731_AddBeatmapOnlineIDUniqueConstraint")] - partial class AddBeatmapOnlineIDUniqueConstraint - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171119065731_AddBeatmapOnlineIDUniqueConstraint")] + partial class AddBeatmapOnlineIDUniqueConstraint + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs index d3830ec0f5..66ebda785b 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; - -namespace osu.Game.Migrations -{ - public partial class AddBeatmapOnlineIDUniqueConstraint : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_OnlineBeatmapID", - table: "BeatmapInfo", - column: "OnlineBeatmapID", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BeatmapInfo_OnlineBeatmapID", - table: "BeatmapInfo"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class AddBeatmapOnlineIDUniqueConstraint : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo", + column: "OnlineBeatmapID", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_OnlineBeatmapID", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs index 6023e88994..fc2496bc24 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -1,307 +1,307 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20171209034410_AddRulesetInfoShortName")] - partial class AddRulesetInfoShortName - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20171209034410_AddRulesetInfoShortName")] + partial class AddRulesetInfoShortName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs index 94519c0b97..5227d1f8f9 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -1,35 +1,35 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; - -namespace osu.Game.Migrations -{ - public partial class AddRulesetInfoShortName : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ShortName", - table: "RulesetInfo", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_ShortName", - table: "RulesetInfo", - column: "ShortName", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_RulesetInfo_ShortName", - table: "RulesetInfo"); - - migrationBuilder.DropColumn( - name: "ShortName", - table: "RulesetInfo"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class AddRulesetInfoShortName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ShortName", + table: "RulesetInfo", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo", + column: "ShortName", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_RulesetInfo_ShortName", + table: "RulesetInfo"); + + migrationBuilder.DropColumn( + name: "ShortName", + table: "RulesetInfo"); + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs index 8e045abc6f..4bb599eec1 100644 --- a/osu.Game/Migrations/20180125143340_Settings.Designer.cs +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -1,329 +1,329 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180125143340_Settings")] - partial class Settings - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180125143340_Settings")] + partial class Settings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs index 86a6b2dc5e..20701d672a 100644 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -1,57 +1,57 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; - -namespace osu.Game.Migrations -{ - public partial class Settings : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding"); - - migrationBuilder.CreateTable( - name: "Settings", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Key = table.Column(type: "INTEGER", nullable: false), - RulesetID = table.Column(type: "INTEGER", nullable: true), - Value = table.Column(type: "TEXT", nullable: true), - Variant = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Settings", x => x.ID); - }); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_RulesetID_Variant", - table: "KeyBinding", - columns: new[] { "RulesetID", "Variant" }); - - migrationBuilder.CreateIndex( - name: "IX_Settings_RulesetID_Variant", - table: "Settings", - columns: new[] { "RulesetID", "Variant" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Settings"); - - migrationBuilder.DropIndex( - name: "IX_KeyBinding_RulesetID_Variant", - table: "KeyBinding"); - - migrationBuilder.CreateIndex( - name: "IX_KeyBinding_Variant", - table: "KeyBinding", - column: "Variant"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class Settings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateTable( + name: "Settings", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column(type: "INTEGER", nullable: false), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Value = table.Column(type: "TEXT", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Settings", x => x.ID); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding", + columns: new[] { "RulesetID", "Variant" }); + + migrationBuilder.CreateIndex( + name: "IX_Settings_RulesetID_Variant", + table: "Settings", + columns: new[] { "RulesetID", "Variant" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + } + } +} diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs index fc1f2fff55..374830ad93 100644 --- a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Infrastructure; -using osu.Game.Database; -using osu.Game.Input.Bindings; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180131154205_AddMuteBinding")] - public partial class AddMuteBinding : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); - migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using osu.Game.Database; +using osu.Game.Input.Bindings; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180131154205_AddMuteBinding")] + public partial class AddMuteBinding : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs index 83b8d6cf8a..cdc4ef2e66 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -1,379 +1,379 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using osu.Game.Database; -using System; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20180219060912_AddSkins")] - partial class AddSkins - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180219060912_AddSkins")] + partial class AddSkins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs index 741fcf4079..6cce4354d9 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -1,73 +1,73 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; - -namespace osu.Game.Migrations -{ - public partial class AddSkins : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "SkinInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Creator = table.Column(type: "TEXT", nullable: true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Name = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_SkinInfo", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "SkinFileInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - FileInfoID = table.Column(type: "INTEGER", nullable: false), - Filename = table.Column(type: "TEXT", nullable: false), - SkinInfoID = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SkinFileInfo", x => x.ID); - table.ForeignKey( - name: "FK_SkinFileInfo_FileInfo_FileInfoID", - column: x => x.FileInfoID, - principalTable: "FileInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", - column: x => x.SkinInfoID, - principalTable: "SkinInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_SkinFileInfo_FileInfoID", - table: "SkinFileInfo", - column: "FileInfoID"); - - migrationBuilder.CreateIndex( - name: "IX_SkinFileInfo_SkinInfoID", - table: "SkinFileInfo", - column: "SkinInfoID"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "SkinFileInfo"); - - migrationBuilder.DropTable( - name: "SkinInfo"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class AddSkins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SkinInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Creator = table.Column(type: "TEXT", nullable: true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "SkinFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false), + SkinInfoID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_SkinFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", + column: x => x.SkinInfoID, + principalTable: "SkinInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_FileInfoID", + table: "SkinFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_SkinInfoID", + table: "SkinFileInfo", + column: "SkinInfoID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SkinFileInfo"); + + migrationBuilder.DropTable( + name: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index b16b9fdefa..2abbe7785f 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,374 +1,374 @@ -// -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - partial class OsuDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MD5Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntKey") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + partial class OsuDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 798ed1b9b3..8c5fc58878 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -1,339 +1,334 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Logging; -using osu.Game.Configuration; -using osu.Game.Online.API.Requests; -using osu.Game.Users; - -namespace osu.Game.Online.API -{ - public class APIAccess : Component, IAPIProvider - { - private readonly OsuConfigManager config; - private readonly OAuth authentication; - - public string Endpoint = @"https://osu.ppy.sh"; - private const string client_id = @"5"; - private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; - - private ConcurrentQueue queue = new ConcurrentQueue(); - - /// - /// The username/email provided by the user when initiating a login. - /// - public string ProvidedUsername { get; private set; } - - private string password; - - public Bindable LocalUser { get; } = new Bindable(createGuestUser()); - - public string Token - { - get { return authentication.Token?.ToString(); } - set { authentication.Token = string.IsNullOrEmpty(value) ? null : OAuthToken.Parse(value); } - } - - protected bool HasLogin => Token != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password); - - private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); - - private readonly Logger log; - - public APIAccess(OsuConfigManager config) - { - this.config = config; - - authentication = new OAuth(client_id, client_secret, Endpoint); - log = Logger.GetLogger(LoggingTarget.Network); - - ProvidedUsername = config.Get(OsuSetting.Username); - Token = config.Get(OsuSetting.Token); - - Task.Factory.StartNew(run, cancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); - } - - private readonly List components = new List(); - - internal new void Schedule(Action action) => base.Schedule(action); - - public void Register(IOnlineComponent component) - { - Scheduler.Add(delegate - { - components.Add(component); - component.APIStateChanged(this, state); - }); - } - - public void Unregister(IOnlineComponent component) - { - Scheduler.Add(delegate - { - components.Remove(component); - }); - } - - public string AccessToken => authentication.RequestAccessToken(); - - /// - /// Number of consecutive requests which failed due to network issues. - /// - private int failureCount; - - private void run() - { - while (!cancellationToken.IsCancellationRequested) - { - switch (State) - { - case APIState.Failing: - //todo: replace this with a ping request. - log.Add(@"In a failing state, waiting a bit before we try again..."); - Thread.Sleep(5000); - if (queue.Count == 0) - { - log.Add(@"Queueing a ping request"); - Queue(new ListChannelsRequest { Timeout = 5000 }); - } - break; - case APIState.Offline: - case APIState.Connecting: - //work to restore a connection... - if (!HasLogin) - { - State = APIState.Offline; - Thread.Sleep(50); - continue; - } - - State = APIState.Connecting; - - // save the username at this point, if the user requested for it to be. - config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); - - if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password)) - { - //todo: this fails even on network-related issues. we should probably handle those differently. - //NotificationOverlay.ShowMessage("Login failed!"); - log.Add(@"Login failed!"); - password = null; - authentication.Clear(); - continue; - } - - var userReq = new GetUserRequest(); - userReq.Success += u => - { - LocalUser.Value = u; - failureCount = 0; - - //we're connected! - State = APIState.Online; - }; - - if (!handleRequest(userReq)) - { - Thread.Sleep(500); - continue; - } - - // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. - // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests - // before actually going online. - while (State != APIState.Online) - Thread.Sleep(500); - - break; - } - - //hard bail if we can't get a valid access token. - if (authentication.RequestAccessToken() == null) - { - Logout(false); - State = APIState.Offline; - continue; - } - - //process the request queue. - APIRequest req; - while (queue.TryPeek(out req)) - { - if (handleRequest(req)) - { - //we have succeeded, so let's unqueue. - queue.TryDequeue(out req); - } - } - - Thread.Sleep(1); - } - } - - public void Login(string username, string password) - { - Debug.Assert(State == APIState.Offline); - - ProvidedUsername = username; - this.password = password; - } - - /// - /// Handle a single API request. - /// - /// The request. - /// true if we should remove this request from the queue. - private bool handleRequest(APIRequest req) - { - try - { - Logger.Log($@"Performing request {req}", LoggingTarget.Network); - req.Perform(this); - - //we could still be in initialisation, at which point we don't want to say we're Online yet. - if (IsLoggedIn) - State = APIState.Online; - - failureCount = 0; - return true; - } - catch (WebException we) - { - HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); - - switch (statusCode) - { - case HttpStatusCode.Unauthorized: - Logout(false); - return true; - case HttpStatusCode.RequestTimeout: - failureCount++; - log.Add($@"API failure count is now {failureCount}"); - - if (failureCount < 3) - //we might try again at an api level. - return false; - - State = APIState.Failing; - flushQueue(); - return true; - } - - req.Fail(we); - return true; - } - catch (Exception e) - { - if (e is TimeoutException) - log.Add(@"API level timeout exception was hit"); - - req.Fail(e); - return true; - } - } - - private APIState state; - public APIState State - { - get { return state; } - private set - { - APIState oldState = state; - APIState newState = value; - - state = value; - - if (oldState != newState) - { - log.Add($@"We just went {newState}!"); - Scheduler.Add(delegate - { - components.ForEach(c => c.APIStateChanged(this, newState)); - OnStateChange?.Invoke(oldState, newState); - }); - } - } - } - - public bool IsLoggedIn => LocalUser.Value.Id > 1; - - public void Queue(APIRequest request) => queue.Enqueue(request); - - public event StateChangeDelegate OnStateChange; - - public delegate void StateChangeDelegate(APIState oldState, APIState newState); - - private void flushQueue(bool failOldRequests = true) - { - var oldQueue = queue; - - //flush the queue. - queue = new ConcurrentQueue(); - - if (failOldRequests) - { - APIRequest req; - while (oldQueue.TryDequeue(out req)) - req.Fail(new WebException(@"Disconnected from server")); - } - } - - public void Logout(bool clearUsername = true) - { - flushQueue(); - if (clearUsername) ProvidedUsername = null; - password = null; - authentication.Clear(); - LocalUser.Value = createGuestUser(); - } - - private static User createGuestUser() => new User - { - Username = @"Guest", - Id = 1, - }; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? Token : string.Empty); - config.Save(); - - flushQueue(); - cancellationToken.Cancel(); - } - } - - public enum APIState - { - /// - /// We cannot login (not enough credentials). - /// - Offline, - - /// - /// We are having connectivity issues. - /// - Failing, - - /// - /// We are in the process of (re-)connecting. - /// - Connecting, - - /// - /// We are online. - /// - Online - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Configuration; +using osu.Game.Online.API.Requests; +using osu.Game.Users; + +namespace osu.Game.Online.API +{ + public class APIAccess : Component, IAPIProvider + { + private readonly OsuConfigManager config; + private readonly OAuth authentication; + + public string Endpoint = @"https://osu.ppy.sh"; + private const string client_id = @"5"; + private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; + + private ConcurrentQueue queue = new ConcurrentQueue(); + + /// + /// The username/email provided by the user when initiating a login. + /// + public string ProvidedUsername { get; private set; } + + private string password; + + public Bindable LocalUser { get; } = new Bindable(createGuestUser()); + + protected bool HasLogin => authentication.Token.Value != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password); + + private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); + + private readonly Logger log; + + public APIAccess(OsuConfigManager config) + { + this.config = config; + + authentication = new OAuth(client_id, client_secret, Endpoint); + log = Logger.GetLogger(LoggingTarget.Network); + + ProvidedUsername = config.Get(OsuSetting.Username); + + authentication.TokenString = config.Get(OsuSetting.Token); + authentication.Token.ValueChanged += onTokenChanged; + + Task.Factory.StartNew(run, cancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + + private void onTokenChanged(OAuthToken token) => config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); + + private readonly List components = new List(); + + internal new void Schedule(Action action) => base.Schedule(action); + + public void Register(IOnlineComponent component) + { + Scheduler.Add(delegate + { + components.Add(component); + component.APIStateChanged(this, state); + }); + } + + public void Unregister(IOnlineComponent component) + { + Scheduler.Add(delegate + { + components.Remove(component); + }); + } + + public string AccessToken => authentication.RequestAccessToken(); + + /// + /// Number of consecutive requests which failed due to network issues. + /// + private int failureCount; + + private void run() + { + while (!cancellationToken.IsCancellationRequested) + { + switch (State) + { + case APIState.Failing: + //todo: replace this with a ping request. + log.Add(@"In a failing state, waiting a bit before we try again..."); + Thread.Sleep(5000); + if (queue.Count == 0) + { + log.Add(@"Queueing a ping request"); + Queue(new ListChannelsRequest { Timeout = 5000 }); + } + break; + case APIState.Offline: + case APIState.Connecting: + //work to restore a connection... + if (!HasLogin) + { + State = APIState.Offline; + Thread.Sleep(50); + continue; + } + + State = APIState.Connecting; + + // save the username at this point, if the user requested for it to be. + config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); + + if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password)) + { + //todo: this fails even on network-related issues. we should probably handle those differently. + //NotificationOverlay.ShowMessage("Login failed!"); + log.Add(@"Login failed!"); + password = null; + authentication.Clear(); + continue; + } + + var userReq = new GetUserRequest(); + userReq.Success += u => + { + LocalUser.Value = u; + failureCount = 0; + + //we're connected! + State = APIState.Online; + }; + + if (!handleRequest(userReq)) + { + Thread.Sleep(500); + continue; + } + + // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. + // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests + // before actually going online. + while (State != APIState.Online) + Thread.Sleep(500); + + break; + } + + //hard bail if we can't get a valid access token. + if (authentication.RequestAccessToken() == null) + { + Logout(false); + State = APIState.Offline; + continue; + } + + //process the request queue. + APIRequest req; + while (queue.TryPeek(out req)) + { + if (handleRequest(req)) + { + //we have succeeded, so let's unqueue. + queue.TryDequeue(out req); + } + } + + Thread.Sleep(1); + } + } + + public void Login(string username, string password) + { + Debug.Assert(State == APIState.Offline); + + ProvidedUsername = username; + this.password = password; + } + + /// + /// Handle a single API request. + /// + /// The request. + /// true if we should remove this request from the queue. + private bool handleRequest(APIRequest req) + { + try + { + Logger.Log($@"Performing request {req}", LoggingTarget.Network); + req.Perform(this); + + //we could still be in initialisation, at which point we don't want to say we're Online yet. + if (IsLoggedIn) + State = APIState.Online; + + failureCount = 0; + return true; + } + catch (WebException we) + { + HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); + + switch (statusCode) + { + case HttpStatusCode.Unauthorized: + Logout(false); + return true; + case HttpStatusCode.RequestTimeout: + failureCount++; + log.Add($@"API failure count is now {failureCount}"); + + if (failureCount < 3) + //we might try again at an api level. + return false; + + State = APIState.Failing; + flushQueue(); + return true; + } + + req.Fail(we); + return true; + } + catch (Exception e) + { + if (e is TimeoutException) + log.Add(@"API level timeout exception was hit"); + + req.Fail(e); + return true; + } + } + + private APIState state; + public APIState State + { + get { return state; } + private set + { + APIState oldState = state; + APIState newState = value; + + state = value; + + if (oldState != newState) + { + log.Add($@"We just went {newState}!"); + Scheduler.Add(delegate + { + components.ForEach(c => c.APIStateChanged(this, newState)); + OnStateChange?.Invoke(oldState, newState); + }); + } + } + } + + public bool IsLoggedIn => LocalUser.Value.Id > 1; + + public void Queue(APIRequest request) => queue.Enqueue(request); + + public event StateChangeDelegate OnStateChange; + + public delegate void StateChangeDelegate(APIState oldState, APIState newState); + + private void flushQueue(bool failOldRequests = true) + { + var oldQueue = queue; + + //flush the queue. + queue = new ConcurrentQueue(); + + if (failOldRequests) + { + APIRequest req; + while (oldQueue.TryDequeue(out req)) + req.Fail(new WebException(@"Disconnected from server")); + } + } + + public void Logout(bool clearUsername = true) + { + flushQueue(); + if (clearUsername) ProvidedUsername = null; + password = null; + authentication.Clear(); + LocalUser.Value = createGuestUser(); + } + + private static User createGuestUser() => new User + { + Username = @"Guest", + Id = 1, + }; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + flushQueue(); + cancellationToken.Cancel(); + } + } + + public enum APIState + { + /// + /// We cannot login (not enough credentials). + /// + Offline, + + /// + /// We are having connectivity issues. + /// + Failing, + + /// + /// We are in the process of (re-)connecting. + /// + Connecting, + + /// + /// We are online. + /// + Online + } +} diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 0a5210723d..86558faf3b 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.IO.Network; - -namespace osu.Game.Online.API -{ - public abstract class APIDownloadRequest : APIRequest - { - protected override WebRequest CreateWebRequest() - { - var request = new WebRequest(Uri); - request.DownloadProgress += request_Progress; - return request; - } - - private void request_Progress(long current, long total) => API.Schedule(() => Progress?.Invoke(current, total)); - - protected APIDownloadRequest() - { - base.Success += onSuccess; - } - - private void onSuccess() - { - Success?.Invoke(WebRequest.ResponseData); - } - - public event APIProgressHandler Progress; - - public new event APISuccessHandler Success; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API +{ + public abstract class APIDownloadRequest : APIRequest + { + protected override WebRequest CreateWebRequest() + { + var request = new WebRequest(Uri); + request.DownloadProgress += request_Progress; + return request; + } + + private void request_Progress(long current, long total) => API.Schedule(() => Progress?.Invoke(current, total)); + + protected APIDownloadRequest() + { + base.Success += onSuccess; + } + + private void onSuccess() + { + Success?.Invoke(WebRequest.ResponseData); + } + + public event APIProgressHandler Progress; + + public new event APISuccessHandler Success; + } +} diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 4b05df661b..9af142b9e8 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -1,121 +1,121 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.IO.Network; - -namespace osu.Game.Online.API -{ - /// - /// An API request with a well-defined response type. - /// - /// Type of the response (used for deserialisation). - public abstract class APIRequest : APIRequest - { - protected override WebRequest CreateWebRequest() => new JsonWebRequest(Uri); - - protected APIRequest() - { - base.Success += onSuccess; - } - - private void onSuccess() - { - Success?.Invoke(((JsonWebRequest)WebRequest).ResponseObject); - } - - public new event APISuccessHandler Success; - } - - /// - /// AN API request with no specified response type. - /// - public abstract class APIRequest - { - /// - /// The maximum amount of time before this request will fail. - /// - public int Timeout = WebRequest.DEFAULT_TIMEOUT; - - protected virtual string Target => string.Empty; - - protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri); - - protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}"; - - private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds); - - public bool ExceededTimeout => remainingTime == 0; - - private DateTimeOffset? startTime; - - protected APIAccess API; - protected WebRequest WebRequest; - - public event APISuccessHandler Success; - public event APIFailureHandler Failure; - - private bool cancelled; - - private Action pendingFailure; - - public void Perform(APIAccess api) - { - API = api; - - if (checkAndProcessFailure()) - return; - - if (startTime == null) - startTime = DateTimeOffset.UtcNow; - - if (remainingTime <= 0) - throw new TimeoutException(@"API request timeout hit"); - - WebRequest = CreateWebRequest(); - WebRequest.AllowRetryOnTimeout = false; - WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); - - if (checkAndProcessFailure()) - return; - - if (!WebRequest.Aborted) //could have been aborted by a Cancel() call - WebRequest.Perform(); - - if (checkAndProcessFailure()) - return; - - api.Schedule(delegate { Success?.Invoke(); }); - } - - public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); - - public void Fail(Exception e) - { - cancelled = true; - - WebRequest?.Abort(); - - pendingFailure = () => Failure?.Invoke(e); - checkAndProcessFailure(); - } - - /// - /// Checked for cancellation or error. Also queues up the Failed event if we can. - /// - /// Whether we are in a failed or cancelled state. - private bool checkAndProcessFailure() - { - if (API == null || pendingFailure == null) return cancelled; - - API.Schedule(pendingFailure); - pendingFailure = null; - return true; - } - } - - public delegate void APIFailureHandler(Exception e); - public delegate void APISuccessHandler(); - public delegate void APIProgressHandler(long current, long total); - public delegate void APISuccessHandler(T content); -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API +{ + /// + /// An API request with a well-defined response type. + /// + /// Type of the response (used for deserialisation). + public abstract class APIRequest : APIRequest + { + protected override WebRequest CreateWebRequest() => new JsonWebRequest(Uri); + + protected APIRequest() + { + base.Success += onSuccess; + } + + private void onSuccess() + { + Success?.Invoke(((JsonWebRequest)WebRequest).ResponseObject); + } + + public new event APISuccessHandler Success; + } + + /// + /// AN API request with no specified response type. + /// + public abstract class APIRequest + { + /// + /// The maximum amount of time before this request will fail. + /// + public int Timeout = WebRequest.DEFAULT_TIMEOUT; + + protected virtual string Target => string.Empty; + + protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri); + + protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}"; + + private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds); + + public bool ExceededTimeout => remainingTime == 0; + + private DateTimeOffset? startTime; + + protected APIAccess API; + protected WebRequest WebRequest; + + public event APISuccessHandler Success; + public event APIFailureHandler Failure; + + private bool cancelled; + + private Action pendingFailure; + + public void Perform(APIAccess api) + { + API = api; + + if (checkAndProcessFailure()) + return; + + if (startTime == null) + startTime = DateTimeOffset.UtcNow; + + if (remainingTime <= 0) + throw new TimeoutException(@"API request timeout hit"); + + WebRequest = CreateWebRequest(); + WebRequest.AllowRetryOnTimeout = false; + WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); + + if (checkAndProcessFailure()) + return; + + if (!WebRequest.Aborted) //could have been aborted by a Cancel() call + WebRequest.Perform(); + + if (checkAndProcessFailure()) + return; + + api.Schedule(delegate { Success?.Invoke(); }); + } + + public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); + + public void Fail(Exception e) + { + cancelled = true; + + WebRequest?.Abort(); + + pendingFailure = () => Failure?.Invoke(e); + checkAndProcessFailure(); + } + + /// + /// Checked for cancellation or error. Also queues up the Failed event if we can. + /// + /// Whether we are in a failed or cancelled state. + private bool checkAndProcessFailure() + { + if (API == null || pendingFailure == null) return cancelled; + + API.Schedule(pendingFailure); + pendingFailure = null; + return true; + } + } + + public delegate void APIFailureHandler(Exception e); + public delegate void APISuccessHandler(); + public delegate void APIProgressHandler(long current, long total); + public delegate void APISuccessHandler(T content); +} diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index fc0dc0ef8b..f99f184d5c 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Game.Users; - -namespace osu.Game.Online.API -{ - public class DummyAPIAccess : IAPIProvider - { - public Bindable LocalUser { get; } = new Bindable(new User - { - Username = @"Dummy", - Id = 1, - }); - - public bool IsLoggedIn => true; - - public void Update() - { - } - - public virtual void Queue(APIRequest request) - { - } - - public void Register(IOnlineComponent component) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Game.Users; + +namespace osu.Game.Online.API +{ + public class DummyAPIAccess : IAPIProvider + { + public Bindable LocalUser { get; } = new Bindable(new User + { + Username = @"Dummy", + Id = 1, + }); + + public bool IsLoggedIn => true; + + public void Update() + { + } + + public virtual void Queue(APIRequest request) + { + } + + public void Register(IOnlineComponent component) + { + } + } +} diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 4119691c85..a3fc9a699c 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Game.Users; - -namespace osu.Game.Online.API -{ - public interface IAPIProvider - { - /// - /// The local user. - /// - Bindable LocalUser { get; } - - /// - /// Returns whether the local user is logged in. - /// - bool IsLoggedIn { get; } - - /// - /// Queue a new request. - /// - /// The request to perform. - void Queue(APIRequest request); - - /// - /// Register a component to receive state changes. - /// - /// The component to register. - void Register(IOnlineComponent component); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Game.Users; + +namespace osu.Game.Online.API +{ + public interface IAPIProvider + { + /// + /// The local user. + /// + Bindable LocalUser { get; } + + /// + /// Returns whether the local user is logged in. + /// + bool IsLoggedIn { get; } + + /// + /// Queue a new request. + /// + /// The request to perform. + void Queue(APIRequest request); + + /// + /// Register a component to receive state changes. + /// + /// The component to register. + void Register(IOnlineComponent component); + } +} diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs index a3a49b42ae..5ca6143428 100644 --- a/osu.Game/Online/API/IOnlineComponent.cs +++ b/osu.Game/Online/API/IOnlineComponent.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Online.API -{ - public interface IOnlineComponent - { - void APIStateChanged(APIAccess api, APIState state); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Online.API +{ + public interface IOnlineComponent + { + void APIStateChanged(APIAccess api, APIState state); + } +} diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index df8af7b8dc..67b908e894 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -1,178 +1,185 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Diagnostics; -using osu.Framework.IO.Network; - -namespace osu.Game.Online.API -{ - public class OAuth - { - private readonly string clientId; - private readonly string clientSecret; - private readonly string endpoint; - - public OAuthToken Token; - - internal OAuth(string clientId, string clientSecret, string endpoint) - { - Debug.Assert(clientId != null); - Debug.Assert(clientSecret != null); - Debug.Assert(endpoint != null); - - this.clientId = clientId; - this.clientSecret = clientSecret; - this.endpoint = endpoint; - } - - internal bool AuthenticateWithLogin(string username, string password) - { - if (string.IsNullOrEmpty(username)) return false; - if (string.IsNullOrEmpty(password)) return false; - - using (var req = new AccessTokenRequestPassword(username, password) - { - Url = $@"{endpoint}/oauth/token", - Method = HttpMethod.POST, - ClientId = clientId, - ClientSecret = clientSecret - }) - { - try - { - req.Perform(); - } - catch - { - return false; - } - - Token = req.ResponseObject; - return true; - } - } - - internal bool AuthenticateWithRefresh(string refresh) - { - try - { - using (var req = new AccessTokenRequestRefresh(refresh) - { - Url = $@"{endpoint}/oauth/token", - Method = HttpMethod.POST, - ClientId = clientId, - ClientSecret = clientSecret - }) - { - req.Perform(); - - Token = req.ResponseObject; - return true; - } - } - catch - { - //todo: potentially only kill the refresh token on certain exception types. - Token = null; - return false; - } - } - - private static readonly object access_token_retrieval_lock = new object(); - - /// - /// Should be run before any API request to make sure we have a valid key. - /// - private bool ensureAccessToken() - { - // if we already have a valid access token, let's use it. - if (accessTokenValid) return true; - - // we want to ensure only a single authentication update is happening at once. - lock (access_token_retrieval_lock) - { - // re-check if valid, in case another request completed and revalidated our access. - if (accessTokenValid) return true; - - // if not, let's try using our refresh token to request a new access token. - if (!string.IsNullOrEmpty(Token?.RefreshToken)) - // ReSharper disable once PossibleNullReferenceException - AuthenticateWithRefresh(Token.RefreshToken); - - return accessTokenValid; - } - } - - private bool accessTokenValid => Token?.IsValid ?? false; - - internal bool HasValidAccessToken => RequestAccessToken() != null; - - internal string RequestAccessToken() - { - if (!ensureAccessToken()) return null; - - return Token.AccessToken; - } - - internal void Clear() - { - Token = null; - } - - private class AccessTokenRequestRefresh : AccessTokenRequest - { - internal readonly string RefreshToken; - - internal AccessTokenRequestRefresh(string refreshToken) - { - RefreshToken = refreshToken; - GrantType = @"refresh_token"; - } - - protected override void PrePerform() - { - AddParameter("refresh_token", RefreshToken); - - base.PrePerform(); - } - } - - private class AccessTokenRequestPassword : AccessTokenRequest - { - internal readonly string Username; - internal readonly string Password; - - internal AccessTokenRequestPassword(string username, string password) - { - Username = username; - Password = password; - GrantType = @"password"; - } - - protected override void PrePerform() - { - AddParameter("username", Username); - AddParameter("password", Password); - - base.PrePerform(); - } - } - - private class AccessTokenRequest : JsonWebRequest - { - protected string GrantType; - - internal string ClientId; - internal string ClientSecret; - - protected override void PrePerform() - { - AddParameter("grant_type", GrantType); - AddParameter("client_id", ClientId); - AddParameter("client_secret", ClientSecret); - - base.PrePerform(); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Diagnostics; +using osu.Framework.Configuration; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API +{ + public class OAuth + { + private readonly string clientId; + private readonly string clientSecret; + private readonly string endpoint; + + public readonly Bindable Token = new Bindable(); + + public string TokenString + { + get => Token.Value?.ToString(); + set => Token.Value = string.IsNullOrEmpty(value) ? null : OAuthToken.Parse(value); + } + + internal OAuth(string clientId, string clientSecret, string endpoint) + { + Debug.Assert(clientId != null); + Debug.Assert(clientSecret != null); + Debug.Assert(endpoint != null); + + this.clientId = clientId; + this.clientSecret = clientSecret; + this.endpoint = endpoint; + } + + internal bool AuthenticateWithLogin(string username, string password) + { + if (string.IsNullOrEmpty(username)) return false; + if (string.IsNullOrEmpty(password)) return false; + + using (var req = new AccessTokenRequestPassword(username, password) + { + Url = $@"{endpoint}/oauth/token", + Method = HttpMethod.POST, + ClientId = clientId, + ClientSecret = clientSecret + }) + { + try + { + req.Perform(); + } + catch + { + return false; + } + + Token.Value = req.ResponseObject; + return true; + } + } + + internal bool AuthenticateWithRefresh(string refresh) + { + try + { + using (var req = new AccessTokenRequestRefresh(refresh) + { + Url = $@"{endpoint}/oauth/token", + Method = HttpMethod.POST, + ClientId = clientId, + ClientSecret = clientSecret + }) + { + req.Perform(); + + Token.Value = req.ResponseObject; + return true; + } + } + catch + { + //todo: potentially only kill the refresh token on certain exception types. + Token.Value = null; + return false; + } + } + + private static readonly object access_token_retrieval_lock = new object(); + + /// + /// Should be run before any API request to make sure we have a valid key. + /// + private bool ensureAccessToken() + { + // if we already have a valid access token, let's use it. + if (accessTokenValid) return true; + + // we want to ensure only a single authentication update is happening at once. + lock (access_token_retrieval_lock) + { + // re-check if valid, in case another request completed and revalidated our access. + if (accessTokenValid) return true; + + // if not, let's try using our refresh token to request a new access token. + if (!string.IsNullOrEmpty(Token.Value?.RefreshToken)) + // ReSharper disable once PossibleNullReferenceException + AuthenticateWithRefresh(Token.Value.RefreshToken); + + return accessTokenValid; + } + } + + private bool accessTokenValid => Token.Value?.IsValid ?? false; + + internal bool HasValidAccessToken => RequestAccessToken() != null; + + internal string RequestAccessToken() + { + if (!ensureAccessToken()) return null; + + return Token.Value.AccessToken; + } + + internal void Clear() + { + Token.Value = null; + } + + private class AccessTokenRequestRefresh : AccessTokenRequest + { + internal readonly string RefreshToken; + + internal AccessTokenRequestRefresh(string refreshToken) + { + RefreshToken = refreshToken; + GrantType = @"refresh_token"; + } + + protected override void PrePerform() + { + AddParameter("refresh_token", RefreshToken); + + base.PrePerform(); + } + } + + private class AccessTokenRequestPassword : AccessTokenRequest + { + internal readonly string Username; + internal readonly string Password; + + internal AccessTokenRequestPassword(string username, string password) + { + Username = username; + Password = password; + GrantType = @"password"; + } + + protected override void PrePerform() + { + AddParameter("username", Username); + AddParameter("password", Password); + + base.PrePerform(); + } + } + + private class AccessTokenRequest : JsonWebRequest + { + protected string GrantType; + + internal string ClientId; + internal string ClientSecret; + + protected override void PrePerform() + { + AddParameter("grant_type", GrantType); + AddParameter("client_id", ClientId); + AddParameter("client_secret", ClientSecret); + + base.PrePerform(); + } + } + } +} diff --git a/osu.Game/Online/API/OAuthToken.cs b/osu.Game/Online/API/OAuthToken.cs index d2b9703a93..b9458583f1 100644 --- a/osu.Game/Online/API/OAuthToken.cs +++ b/osu.Game/Online/API/OAuthToken.cs @@ -1,63 +1,63 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Globalization; -using Newtonsoft.Json; - -namespace osu.Game.Online.API -{ - [Serializable] - public class OAuthToken - { - /// - /// OAuth 2.0 access token. - /// - [JsonProperty(@"access_token")] - public string AccessToken; - - [JsonProperty(@"expires_in")] - public long ExpiresIn - { - get - { - return AccessTokenExpiry - DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - } - - set - { - AccessTokenExpiry = DateTimeOffset.Now.AddSeconds(value).ToUnixTimeSeconds(); - } - } - - public bool IsValid => !string.IsNullOrEmpty(AccessToken) && ExpiresIn > 30; - - public long AccessTokenExpiry; - - /// - /// OAuth 2.0 refresh token. - /// - [JsonProperty(@"refresh_token")] - public string RefreshToken; - - public override string ToString() => $@"{AccessToken}|{AccessTokenExpiry.ToString(NumberFormatInfo.InvariantInfo)}|{RefreshToken}"; - - public static OAuthToken Parse(string value) - { - try - { - string[] parts = value.Split('|'); - return new OAuthToken - { - AccessToken = parts[0], - AccessTokenExpiry = long.Parse(parts[1], NumberFormatInfo.InvariantInfo), - RefreshToken = parts[2] - }; - } - catch - { - } - return null; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Globalization; +using Newtonsoft.Json; + +namespace osu.Game.Online.API +{ + [Serializable] + public class OAuthToken + { + /// + /// OAuth 2.0 access token. + /// + [JsonProperty(@"access_token")] + public string AccessToken; + + [JsonProperty(@"expires_in")] + public long ExpiresIn + { + get + { + return AccessTokenExpiry - DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + } + + set + { + AccessTokenExpiry = DateTimeOffset.Now.AddSeconds(value).ToUnixTimeSeconds(); + } + } + + public bool IsValid => !string.IsNullOrEmpty(AccessToken) && ExpiresIn > 30; + + public long AccessTokenExpiry; + + /// + /// OAuth 2.0 refresh token. + /// + [JsonProperty(@"refresh_token")] + public string RefreshToken; + + public override string ToString() => $@"{AccessToken}|{AccessTokenExpiry.ToString(NumberFormatInfo.InvariantInfo)}|{RefreshToken}"; + + public static OAuthToken Parse(string value) + { + try + { + string[] parts = value.Split('|'); + return new OAuthToken + { + AccessToken = parts[0], + AccessTokenExpiry = long.Parse(parts[1], NumberFormatInfo.InvariantInfo), + RefreshToken = parts[2] + }; + } + catch + { + } + return null; + } + } +} diff --git a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs index 28376a1b4f..2661303652 100644 --- a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs @@ -1,145 +1,145 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using System; - -namespace osu.Game.Online.API.Requests -{ - public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong... - { - [JsonProperty(@"covers")] - private BeatmapSetOnlineCovers covers { get; set; } - - [JsonProperty(@"preview_url")] - private string preview { get; set; } - - [JsonProperty(@"play_count")] - private int playCount { get; set; } - - [JsonProperty(@"favourite_count")] - private int favouriteCount { get; set; } - - [JsonProperty(@"bpm")] - private double bpm { get; set; } - - [JsonProperty(@"video")] - private bool hasVideo { get; set; } - - [JsonProperty(@"status")] - private BeatmapSetOnlineStatus status { get; set; } - - [JsonProperty(@"submitted_date")] - private DateTimeOffset submitted { get; set; } - - [JsonProperty(@"ranked_date")] - private DateTimeOffset ranked { get; set; } - - [JsonProperty(@"last_updated")] - private DateTimeOffset lastUpdated { get; set; } - - [JsonProperty(@"user_id")] - private long creatorId { - set { Author.Id = value; } - } - - [JsonProperty(@"beatmaps")] - private IEnumerable beatmaps { get; set; } - - public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) - { - return new BeatmapSetInfo - { - OnlineBeatmapSetID = OnlineBeatmapSetID, - Metadata = this, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = covers, - Preview = preview, - PlayCount = playCount, - FavouriteCount = favouriteCount, - BPM = bpm, - Status = status, - HasVideo = hasVideo, - Submitted = submitted, - Ranked = ranked, - LastUpdated = lastUpdated, - }, - Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), - }; - } - - private class APIResponseBeatmap : BeatmapMetadata - { - [JsonProperty(@"id")] - private int onlineBeatmapID { get; set; } - - [JsonProperty(@"playcount")] - private int playCount { get; set; } - - [JsonProperty(@"passcount")] - private int passCount { get; set; } - - [JsonProperty(@"mode_int")] - private int ruleset { get; set; } - - [JsonProperty(@"difficulty_rating")] - private double starDifficulty { get; set; } - - [JsonProperty(@"drain")] - private float drainRate { get; set; } - - [JsonProperty(@"cs")] - private float circleSize { get; set; } - - [JsonProperty(@"ar")] - private float approachRate { get; set; } - - [JsonProperty(@"accuracy")] - private float overallDifficulty { get; set; } - - [JsonProperty(@"total_length")] - private double length { get; set; } - - [JsonProperty(@"count_circles")] - private int circleCount { get; set; } - - [JsonProperty(@"count_sliders")] - private int sliderCount { get; set; } - - [JsonProperty(@"version")] - private string version { get; set; } - - public BeatmapInfo ToBeatmap(RulesetStore rulesets) - { - return new BeatmapInfo - { - Metadata = this, - Ruleset = rulesets.GetRuleset(ruleset), - StarDifficulty = starDifficulty, - OnlineBeatmapID = onlineBeatmapID, - Version = version, - BaseDifficulty = new BeatmapDifficulty - { - DrainRate = drainRate, - CircleSize = circleSize, - ApproachRate = approachRate, - OverallDifficulty = overallDifficulty, - }, - OnlineInfo = new BeatmapOnlineInfo - { - PlayCount = playCount, - PassCount = passCount, - Length = length, - CircleCount = circleCount, - SliderCount = sliderCount, - }, - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using System; + +namespace osu.Game.Online.API.Requests +{ + public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong... + { + [JsonProperty(@"covers")] + private BeatmapSetOnlineCovers covers { get; set; } + + [JsonProperty(@"preview_url")] + private string preview { get; set; } + + [JsonProperty(@"play_count")] + private int playCount { get; set; } + + [JsonProperty(@"favourite_count")] + private int favouriteCount { get; set; } + + [JsonProperty(@"bpm")] + private double bpm { get; set; } + + [JsonProperty(@"video")] + private bool hasVideo { get; set; } + + [JsonProperty(@"status")] + private BeatmapSetOnlineStatus status { get; set; } + + [JsonProperty(@"submitted_date")] + private DateTimeOffset submitted { get; set; } + + [JsonProperty(@"ranked_date")] + private DateTimeOffset ranked { get; set; } + + [JsonProperty(@"last_updated")] + private DateTimeOffset lastUpdated { get; set; } + + [JsonProperty(@"user_id")] + private long creatorId { + set { Author.Id = value; } + } + + [JsonProperty(@"beatmaps")] + private IEnumerable beatmaps { get; set; } + + public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) + { + return new BeatmapSetInfo + { + OnlineBeatmapSetID = OnlineBeatmapSetID, + Metadata = this, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = covers, + Preview = preview, + PlayCount = playCount, + FavouriteCount = favouriteCount, + BPM = bpm, + Status = status, + HasVideo = hasVideo, + Submitted = submitted, + Ranked = ranked, + LastUpdated = lastUpdated, + }, + Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), + }; + } + + private class APIResponseBeatmap : BeatmapMetadata + { + [JsonProperty(@"id")] + private int onlineBeatmapID { get; set; } + + [JsonProperty(@"playcount")] + private int playCount { get; set; } + + [JsonProperty(@"passcount")] + private int passCount { get; set; } + + [JsonProperty(@"mode_int")] + private int ruleset { get; set; } + + [JsonProperty(@"difficulty_rating")] + private double starDifficulty { get; set; } + + [JsonProperty(@"drain")] + private float drainRate { get; set; } + + [JsonProperty(@"cs")] + private float circleSize { get; set; } + + [JsonProperty(@"ar")] + private float approachRate { get; set; } + + [JsonProperty(@"accuracy")] + private float overallDifficulty { get; set; } + + [JsonProperty(@"total_length")] + private double length { get; set; } + + [JsonProperty(@"count_circles")] + private int circleCount { get; set; } + + [JsonProperty(@"count_sliders")] + private int sliderCount { get; set; } + + [JsonProperty(@"version")] + private string version { get; set; } + + public BeatmapInfo ToBeatmap(RulesetStore rulesets) + { + return new BeatmapInfo + { + Metadata = this, + Ruleset = rulesets.GetRuleset(ruleset), + StarDifficulty = starDifficulty, + OnlineBeatmapID = onlineBeatmapID, + Version = version, + BaseDifficulty = new BeatmapDifficulty + { + DrainRate = drainRate, + CircleSize = circleSize, + ApproachRate = approachRate, + OverallDifficulty = overallDifficulty, + }, + OnlineInfo = new BeatmapOnlineInfo + { + PlayCount = playCount, + PassCount = passCount, + Length = length, + CircleCount = circleCount, + SliderCount = sliderCount, + }, + }; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs index 34ebe0c92a..c72bf8cbed 100644 --- a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using System; - -namespace osu.Game.Online.API.Requests -{ - public class DownloadBeatmapSetRequest : APIDownloadRequest - { - public readonly BeatmapSetInfo BeatmapSet; - - public Action DownloadProgressed; - - private readonly bool noVideo; - - public DownloadBeatmapSetRequest(BeatmapSetInfo set, bool noVideo) - { - this.noVideo = noVideo; - BeatmapSet = set; - - Progress += (current, total) => DownloadProgressed?.Invoke((float) current / total); - } - - protected override string Target => $@"beatmapsets/{BeatmapSet.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using System; + +namespace osu.Game.Online.API.Requests +{ + public class DownloadBeatmapSetRequest : APIDownloadRequest + { + public readonly BeatmapSetInfo BeatmapSet; + + public Action DownloadProgressed; + + private readonly bool noVideo; + + public DownloadBeatmapSetRequest(BeatmapSetInfo set, bool noVideo) + { + this.noVideo = noVideo; + BeatmapSet = set; + + Progress += (current, total) => DownloadProgressed?.Invoke((float) current / total); + } + + protected override string Target => $@"beatmapsets/{BeatmapSet.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs index 4b6129e015..b853da7e76 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; -using osu.Game.Beatmaps; - -namespace osu.Game.Online.API.Requests -{ - public class GetBeatmapDetailsRequest : APIRequest - { - private readonly BeatmapInfo beatmap; - - private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; - - public GetBeatmapDetailsRequest(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - } - - protected override string Target => $@"beatmaps/{lookupString}"; - } - - public class GetBeatmapDetailsResponse : BeatmapMetrics - { - //the online API returns some metrics as a nested object. - [JsonProperty(@"failtimes")] - private BeatmapMetrics failTimes - { - set - { - Fails = value.Fails; - Retries = value.Retries; - } - } - - //and other metrics in the beatmap set. - [JsonProperty(@"beatmapset")] - private BeatmapMetrics beatmapSet - { - set - { - Ratings = value.Ratings; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Beatmaps; + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapDetailsRequest : APIRequest + { + private readonly BeatmapInfo beatmap; + + private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; + + public GetBeatmapDetailsRequest(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + protected override string Target => $@"beatmaps/{lookupString}"; + } + + public class GetBeatmapDetailsResponse : BeatmapMetrics + { + //the online API returns some metrics as a nested object. + [JsonProperty(@"failtimes")] + private BeatmapMetrics failTimes + { + set + { + Fails = value.Fails; + Retries = value.Retries; + } + } + + //and other metrics in the beatmap set. + [JsonProperty(@"beatmapset")] + private BeatmapMetrics beatmapSet + { + set + { + Ratings = value.Ratings; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs index cba1d9471c..b031791900 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Online.API.Requests -{ - public class GetBeatmapSetRequest : APIRequest - { - private readonly int beatmapSetId; - - public GetBeatmapSetRequest(int beatmapSetId) - { - this.beatmapSetId = beatmapSetId; - } - - protected override string Target => $@"beatmapsets/{beatmapSetId}"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapSetRequest : APIRequest + { + private readonly int beatmapSetId; + + public GetBeatmapSetRequest(int beatmapSetId) + { + this.beatmapSetId = beatmapSetId; + } + + protected override string Target => $@"beatmapsets/{beatmapSetId}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetFriendsRequest.cs b/osu.Game/Online/API/Requests/GetFriendsRequest.cs index 5bc3d8e39e..13bdd23170 100644 --- a/osu.Game/Online/API/Requests/GetFriendsRequest.cs +++ b/osu.Game/Online/API/Requests/GetFriendsRequest.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Users; - -namespace osu.Game.Online.API.Requests -{ - public class GetFriendsRequest : APIRequest> - { - protected override string Target => @"friends"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class GetFriendsRequest : APIRequest> + { + protected override string Target => @"friends"; + } +} diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs index cf6a0cb6fd..5ab621c662 100644 --- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs +++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.IO.Network; -using osu.Game.Online.Chat; - -namespace osu.Game.Online.API.Requests -{ - public class GetMessagesRequest : APIMessagesRequest - { - private readonly IEnumerable channels; - - public GetMessagesRequest(IEnumerable channels, long? sinceId) : base(sinceId) - { - if (channels == null) - throw new ArgumentNullException(nameof(channels)); - if (channels.Any(c => c.Target != TargetType.Channel)) - throw new ArgumentException("All channels in the argument channels must have the targettype Channel"); - - this.channels = channels; - } - - protected override WebRequest CreateWebRequest() - { - string channelString = string.Join(",", channels.Select(x => x.Id)); - - var req = base.CreateWebRequest(); - req.AddParameter(@"channels", channelString); - - return req; - } - - protected override string Target => @"chat/messages"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class GetMessagesRequest : APIMessagesRequest + { + private readonly IEnumerable channels; + + public GetMessagesRequest(IEnumerable channels, long? sinceId) : base(sinceId) + { + if (channels == null) + throw new ArgumentNullException(nameof(channels)); + if (channels.Any(c => c.Target != TargetType.Channel)) + throw new ArgumentException("All channels in the argument channels must have the targettype Channel"); + + this.channels = channels; + } + + protected override WebRequest CreateWebRequest() + { + string channelString = string.Join(",", channels.Select(x => x.Id)); + + var req = base.CreateWebRequest(); + req.AddParameter(@"channels", channelString); + + return req; + } + + protected override string Target => @"chat/messages"; + } +} diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index b726a3767a..cff6fd4ea5 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -1,166 +1,166 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Users; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Select.Leaderboards; -using osu.Framework.IO.Network; - -namespace osu.Game.Online.API.Requests -{ - public class GetScoresRequest : APIRequest - { - private readonly BeatmapInfo beatmap; - private readonly LeaderboardScope scope; - private readonly RulesetInfo ruleset; - - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) - { - if (!beatmap.OnlineBeatmapID.HasValue) - throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); - - if (scope == LeaderboardScope.Local) - throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); - - this.beatmap = beatmap; - this.scope = scope; - this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); - - Success += onSuccess; - } - - private void onSuccess(GetScoresResponse r) - { - foreach (OnlineScore score in r.Scores) - score.ApplyBeatmap(beatmap); - } - - protected override WebRequest CreateWebRequest() - { - var req = base.CreateWebRequest(); - - req.Timeout = 30000; - req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); - req.AddParameter(@"mode", ruleset.ShortName); - - return req; - } - - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; - } - - public class GetScoresResponse - { - [JsonProperty(@"scores")] - public IEnumerable Scores; - } - - public class OnlineScore : Score - { - [JsonProperty(@"score")] - private double totalScore - { - set { TotalScore = value; } - } - - [JsonProperty(@"max_combo")] - private int maxCombo - { - set { MaxCombo = value; } - } - - [JsonProperty(@"user")] - private User user - { - set { User = value; } - } - - [JsonProperty(@"replay_data")] - private Replay replay - { - set { Replay = value; } - } - - [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } - - [JsonProperty(@"score_id")] - private long onlineScoreID - { - set { OnlineScoreID = value; } - } - - [JsonProperty(@"created_at")] - private DateTimeOffset date - { - set { Date = value; } - } - - [JsonProperty(@"beatmap")] - private BeatmapInfo beatmap - { - set { Beatmap = value; } - } - - [JsonProperty(@"beatmapset")] - private BeatmapMetadata metadata - { - set { Beatmap.Metadata = value; } - } - - [JsonProperty(@"statistics")] - private Dictionary jsonStats - { - set - { - foreach (var kvp in value) - { - HitResult newKey; - switch (kvp.Key) - { - case @"count_300": - newKey = HitResult.Great; - break; - case @"count_100": - newKey = HitResult.Good; - break; - case @"count_50": - newKey = HitResult.Meh; - break; - case @"count_miss": - newKey = HitResult.Miss; - break; - default: - continue; - } - - Statistics.Add(newKey, kvp.Value); - } - } - } - - [JsonProperty(@"mods")] - private string[] modStrings { get; set; } - - public void ApplyBeatmap(BeatmapInfo beatmap) - { - Beatmap = beatmap; - ApplyRuleset(beatmap.Ruleset); - } - - public void ApplyRuleset(RulesetInfo ruleset) - { - Ruleset = ruleset; - - // Evaluate the mod string - Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Users; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class GetScoresRequest : APIRequest + { + private readonly BeatmapInfo beatmap; + private readonly LeaderboardScope scope; + private readonly RulesetInfo ruleset; + + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) + { + if (!beatmap.OnlineBeatmapID.HasValue) + throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + + if (scope == LeaderboardScope.Local) + throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); + + this.beatmap = beatmap; + this.scope = scope; + this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); + + Success += onSuccess; + } + + private void onSuccess(GetScoresResponse r) + { + foreach (OnlineScore score in r.Scores) + score.ApplyBeatmap(beatmap); + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Timeout = 30000; + req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); + req.AddParameter(@"mode", ruleset.ShortName); + + return req; + } + + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; + } + + public class GetScoresResponse + { + [JsonProperty(@"scores")] + public IEnumerable Scores; + } + + public class OnlineScore : Score + { + [JsonProperty(@"score")] + private double totalScore + { + set { TotalScore = value; } + } + + [JsonProperty(@"max_combo")] + private int maxCombo + { + set { MaxCombo = value; } + } + + [JsonProperty(@"user")] + private User user + { + set { User = value; } + } + + [JsonProperty(@"replay_data")] + private Replay replay + { + set { Replay = value; } + } + + [JsonProperty(@"mode_int")] + public int OnlineRulesetID { get; set; } + + [JsonProperty(@"score_id")] + private long onlineScoreID + { + set { OnlineScoreID = value; } + } + + [JsonProperty(@"created_at")] + private DateTimeOffset date + { + set { Date = value; } + } + + [JsonProperty(@"beatmap")] + private BeatmapInfo beatmap + { + set { Beatmap = value; } + } + + [JsonProperty(@"beatmapset")] + private BeatmapMetadata metadata + { + set { Beatmap.Metadata = value; } + } + + [JsonProperty(@"statistics")] + private Dictionary jsonStats + { + set + { + foreach (var kvp in value) + { + HitResult newKey; + switch (kvp.Key) + { + case @"count_300": + newKey = HitResult.Great; + break; + case @"count_100": + newKey = HitResult.Good; + break; + case @"count_50": + newKey = HitResult.Meh; + break; + case @"count_miss": + newKey = HitResult.Miss; + break; + default: + continue; + } + + Statistics.Add(newKey, kvp.Value); + } + } + } + + [JsonProperty(@"mods")] + private string[] modStrings { get; set; } + + public void ApplyBeatmap(BeatmapInfo beatmap) + { + Beatmap = beatmap; + ApplyRuleset(beatmap.Ruleset); + } + + public void ApplyRuleset(RulesetInfo ruleset) + { + Ruleset = ruleset; + + // Evaluate the mod string + Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); + } + } +} diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 70468c557a..48e9babd97 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Humanizer; -using System.Collections.Generic; - -namespace osu.Game.Online.API.Requests -{ - public class GetUserBeatmapsRequest : APIRequest> - { - private readonly long userId; - private readonly int offset; - private readonly BeatmapSetType type; - - public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0) - { - this.userId = userId; - this.offset = offset; - this.type = type; - } - - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}"; - } - - public enum BeatmapSetType - { - Favourite, - RankedAndApproved, - Unranked, - Graveyard - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Humanizer; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserBeatmapsRequest : APIRequest> + { + private readonly long userId; + private readonly int offset; + private readonly BeatmapSetType type; + + public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0) + { + this.userId = userId; + this.offset = offset; + this.type = type; + } + + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}"; + } + + public enum BeatmapSetType + { + Favourite, + RankedAndApproved, + Unranked, + Graveyard + } +} diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs index 3fcbe98b11..106f3d4fb1 100644 --- a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs @@ -1,48 +1,48 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using System.Collections.Generic; - -namespace osu.Game.Online.API.Requests -{ - public class GetUserMostPlayedBeatmapsRequest : APIRequest> - { - private readonly long userId; - private readonly int offset; - - public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0) - { - this.userId = userId; - this.offset = offset; - } - - protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}"; - } - - public class MostPlayedBeatmap - { - [JsonProperty("beatmap_id")] - public int BeatmapID; - - [JsonProperty("count")] - public int PlayCount; - - [JsonProperty] - private BeatmapInfo beatmap; - - [JsonProperty] - private APIResponseBeatmapSet beatmapSet; - - public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) - { - BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets); - beatmap.BeatmapSet = setInfo; - beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID; - beatmap.Metadata = setInfo.Metadata; - return beatmap; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserMostPlayedBeatmapsRequest : APIRequest> + { + private readonly long userId; + private readonly int offset; + + public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0) + { + this.userId = userId; + this.offset = offset; + } + + protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}"; + } + + public class MostPlayedBeatmap + { + [JsonProperty("beatmap_id")] + public int BeatmapID; + + [JsonProperty("count")] + public int PlayCount; + + [JsonProperty] + private BeatmapInfo beatmap; + + [JsonProperty] + private APIResponseBeatmapSet beatmapSet; + + public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) + { + BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets); + beatmap.BeatmapSet = setInfo; + beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID; + beatmap.Metadata = setInfo.Metadata; + return beatmap; + } + } +} diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index d1685b01f3..ded0e4f80d 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -1,130 +1,130 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; -using osu.Game.Rulesets.Scoring; -using Humanizer; -using System; -using System.Collections.Generic; - -namespace osu.Game.Online.API.Requests -{ - public class GetUserRecentActivitiesRequest : APIRequest> - { - private readonly long userId; - private readonly int offset; - - public GetUserRecentActivitiesRequest(long userId, int offset = 0) - { - this.userId = userId; - this.offset = offset; - } - - protected override string Target => $"users/{userId}/recent_activity?offset={offset}"; - } - - public class RecentActivity - { - [JsonProperty("id")] - public int ID; - - [JsonProperty("createdAt")] - public DateTimeOffset CreatedAt; - - [JsonProperty] - private string type - { - set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize()); - } - - public RecentActivityType Type; - - [JsonProperty] - private string scoreRank - { - set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value); - } - - public ScoreRank ScoreRank; - - [JsonProperty("rank")] - public int Rank; - - [JsonProperty("approval")] - public BeatmapApproval Approval; - - [JsonProperty("count")] - public int Count; - - [JsonProperty("mode")] - public string Mode; - - [JsonProperty("beatmap")] - public RecentActivityBeatmap Beatmap; - - [JsonProperty("beatmapset")] - public RecentActivityBeatmap Beatmapset; - - [JsonProperty("user")] - public RecentActivityUser User; - - [JsonProperty("achievement")] - public RecentActivityAchievement Achievement; - - public class RecentActivityBeatmap - { - [JsonProperty("title")] - public string Title; - - [JsonProperty("url")] - public string Url; - } - - public class RecentActivityUser - { - [JsonProperty("username")] - public string Username; - - [JsonProperty("url")] - public string Url; - - [JsonProperty("previousUsername")] - public string PreviousUsername; - } - - public class RecentActivityAchievement - { - [JsonProperty("slug")] - public string Slug; - - [JsonProperty("name")] - public string Name; - } - - } - - public enum RecentActivityType - { - Achievement, - BeatmapPlaycount, - BeatmapsetApprove, - BeatmapsetDelete, - BeatmapsetRevive, - BeatmapsetUpdate, - BeatmapsetUpload, - Medal, - Rank, - RankLost, - UserSupportAgain, - UserSupportFirst, - UserSupportGift, - UsernameChange, - } - - public enum BeatmapApproval - { - Ranked, - Approved, - Qualified, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Rulesets.Scoring; +using Humanizer; +using System; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserRecentActivitiesRequest : APIRequest> + { + private readonly long userId; + private readonly int offset; + + public GetUserRecentActivitiesRequest(long userId, int offset = 0) + { + this.userId = userId; + this.offset = offset; + } + + protected override string Target => $"users/{userId}/recent_activity?offset={offset}"; + } + + public class RecentActivity + { + [JsonProperty("id")] + public int ID; + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt; + + [JsonProperty] + private string type + { + set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize()); + } + + public RecentActivityType Type; + + [JsonProperty] + private string scoreRank + { + set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value); + } + + public ScoreRank ScoreRank; + + [JsonProperty("rank")] + public int Rank; + + [JsonProperty("approval")] + public BeatmapApproval Approval; + + [JsonProperty("count")] + public int Count; + + [JsonProperty("mode")] + public string Mode; + + [JsonProperty("beatmap")] + public RecentActivityBeatmap Beatmap; + + [JsonProperty("beatmapset")] + public RecentActivityBeatmap Beatmapset; + + [JsonProperty("user")] + public RecentActivityUser User; + + [JsonProperty("achievement")] + public RecentActivityAchievement Achievement; + + public class RecentActivityBeatmap + { + [JsonProperty("title")] + public string Title; + + [JsonProperty("url")] + public string Url; + } + + public class RecentActivityUser + { + [JsonProperty("username")] + public string Username; + + [JsonProperty("url")] + public string Url; + + [JsonProperty("previousUsername")] + public string PreviousUsername; + } + + public class RecentActivityAchievement + { + [JsonProperty("slug")] + public string Slug; + + [JsonProperty("name")] + public string Name; + } + + } + + public enum RecentActivityType + { + Achievement, + BeatmapPlaycount, + BeatmapsetApprove, + BeatmapsetDelete, + BeatmapsetRevive, + BeatmapsetUpdate, + BeatmapsetUpload, + Medal, + Rank, + RankLost, + UserSupportAgain, + UserSupportFirst, + UserSupportGift, + UsernameChange, + } + + public enum BeatmapApproval + { + Ranked, + Approved, + Qualified, + } +} diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 6a586bfe2e..9026d10334 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Users; - -namespace osu.Game.Online.API.Requests -{ - public class GetUserRequest : APIRequest - { - private long? userId; - - public GetUserRequest(long? userId = null) - { - this.userId = userId; - } - - protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserRequest : APIRequest + { + private long? userId; + + public GetUserRequest(long? userId = null) + { + this.userId = userId; + } + + protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; + } +} diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 7bdd1a94b9..7757f529e0 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; - -namespace osu.Game.Online.API.Requests -{ - public class GetUserScoresRequest : APIRequest> - { - private readonly long userId; - private readonly ScoreType type; - private readonly int offset; - - public GetUserScoresRequest(long userId, ScoreType type, int offset = 0) - { - this.userId = userId; - this.type = type; - this.offset = offset; - } - - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}?offset={offset}"; - } - - public enum ScoreType - { - Best, - Firsts, - Recent - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserScoresRequest : APIRequest> + { + private readonly long userId; + private readonly ScoreType type; + private readonly int offset; + + public GetUserScoresRequest(long userId, ScoreType type, int offset = 0) + { + this.userId = userId; + this.type = type; + this.offset = offset; + } + + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}?offset={offset}"; + } + + public enum ScoreType + { + Best, + Firsts, + Recent + } +} diff --git a/osu.Game/Online/API/Requests/GetUsersRequest.cs b/osu.Game/Online/API/Requests/GetUsersRequest.cs index 267985a253..575d0464aa 100644 --- a/osu.Game/Online/API/Requests/GetUsersRequest.cs +++ b/osu.Game/Online/API/Requests/GetUsersRequest.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using Newtonsoft.Json; -using osu.Game.Users; - -namespace osu.Game.Online.API.Requests -{ - public class GetUsersRequest : APIRequest> - { - protected override string Target => @"rankings/osu/performance"; - } - - public class RankingEntry - { - [JsonProperty] - public User User; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class GetUsersRequest : APIRequest> + { + protected override string Target => @"rankings/osu/performance"; + } + + public class RankingEntry + { + [JsonProperty] + public User User; + } +} diff --git a/osu.Game/Online/API/Requests/ListChannelsRequest.cs b/osu.Game/Online/API/Requests/ListChannelsRequest.cs index b387af9694..d1ee4fde01 100644 --- a/osu.Game/Online/API/Requests/ListChannelsRequest.cs +++ b/osu.Game/Online/API/Requests/ListChannelsRequest.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Online.Chat; - -namespace osu.Game.Online.API.Requests -{ - public class ListChannelsRequest : APIRequest> - { - protected override string Target => @"chat/channels"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class ListChannelsRequest : APIRequest> + { + protected override string Target => @"chat/channels"; + } +} diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs index e13128cc3c..44429bdcd5 100644 --- a/osu.Game/Online/API/Requests/PostMessageRequest.cs +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions; -using osu.Framework.IO.Network; -using osu.Game.Online.Chat; - -namespace osu.Game.Online.API.Requests -{ - public class PostMessageRequest : APIRequest - { - private readonly Message message; - - public PostMessageRequest(Message message) - { - this.message = message; - } - - protected override WebRequest CreateWebRequest() - { - var req = base.CreateWebRequest(); - - req.Method = HttpMethod.POST; - req.AddParameter(@"target_type", message.TargetType.GetDescription()); - req.AddParameter(@"target_id", message.TargetId.ToString()); - req.AddParameter(@"is_action", message.IsAction.ToString().ToLower()); - req.AddParameter(@"message", message.Content); - - return req; - } - - protected override string Target => @"chat/messages"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class PostMessageRequest : APIRequest + { + private readonly Message message; + + public PostMessageRequest(Message message) + { + this.message = message; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Method = HttpMethod.POST; + req.AddParameter(@"target_type", message.TargetType.GetDescription()); + req.AddParameter(@"target_id", message.TargetId.ToString()); + req.AddParameter(@"is_action", message.IsAction.ToString().ToLower()); + req.AddParameter(@"message", message.Content); + + return req; + } + + protected override string Target => @"chat/messages"; + } +} diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index c54d0ea556..e961dd9a9e 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Game.Overlays.Direct; -using osu.Game.Rulesets; - -namespace osu.Game.Online.API.Requests -{ - public class SearchBeatmapSetsRequest : APIRequest> - { - private readonly string query; - private readonly RulesetInfo ruleset; - private readonly RankStatus rankStatus; - private readonly DirectSortCriteria sortCriteria; - private readonly SortDirection direction; - private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; - - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, RankStatus rankStatus = RankStatus.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) - { - this.query = System.Uri.EscapeDataString(query); - this.ruleset = ruleset; - this.rankStatus = rankStatus; - this.sortCriteria = sortCriteria; - this.direction = direction; - } - - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests +{ + public class SearchBeatmapSetsRequest : APIRequest> + { + private readonly string query; + private readonly RulesetInfo ruleset; + private readonly RankStatus rankStatus; + private readonly DirectSortCriteria sortCriteria; + private readonly SortDirection direction; + private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; + + public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, RankStatus rankStatus = RankStatus.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) + { + this.query = System.Uri.EscapeDataString(query); + this.ruleset = ruleset; + this.rankStatus = rankStatus; + this.sortCriteria = sortCriteria; + this.direction = direction; + } + + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; + } +} diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 93b26363ed..e9cd7e51e0 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -1,123 +1,123 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Newtonsoft.Json; -using osu.Framework.Configuration; -using osu.Framework.Lists; -using osu.Game.Users; - -namespace osu.Game.Online.Chat -{ - public class Channel - { - public const int MAX_HISTORY = 300; - - [JsonProperty(@"name")] - public string Name; - - [JsonProperty(@"description")] - public string Topic; - - [JsonProperty(@"type")] - public string Type; - - [JsonProperty(@"channel_id")] - public long Id; - - [JsonConstructor] - public Channel() - { - } - - /// - /// Contructs a private channel - /// TODO this class needs to be serialized from something like channels/private, instead of creating from a contructor - /// - /// The user - public Channel(User user) - { - Target = TargetType.User; - Name = user.Username; - Id = user.Id; - JoinedUsers.Add(user); - } - - /// - /// Contains every joined user except yourself - /// - public ObservableCollection JoinedUsers = new ObservableCollection(); - public readonly SortedList Messages = new SortedList(Comparer.Default); - private readonly List pendingMessages = new List(); - - public event Action> NewMessagesArrived; - public event Action PendingMessageResolved; - public event Action MessageRemoved; - - public Bindable Joined = new Bindable(); - public TargetType Target { get; set; } - public bool ReadOnly { get; set; } - - public void AddLocalEcho(LocalEchoMessage message) - { - pendingMessages.Add(message); - Messages.Add(message); - - NewMessagesArrived?.Invoke(new[] { message }); - } - - public void AddNewMessages(params Message[] messages) - { - messages = messages.Except(Messages).ToArray(); - - Messages.AddRange(messages); - - purgeOldMessages(); - - NewMessagesArrived?.Invoke(messages); - } - - /// - /// Replace or remove a message from the channel. - /// - /// The local echo message (client-side). - /// The response message, or null if the message became invalid. - public void ReplaceMessage(LocalEchoMessage echo, Message final) - { - if (!pendingMessages.Remove(echo)) - throw new InvalidOperationException("Attempted to remove echo that wasn't present"); - - Messages.Remove(echo); - - if (final == null) - { - MessageRemoved?.Invoke(echo); - return; - } - - if (Messages.Contains(final)) - { - // message already inserted, so let's throw away this update. - // we may want to handle this better in the future, but for the time being api requests are single-threaded so order is assumed. - MessageRemoved?.Invoke(echo); - return; - } - - Messages.Add(final); - PendingMessageResolved?.Invoke(echo, final); - } - - private void purgeOldMessages() - { - // never purge local echos - int messageCount = Messages.Count - pendingMessages.Count; - if (messageCount > MAX_HISTORY) - Messages.RemoveRange(0, messageCount - MAX_HISTORY); - } - - public override string ToString() => Name; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Configuration; +using osu.Framework.Lists; +using osu.Game.Users; + +namespace osu.Game.Online.Chat +{ + public class Channel + { + public const int MAX_HISTORY = 300; + + [JsonProperty(@"name")] + public string Name; + + [JsonProperty(@"description")] + public string Topic; + + [JsonProperty(@"type")] + public string Type; + + [JsonProperty(@"channel_id")] + public long Id; + + [JsonConstructor] + public Channel() + { + } + + /// + /// Contructs a private channel + /// TODO this class needs to be serialized from something like channels/private, instead of creating from a contructor + /// + /// The user + public Channel(User user) + { + Target = TargetType.User; + Name = user.Username; + Id = user.Id; + JoinedUsers.Add(user); + } + + /// + /// Contains every joined user except yourself + /// + public ObservableCollection JoinedUsers = new ObservableCollection(); + public readonly SortedList Messages = new SortedList(Comparer.Default); + private readonly List pendingMessages = new List(); + + public event Action> NewMessagesArrived; + public event Action PendingMessageResolved; + public event Action MessageRemoved; + + public Bindable Joined = new Bindable(); + public TargetType Target { get; set; } + public bool ReadOnly { get; set; } + + public void AddLocalEcho(LocalEchoMessage message) + { + pendingMessages.Add(message); + Messages.Add(message); + + NewMessagesArrived?.Invoke(new[] { message }); + } + + public void AddNewMessages(params Message[] messages) + { + messages = messages.Except(Messages).ToArray(); + + Messages.AddRange(messages); + + purgeOldMessages(); + + NewMessagesArrived?.Invoke(messages); + } + + /// + /// Replace or remove a message from the channel. + /// + /// The local echo message (client-side). + /// The response message, or null if the message became invalid. + public void ReplaceMessage(LocalEchoMessage echo, Message final) + { + if (!pendingMessages.Remove(echo)) + throw new InvalidOperationException("Attempted to remove echo that wasn't present"); + + Messages.Remove(echo); + + if (final == null) + { + MessageRemoved?.Invoke(echo); + return; + } + + if (Messages.Contains(final)) + { + // message already inserted, so let's throw away this update. + // we may want to handle this better in the future, but for the time being api requests are single-threaded so order is assumed. + MessageRemoved?.Invoke(echo); + return; + } + + Messages.Add(final); + PendingMessageResolved?.Invoke(echo, final); + } + + private void purgeOldMessages() + { + // never purge local echos + int messageCount = Messages.Count - pendingMessages.Count; + if (messageCount > MAX_HISTORY) + Messages.RemoveRange(0, messageCount - MAX_HISTORY); + } + + public override string ToString() => Name; + } +} diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 234781fb52..475363bd51 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -1,59 +1,59 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Cursor; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using OpenTK; - -namespace osu.Game.Online.Chat -{ - /// - /// An invisible drawable that brings multiple pieces together to form a consumable clickable link. - /// - public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip - { - /// - /// Each word part of a chat link (split for word-wrap support). - /// - public List Parts; - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); - - protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); - - public DrawableLinkCompiler(IEnumerable parts) - { - Parts = parts.ToList(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.Blue; - } - - protected override IEnumerable EffectTargets => Parts; - - public string TooltipText { get; set; } - - private class LinkHoverSounds : HoverClickSounds - { - private readonly List parts; - - public LinkHoverSounds(HoverSampleSet sampleSet, List parts) - : base(sampleSet) - { - this.parts = parts; - } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Cursor; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using OpenTK; + +namespace osu.Game.Online.Chat +{ + /// + /// An invisible drawable that brings multiple pieces together to form a consumable clickable link. + /// + public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip + { + /// + /// Each word part of a chat link (split for word-wrap support). + /// + public List Parts; + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); + + protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); + + public DrawableLinkCompiler(IEnumerable parts) + { + Parts = parts.ToList(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.Blue; + } + + protected override IEnumerable EffectTargets => Parts; + + public string TooltipText { get; set; } + + private class LinkHoverSounds : HoverClickSounds + { + private readonly List parts; + + public LinkHoverSounds(HoverSampleSet sampleSet, List parts) + : base(sampleSet) + { + this.parts = parts; + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); + } + } +} diff --git a/osu.Game/Online/Chat/ErrorMessage.cs b/osu.Game/Online/Chat/ErrorMessage.cs index b083cb6a10..87652c50c4 100644 --- a/osu.Game/Online/Chat/ErrorMessage.cs +++ b/osu.Game/Online/Chat/ErrorMessage.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Online.Chat -{ - public class ErrorMessage : InfoMessage - { - public ErrorMessage(string message) : base(message) - { - Sender.Colour = @"ff0000"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Online.Chat +{ + public class ErrorMessage : InfoMessage + { + public ErrorMessage(string message) : base(message) + { + Sender.Colour = @"ff0000"; + } + } +} diff --git a/osu.Game/Online/Chat/InfoMessage.cs b/osu.Game/Online/Chat/InfoMessage.cs index 3e521de28d..2be025e403 100644 --- a/osu.Game/Online/Chat/InfoMessage.cs +++ b/osu.Game/Online/Chat/InfoMessage.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Users; - -namespace osu.Game.Online.Chat -{ - public class InfoMessage : Message - { - private static int infoID = -1; - - public InfoMessage(string message) : base(infoID--) - { - Timestamp = DateTimeOffset.Now; - Content = message; - - Sender = new User - { - Username = @"system", - Colour = @"0000ff", - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Users; + +namespace osu.Game.Online.Chat +{ + public class InfoMessage : Message + { + private static int infoID = -1; + + public InfoMessage(string message) : base(infoID--) + { + Timestamp = DateTimeOffset.Now; + Content = message; + + Sender = new User + { + Username = @"system", + Colour = @"0000ff", + }; + } + } +} diff --git a/osu.Game/Online/Chat/LocalEchoMessage.cs b/osu.Game/Online/Chat/LocalEchoMessage.cs index 1b31fe08d6..2e90b9d3fd 100644 --- a/osu.Game/Online/Chat/LocalEchoMessage.cs +++ b/osu.Game/Online/Chat/LocalEchoMessage.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Online.Chat -{ - public class LocalEchoMessage : Message - { - public LocalEchoMessage() : base(null) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Online.Chat +{ + public class LocalEchoMessage : Message + { + public LocalEchoMessage() : base(null) + { + } + } +} diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index d1d1f1b55b..6c3d118d1b 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -1,83 +1,83 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using Newtonsoft.Json; -using osu.Game.Users; - -namespace osu.Game.Online.Chat -{ - public class Message : IComparable, IEquatable - { - [JsonProperty(@"message_id")] - public readonly long? Id; - - //todo: this should be inside sender. - [JsonProperty(@"sender_id")] - public int UserId; - - [JsonProperty(@"target_type")] - public TargetType TargetType; - - [JsonProperty(@"target_id")] - public long TargetId; - - [JsonProperty(@"is_action")] - public bool IsAction; - - [JsonProperty(@"timestamp")] - public DateTimeOffset Timestamp; - - [JsonProperty(@"content")] - public string Content; - - [JsonProperty(@"sender")] - public User Sender; - - [JsonConstructor] - public Message() - { - } - - /// - /// The text that is displayed in chat. - /// - public string DisplayContent { get; set; } - - /// - /// The links found in this message. - /// - /// The s' and s are according to - public List Links; - - public Message(long? id) - { - Id = id; - } - - public int CompareTo(Message other) - { - if (!Id.HasValue) - return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp); - if (!other.Id.HasValue) - return -1; - - return Id.Value.CompareTo(other.Id.Value); - } - - public virtual bool Equals(Message other) => Id == other?.Id; - - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - public override int GetHashCode() => Id.GetHashCode(); - } - - public enum TargetType - { - [Description(@"channel")] - Channel, - [Description(@"user")] - User - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Newtonsoft.Json; +using osu.Game.Users; + +namespace osu.Game.Online.Chat +{ + public class Message : IComparable, IEquatable + { + [JsonProperty(@"message_id")] + public readonly long? Id; + + //todo: this should be inside sender. + [JsonProperty(@"sender_id")] + public int UserId; + + [JsonProperty(@"target_type")] + public TargetType TargetType; + + [JsonProperty(@"target_id")] + public long TargetId; + + [JsonProperty(@"is_action")] + public bool IsAction; + + [JsonProperty(@"timestamp")] + public DateTimeOffset Timestamp; + + [JsonProperty(@"content")] + public string Content; + + [JsonProperty(@"sender")] + public User Sender; + + [JsonConstructor] + public Message() + { + } + + /// + /// The text that is displayed in chat. + /// + public string DisplayContent { get; set; } + + /// + /// The links found in this message. + /// + /// The s' and s are according to + public List Links; + + public Message(long? id) + { + Id = id; + } + + public int CompareTo(Message other) + { + if (!Id.HasValue) + return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp); + if (!other.Id.HasValue) + return -1; + + return Id.Value.CompareTo(other.Id.Value); + } + + public virtual bool Equals(Message other) => Id == other?.Id; + + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + public override int GetHashCode() => Id.GetHashCode(); + } + + public enum TargetType + { + [Description(@"channel")] + Channel, + [Description(@"user")] + User + } +} diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 9966f78435..dd19868954 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -1,278 +1,278 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace osu.Game.Online.Chat -{ - public static class MessageFormatter - { - // [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points) - private static readonly Regex wiki_regex = new Regex(@"\[\[([^\]]+)\]\]"); - - // (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234) - private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); - - // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) - private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); - - // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format - private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)"); - - // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used - // This is in the format (, [optional]): - // http[s]://.[:port][/path][?query][#fragment] - private static readonly Regex advanced_link_regex = new Regex( - // protocol - @"(?[a-z]*?:\/\/" + - // domain + tld - @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + - // port (optional) - @"(?::\d+)?)" + - // path (optional) - @"(?(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" + - // query (optional) - @"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" + - // fragment (optional) - @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", - RegexOptions.IgnoreCase); - - // 00:00:000 (1,2,3) - test - private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); - - // #osu - private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)"); - - // Unicode emojis - private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); - - private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) - { - int captureOffset = 0; - foreach (Match m in regex.Matches(result.Text, startIndex)) - { - var index = m.Index - captureOffset; - - var displayText = string.Format(display, - m.Groups[0], - m.Groups.Count > 1 ? m.Groups[1].Value : "", - m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); - - var linkText = string.Format(link, - m.Groups[0], - m.Groups.Count > 1 ? m.Groups[1].Value : "", - m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); - - if (displayText.Length == 0 || linkText.Length == 0) continue; - - // Check for encapsulated links - if (result.Links.Find(l => l.Index <= index && l.Index + l.Length >= index + m.Length || index <= l.Index && index + m.Length >= l.Index + l.Length) == null) - { - result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText); - - //since we just changed the line display text, offset any already processed links. - result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); - - var details = getLinkDetails(linkText); - result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); - - //adjust the offset for processing the current matches group. - captureOffset += m.Length - displayText.Length; - } - } - } - - private static void handleAdvanced(Regex regex, MessageFormatterResult result, int startIndex = 0) - { - foreach (Match m in regex.Matches(result.Text, startIndex)) - { - var index = m.Index; - var link = m.Groups["link"].Value; - var indexLength = link.Length; - - var details = getLinkDetails(link); - result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument)); - } - } - - private static LinkDetails getLinkDetails(string url) - { - var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - args[0] = args[0].TrimEnd(':'); - - switch (args[0]) - { - case "http": - case "https": - // length > 3 since all these links need another argument to work - if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh")) - { - switch (args[2]) - { - case "b": - case "beatmaps": - return new LinkDetails(LinkAction.OpenBeatmap, args[3]); - case "s": - case "beatmapsets": - case "d": - return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); - case "u": - return new LinkDetails(LinkAction.OpenUserProfile, args[3]); - } - } - - return new LinkDetails(LinkAction.External, null); - case "osu": - // every internal link also needs some kind of argument - if (args.Length < 3) - return new LinkDetails(LinkAction.External, null); - - LinkAction linkType; - switch (args[1]) - { - case "chan": - linkType = LinkAction.OpenChannel; - break; - case "edit": - linkType = LinkAction.OpenEditorTimestamp; - break; - case "b": - linkType = LinkAction.OpenBeatmap; - break; - case "s": - case "dl": - linkType = LinkAction.OpenBeatmapSet; - break; - case "spectate": - linkType = LinkAction.Spectate; - break; - case "u": - linkType = LinkAction.OpenUserProfile; - break; - default: - linkType = LinkAction.External; - break; - } - - return new LinkDetails(linkType, args[2]); - case "osump": - return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); - default: - return new LinkDetails(LinkAction.External, null); - } - } - - private static MessageFormatterResult format(string toFormat, int startIndex = 0, int space = 3) - { - var result = new MessageFormatterResult(toFormat); - - // handle the [link display] format - handleMatches(new_link_regex, "{2}", "{1}", result, startIndex); - - // handle the standard markdown []() format - handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex); - - // handle the ()[] link format - handleMatches(old_link_regex, "{1}", "{2}", result, startIndex); - - // handle wiki links - handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); - - // handle bare links - handleAdvanced(advanced_link_regex, result, startIndex); - - // handle editor times - handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); - - // handle channels - handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel); - - var empty = ""; - while (space-- > 0) - empty += "\0"; - - handleMatches(emoji_regex, empty, "{0}", result, startIndex); - - return result; - } - - public static Message FormatMessage(Message inputMessage) - { - var result = format(inputMessage.Content); - - inputMessage.DisplayContent = result.Text; - - // Sometimes, regex matches are not in order - result.Links.Sort(); - inputMessage.Links = result.Links; - return inputMessage; - } - - public static MessageFormatterResult FormatText(string text) - { - var result = format(text); - - result.Links.Sort(); - - return result; - } - - public class MessageFormatterResult - { - public List Links = new List(); - public string Text; - public string OriginalText; - - public MessageFormatterResult(string text) - { - OriginalText = Text = text; - } - } - - public class LinkDetails - { - public LinkAction Action; - public string Argument; - - public LinkDetails(LinkAction action, string argument) - { - Action = action; - Argument = argument; - } - } - } - - public enum LinkAction - { - External, - OpenBeatmap, - OpenBeatmapSet, - OpenChannel, - OpenEditorTimestamp, - JoinMultiplayerMatch, - Spectate, - OpenUserProfile, - } - - public class Link : IComparable - { - public string Url; - public int Index; - public int Length; - public LinkAction Action; - public string Argument; - - public Link(string url, int startIndex, int length, LinkAction action, string argument) - { - Url = url; - Index = startIndex; - Length = length; - Action = action; - Argument = argument; - } - - public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace osu.Game.Online.Chat +{ + public static class MessageFormatter + { + // [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points) + private static readonly Regex wiki_regex = new Regex(@"\[\[([^\]]+)\]\]"); + + // (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234) + private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); + + // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) + private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); + + // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format + private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)"); + + // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used + // This is in the format (, [optional]): + // http[s]://.[:port][/path][?query][#fragment] + private static readonly Regex advanced_link_regex = new Regex( + // protocol + @"(?[a-z]*?:\/\/" + + // domain + tld + @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + + // port (optional) + @"(?::\d+)?)" + + // path (optional) + @"(?(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" + + // query (optional) + @"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" + + // fragment (optional) + @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", + RegexOptions.IgnoreCase); + + // 00:00:000 (1,2,3) - test + private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); + + // #osu + private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)"); + + // Unicode emojis + private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); + + private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) + { + int captureOffset = 0; + foreach (Match m in regex.Matches(result.Text, startIndex)) + { + var index = m.Index - captureOffset; + + var displayText = string.Format(display, + m.Groups[0], + m.Groups.Count > 1 ? m.Groups[1].Value : "", + m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + + var linkText = string.Format(link, + m.Groups[0], + m.Groups.Count > 1 ? m.Groups[1].Value : "", + m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + + if (displayText.Length == 0 || linkText.Length == 0) continue; + + // Check for encapsulated links + if (result.Links.Find(l => l.Index <= index && l.Index + l.Length >= index + m.Length || index <= l.Index && index + m.Length >= l.Index + l.Length) == null) + { + result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText); + + //since we just changed the line display text, offset any already processed links. + result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); + + var details = getLinkDetails(linkText); + result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); + + //adjust the offset for processing the current matches group. + captureOffset += m.Length - displayText.Length; + } + } + } + + private static void handleAdvanced(Regex regex, MessageFormatterResult result, int startIndex = 0) + { + foreach (Match m in regex.Matches(result.Text, startIndex)) + { + var index = m.Index; + var link = m.Groups["link"].Value; + var indexLength = link.Length; + + var details = getLinkDetails(link); + result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument)); + } + } + + private static LinkDetails getLinkDetails(string url) + { + var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + args[0] = args[0].TrimEnd(':'); + + switch (args[0]) + { + case "http": + case "https": + // length > 3 since all these links need another argument to work + if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh")) + { + switch (args[2]) + { + case "b": + case "beatmaps": + return new LinkDetails(LinkAction.OpenBeatmap, args[3]); + case "s": + case "beatmapsets": + case "d": + return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); + case "u": + return new LinkDetails(LinkAction.OpenUserProfile, args[3]); + } + } + + return new LinkDetails(LinkAction.External, null); + case "osu": + // every internal link also needs some kind of argument + if (args.Length < 3) + return new LinkDetails(LinkAction.External, null); + + LinkAction linkType; + switch (args[1]) + { + case "chan": + linkType = LinkAction.OpenChannel; + break; + case "edit": + linkType = LinkAction.OpenEditorTimestamp; + break; + case "b": + linkType = LinkAction.OpenBeatmap; + break; + case "s": + case "dl": + linkType = LinkAction.OpenBeatmapSet; + break; + case "spectate": + linkType = LinkAction.Spectate; + break; + case "u": + linkType = LinkAction.OpenUserProfile; + break; + default: + linkType = LinkAction.External; + break; + } + + return new LinkDetails(linkType, args[2]); + case "osump": + return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); + default: + return new LinkDetails(LinkAction.External, null); + } + } + + private static MessageFormatterResult format(string toFormat, int startIndex = 0, int space = 3) + { + var result = new MessageFormatterResult(toFormat); + + // handle the [link display] format + handleMatches(new_link_regex, "{2}", "{1}", result, startIndex); + + // handle the standard markdown []() format + handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex); + + // handle the ()[] link format + handleMatches(old_link_regex, "{1}", "{2}", result, startIndex); + + // handle wiki links + handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); + + // handle bare links + handleAdvanced(advanced_link_regex, result, startIndex); + + // handle editor times + handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); + + // handle channels + handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel); + + var empty = ""; + while (space-- > 0) + empty += "\0"; + + handleMatches(emoji_regex, empty, "{0}", result, startIndex); + + return result; + } + + public static Message FormatMessage(Message inputMessage) + { + var result = format(inputMessage.Content); + + inputMessage.DisplayContent = result.Text; + + // Sometimes, regex matches are not in order + result.Links.Sort(); + inputMessage.Links = result.Links; + return inputMessage; + } + + public static MessageFormatterResult FormatText(string text) + { + var result = format(text); + + result.Links.Sort(); + + return result; + } + + public class MessageFormatterResult + { + public List Links = new List(); + public string Text; + public string OriginalText; + + public MessageFormatterResult(string text) + { + OriginalText = Text = text; + } + } + + public class LinkDetails + { + public LinkAction Action; + public string Argument; + + public LinkDetails(LinkAction action, string argument) + { + Action = action; + Argument = argument; + } + } + } + + public enum LinkAction + { + External, + OpenBeatmap, + OpenBeatmapSet, + OpenChannel, + OpenEditorTimestamp, + JoinMultiplayerMatch, + Spectate, + OpenUserProfile, + } + + public class Link : IComparable + { + public string Url; + public int Index; + public int Length; + public LinkAction Action; + public string Argument; + + public Link(string url, int startIndex, int length, LinkAction action, string argument) + { + Url = url; + Index = startIndex; + Length = length; + Action = action; + Argument = argument; + } + + public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; + } +} diff --git a/osu.Game/Online/Multiplayer/GameType.cs b/osu.Game/Online/Multiplayer/GameType.cs index 01588b05d3..571d3df681 100644 --- a/osu.Game/Online/Multiplayer/GameType.cs +++ b/osu.Game/Online/Multiplayer/GameType.cs @@ -1,146 +1,146 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; - -namespace osu.Game.Online.Multiplayer -{ - public abstract class GameType - { - public abstract string Name { get; } - public abstract Drawable GetIcon(OsuColour colours, float size); - } - - public class GameTypeTag : GameType - { - public override string Name => "Tag"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.fa_refresh, - Size = new Vector2(size), - Colour = colours.Blue, - Shadow = false, - }; - } - } - - public class GameTypeVersus : GameType - { - public override string Name => "Versus"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new VersusRow(colours.Blue, colours.Blue, size * 0.6f) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - } - } - - public class GameTypeTagTeam : GameType - { - public override string Name => "Tag Team"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f), - Children = new[] - { - new SpriteIcon - { - Icon = FontAwesome.fa_refresh, - Size = new Vector2(size * 0.75f), - Colour = colours.Blue, - Shadow = false, - }, - new SpriteIcon - { - Icon = FontAwesome.fa_refresh, - Size = new Vector2(size * 0.75f), - Colour = colours.Pink, - Shadow = false, - }, - }, - }; - } - } - - public class GameTypeTeamVersus : GameType - { - public override string Name => "Team Versus"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2f), - Children = new[] - { - new VersusRow(colours.Blue, colours.Pink, size * 0.5f), - new VersusRow(colours.Blue, colours.Pink, size * 0.5f), - }, - }; - } - } - - public class VersusRow : FillFlowContainer - { - public VersusRow(Color4 first, Color4 second, float size) - { - var triangleSize = new Vector2(size); - AutoSizeAxes = Axes.Both; - Spacing = new Vector2(2f, 0f); - - Children = new[] - { - new Container - { - Size = triangleSize, - Colour = first, - Children = new[] - { - new EquilateralTriangle - { - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Rotation = 90, - EdgeSmoothness = new Vector2(1f), - }, - }, - }, - new Container - { - Size = triangleSize, - Colour = second, - Children = new[] - { - new EquilateralTriangle - { - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Rotation = -90, - EdgeSmoothness = new Vector2(1f), - }, - }, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Online.Multiplayer +{ + public abstract class GameType + { + public abstract string Name { get; } + public abstract Drawable GetIcon(OsuColour colours, float size); + } + + public class GameTypeTag : GameType + { + public override string Name => "Tag"; + public override Drawable GetIcon(OsuColour colours, float size) + { + return new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_refresh, + Size = new Vector2(size), + Colour = colours.Blue, + Shadow = false, + }; + } + } + + public class GameTypeVersus : GameType + { + public override string Name => "Versus"; + public override Drawable GetIcon(OsuColour colours, float size) + { + return new VersusRow(colours.Blue, colours.Blue, size * 0.6f) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } + + public class GameTypeTagTeam : GameType + { + public override string Name => "Tag Team"; + public override Drawable GetIcon(OsuColour colours, float size) + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f), + Children = new[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_refresh, + Size = new Vector2(size * 0.75f), + Colour = colours.Blue, + Shadow = false, + }, + new SpriteIcon + { + Icon = FontAwesome.fa_refresh, + Size = new Vector2(size * 0.75f), + Colour = colours.Pink, + Shadow = false, + }, + }, + }; + } + } + + public class GameTypeTeamVersus : GameType + { + public override string Name => "Team Versus"; + public override Drawable GetIcon(OsuColour colours, float size) + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2f), + Children = new[] + { + new VersusRow(colours.Blue, colours.Pink, size * 0.5f), + new VersusRow(colours.Blue, colours.Pink, size * 0.5f), + }, + }; + } + } + + public class VersusRow : FillFlowContainer + { + public VersusRow(Color4 first, Color4 second, float size) + { + var triangleSize = new Vector2(size); + AutoSizeAxes = Axes.Both; + Spacing = new Vector2(2f, 0f); + + Children = new[] + { + new Container + { + Size = triangleSize, + Colour = first, + Children = new[] + { + new EquilateralTriangle + { + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Rotation = 90, + EdgeSmoothness = new Vector2(1f), + }, + }, + }, + new Container + { + Size = triangleSize, + Colour = second, + Children = new[] + { + new EquilateralTriangle + { + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Rotation = -90, + EdgeSmoothness = new Vector2(1f), + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 1971f9941c..f1c23e9e84 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Game.Beatmaps; -using osu.Game.Users; - -namespace osu.Game.Online.Multiplayer -{ - public class Room - { - public Bindable Name = new Bindable(); - public Bindable Host = new Bindable(); - public Bindable Status = new Bindable(); - public Bindable Type = new Bindable(); - public Bindable Beatmap = new Bindable(); - public Bindable MaxParticipants = new Bindable(); - public Bindable Participants = new Bindable(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Game.Beatmaps; +using osu.Game.Users; + +namespace osu.Game.Online.Multiplayer +{ + public class Room + { + public Bindable Name = new Bindable(); + public Bindable Host = new Bindable(); + public Bindable Status = new Bindable(); + public Bindable Type = new Bindable(); + public Bindable Beatmap = new Bindable(); + public Bindable MaxParticipants = new Bindable(); + public Bindable Participants = new Bindable(); + } +} diff --git a/osu.Game/Online/Multiplayer/RoomStatus.cs b/osu.Game/Online/Multiplayer/RoomStatus.cs index 1d82c8f6a8..6b82e5f059 100644 --- a/osu.Game/Online/Multiplayer/RoomStatus.cs +++ b/osu.Game/Online/Multiplayer/RoomStatus.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Game.Graphics; - -namespace osu.Game.Online.Multiplayer -{ - public abstract class RoomStatus - { - public abstract string Message { get; } - public abstract Color4 GetAppropriateColour(OsuColour colours); - } - - public class RoomStatusOpen : RoomStatus - { - public override string Message => @"Welcoming Players"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight; - } - - public class RoomStatusPlaying : RoomStatus - { - public override string Message => @"Now Playing"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Game.Graphics; + +namespace osu.Game.Online.Multiplayer +{ + public abstract class RoomStatus + { + public abstract string Message { get; } + public abstract Color4 GetAppropriateColour(OsuColour colours); + } + + public class RoomStatusOpen : RoomStatus + { + public override string Message => @"Welcoming Players"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight; + } + + public class RoomStatusPlaying : RoomStatus + { + public override string Message => @"Now Playing"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cc942a1e32..d4bc5e9185 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1,541 +1,545 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Configuration; -using osu.Framework.Screens; -using osu.Game.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; -using osu.Framework.Logging; -using osu.Framework.Allocation; -using osu.Game.Overlays.Toolbar; -using osu.Game.Screens; -using osu.Game.Screens.Menu; -using OpenTK; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using osu.Framework.Audio; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Platform; -using osu.Framework.Threading; -using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; -using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets; -using osu.Game.Screens.Play; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets.Mods; -using osu.Game.Skinning; -using OpenTK.Graphics; -using osu.Game.Overlays.Volume; - -namespace osu.Game -{ - public class OsuGame : OsuGameBase, IKeyBindingHandler - { - public Toolbar Toolbar; - - private ChatOverlay chat; - - private MusicController musicController; - - private NotificationOverlay notifications; - - private DialogOverlay dialogOverlay; - - private DirectOverlay direct; - - private SocialOverlay social; - - private UserProfileOverlay userProfile; - - private BeatmapSetOverlay beatmapSetOverlay; - - public virtual Storage GetStorageForStableInstall() => null; - - private Intro intro - { - get - { - Screen s = screenStack; - while (s != null && !(s is Intro)) - s = s.ChildScreen; - return s as Intro; - } - } - - public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; - - public readonly BindableBool ShowOverlays = new BindableBool(); - - private OsuScreen screenStack; - - private VolumeOverlay volume; - private OnScreenDisplay onscreenDisplay; - - private Bindable configRuleset; - public Bindable Ruleset = new Bindable(); - - private Bindable configSkin; - - private readonly string[] args; - - private SettingsOverlay settings; - - // todo: move this to SongSelect once Screen has the ability to unsuspend. - public readonly Bindable> SelectedMods = new Bindable>(new List()); - - public OsuGame(string[] args = null) - { - this.args = args; - } - - public void ToggleSettings() => settings.ToggleVisibility(); - - public void ToggleDirect() => direct.ToggleVisibility(); - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - - [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfig) - { - this.frameworkConfig = frameworkConfig; - - ScoreStore.ScoreImported += score => Schedule(() => LoadScore(score)); - - if (!Host.IsPrimaryInstance) - { - Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); - Environment.Exit(0); - } - - if (args?.Length > 0) - { - var paths = args.Where(a => !a.StartsWith(@"-")); - - Task.Run(() => Import(paths.ToArray())); - } - - dependencies.CacheAs(this); - - // bind config int to database RulesetInfo - configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); - Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); - Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; - - // bind config int to database SkinInfo - configSkin = LocalConfig.GetBindable(OsuSetting.Skin); - SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID; - configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == id) ?? SkinInfo.Default; - configSkin.TriggerChange(); - - LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); - } - - private ScheduledDelegate scoreLoad; - - /// - /// Show a beatmap set as an overlay. - /// - /// The set to display. - public void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); - - /// - /// Show a user's profile as an overlay. - /// - /// The user to display. - public void ShowUser(long userId) => userProfile.ShowUser(userId); - - protected void LoadScore(Score s) - { - scoreLoad?.Cancel(); - - var menu = intro.ChildScreen; - - if (menu == null) - { - scoreLoad = Schedule(() => LoadScore(s)); - return; - } - - if (!menu.IsCurrentScreen) - { - menu.MakeCurrent(); - this.Delay(500).Schedule(() => LoadScore(s), out scoreLoad); - return; - } - - if (s.Beatmap == null) - { - notifications.Post(new SimpleNotification - { - Text = @"Tried to load a score for a beatmap we don't have!", - Icon = FontAwesome.fa_life_saver, - }); - return; - } - - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap); - - menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay))); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // The next time this is updated is in UpdateAfterChildren, which occurs too late and results - // in the cursor being shown for a few frames during the intro. - // This prevents the cursor from showing until we have a screen with CursorVisible = true - CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; - - // hook up notifications to components. - SkinManager.PostNotification = n => notifications?.Post(n); - BeatmapManager.PostNotification = n => notifications?.Post(n); - - BeatmapManager.GetStableStorage = GetStorageForStableInstall; - - AddRange(new Drawable[] - { - new VolumeControlReceptor - { - RelativeSizeAxes = Axes.Both, - ActionRequested = action => volume.Adjust(action) - }, - mainContent = new Container { RelativeSizeAxes = Axes.Both }, - overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, - }); - - loadComponentSingleFile(screenStack = new Loader(), d => - { - screenStack.ModePushed += screenAdded; - screenStack.Exited += screenRemoved; - mainContent.Add(screenStack); - }); - - loadComponentSingleFile(Toolbar = new Toolbar - { - Depth = -5, - OnHome = delegate - { - hideAllOverlays(); - intro?.ChildScreen?.MakeCurrent(); - }, - }, overlayContent.Add); - - loadComponentSingleFile(volume = new VolumeOverlay(), overlayContent.Add); - loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); - loadComponentSingleFile(new ScreenshotManager(), Add); - - //overlay elements - loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(settings = new MainSettings - { - GetToolbarHeight = () => ToolbarOffset, - Depth = -1 - }, overlayContent.Add); - loadComponentSingleFile(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); - loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); - loadComponentSingleFile(musicController = new MusicController - { - Depth = -4, - Position = new Vector2(0, Toolbar.HEIGHT), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, overlayContent.Add); - - loadComponentSingleFile(notifications = new NotificationOverlay - { - GetToolbarHeight = () => ToolbarOffset, - Depth = -4, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, overlayContent.Add); - - loadComponentSingleFile(dialogOverlay = new DialogOverlay - { - Depth = -6, - }, overlayContent.Add); - - forwardLoggedErrorsToNotifications(); - - dependencies.Cache(settings); - dependencies.Cache(onscreenDisplay); - dependencies.Cache(social); - dependencies.Cache(direct); - dependencies.Cache(chat); - dependencies.Cache(userProfile); - dependencies.Cache(musicController); - dependencies.Cache(beatmapSetOverlay); - dependencies.Cache(notifications); - dependencies.Cache(dialogOverlay); - - // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct }; - foreach (var overlay in singleDisplayOverlays) - { - overlay.StateChanged += state => - { - if (state == Visibility.Hidden) return; - - foreach (var c in singleDisplayOverlays) - { - if (c == overlay) continue; - c.State = Visibility.Hidden; - } - }; - } - - var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications }; - foreach (var overlay in singleDisplaySideOverlays) - { - overlay.StateChanged += state => - { - if (state == Visibility.Hidden) return; - - foreach (var c in singleDisplaySideOverlays) - { - if (c == overlay) continue; - c.State = Visibility.Hidden; - } - }; - } - - // eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time. - var informationalOverlays = new OverlayContainer[] { beatmapSetOverlay, userProfile }; - foreach (var overlay in informationalOverlays) - { - overlay.StateChanged += state => - { - if (state == Visibility.Hidden) return; - - foreach (var c in informationalOverlays) - { - if (c == overlay) continue; - c.State = Visibility.Hidden; - } - }; - } - - void updateScreenOffset() - { - float offset = 0; - - if (settings.State == Visibility.Visible) - offset += ToolbarButton.WIDTH / 2; - if (notifications.State == Visibility.Visible) - offset -= ToolbarButton.WIDTH / 2; - - screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); - } - - settings.StateChanged += _ => updateScreenOffset(); - notifications.StateChanged += _ => updateScreenOffset(); - - notifications.Enabled.BindTo(ShowOverlays); - - ShowOverlays.ValueChanged += show => - { - //central game screen change logic. - if (!show) - { - hideAllOverlays(); - musicController.State = Visibility.Hidden; - Toolbar.State = Visibility.Hidden; - } - else - Toolbar.State = Visibility.Visible; - }; - } - - private void forwardLoggedErrorsToNotifications() - { - int recentErrorCount = 0; - - const double debounce = 5000; - - Logger.NewEntry += entry => - { - if (entry.Level < LogLevel.Error || entry.Target == null) return; - - if (recentErrorCount < 2) - { - notifications.Post(new SimpleNotification - { - Icon = FontAwesome.fa_bomb, - Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.", - Activated = () => - { - Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); - return true; - } - }); - } - - Interlocked.Increment(ref recentErrorCount); - - Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce); - }; - } - - private Task asyncLoadStream; - private int visibleOverlayCount; - - private void loadComponentSingleFile(T d, Action add) - where T : Drawable - { - var focused = d as FocusedOverlayContainer; - if (focused != null) - { - focused.StateChanged += s => - { - visibleOverlayCount += s == Visibility.Visible ? 1 : -1; - screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); - }; - } - - // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). - // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, - // we could avoid the need for scheduling altogether. - Schedule(() => { asyncLoadStream = asyncLoadStream?.ContinueWith(t => LoadComponentAsync(d, add).Wait()) ?? LoadComponentAsync(d, add); }); - } - - public bool OnPressed(GlobalAction action) - { - if (intro == null) return false; - - switch (action) - { - case GlobalAction.ToggleChat: - chat.ToggleVisibility(); - return true; - case GlobalAction.ToggleSocial: - social.ToggleVisibility(); - return true; - case GlobalAction.ResetInputSettings: - var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); - - sensitivity.Disabled = false; - sensitivity.Value = 1; - sensitivity.Disabled = true; - - frameworkConfig.Set(FrameworkSetting.ActiveInputHandlers, string.Empty); - frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); - return true; - case GlobalAction.ToggleToolbar: - Toolbar.ToggleVisibility(); - return true; - case GlobalAction.ToggleSettings: - settings.ToggleVisibility(); - return true; - case GlobalAction.ToggleDirect: - direct.ToggleVisibility(); - return true; - } - - return false; - } - - private readonly BindableDouble inactiveVolumeAdjust = new BindableDouble(); - - protected override void OnDeactivated() - { - base.OnDeactivated(); - Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeAdjust); - } - - protected override void OnActivated() - { - base.OnActivated(); - Audio.RemoveAdjustment(AdjustableProperty.Volume, inactiveVolumeAdjust); - } - - public bool OnReleased(GlobalAction action) => false; - - private Container mainContent; - - private Container overlayContent; - - private OsuScreen currentScreen; - private FrameworkConfigManager frameworkConfig; - - private void hideAllOverlays() - { - settings.State = Visibility.Hidden; - chat.State = Visibility.Hidden; - direct.State = Visibility.Hidden; - social.State = Visibility.Hidden; - userProfile.State = Visibility.Hidden; - notifications.State = Visibility.Hidden; - } - - protected override bool OnExiting() - { - if (screenStack.ChildScreen == null) return false; - - if (intro == null) return true; - - if (!intro.DidLoadMenu || intro.ChildScreen != null) - { - Scheduler.Add(intro.MakeCurrent); - return true; - } - - return base.OnExiting(); - } - - /// - /// Use to programatically exit the game as if the user was triggering via alt-f4. - /// Will keep persisting until an exit occurs (exit may be blocked multiple times). - /// - public void GracefullyExit() - { - if (!OnExiting()) - Exit(); - else - Scheduler.AddDelayed(GracefullyExit, 2000); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // we only want to apply these restrictions when we are inside a screen stack. - // the use case for not applying is in visual/unit tests. - bool applyRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false; - - Ruleset.Disabled = applyRestrictions; - Beatmap.Disabled = applyRestrictions; - - mainContent.Padding = new MarginPadding { Top = ToolbarOffset }; - - CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; - } - - private void screenAdded(Screen newScreen) - { - currentScreen = (OsuScreen)newScreen; - - newScreen.ModePushed += screenAdded; - newScreen.Exited += screenRemoved; - } - - private void screenRemoved(Screen newScreen) - { - currentScreen = (OsuScreen)newScreen; - - if (newScreen == null) - Exit(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Configuration; +using osu.Framework.Screens; +using osu.Game.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Framework.Logging; +using osu.Framework.Allocation; +using osu.Game.Overlays.Toolbar; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using OpenTK; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Audio; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Platform; +using osu.Framework.Threading; +using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; +using osu.Game.Skinning; +using OpenTK.Graphics; +using osu.Game.Overlays.Volume; + +namespace osu.Game +{ + /// + /// The full osu! experience. Builds on top of to add menus and binding logic + /// for initial components that are generally retrieved via DI. + /// + public class OsuGame : OsuGameBase, IKeyBindingHandler + { + public Toolbar Toolbar; + + private ChatOverlay chat; + + private MusicController musicController; + + private NotificationOverlay notifications; + + private DialogOverlay dialogOverlay; + + private DirectOverlay direct; + + private SocialOverlay social; + + private UserProfileOverlay userProfile; + + private BeatmapSetOverlay beatmapSetOverlay; + + public virtual Storage GetStorageForStableInstall() => null; + + private Intro intro + { + get + { + Screen s = screenStack; + while (s != null && !(s is Intro)) + s = s.ChildScreen; + return s as Intro; + } + } + + public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; + + public readonly BindableBool ShowOverlays = new BindableBool(); + + private OsuScreen screenStack; + + private VolumeOverlay volume; + private OnScreenDisplay onscreenDisplay; + + private Bindable configRuleset; + public Bindable Ruleset = new Bindable(); + + private Bindable configSkin; + + private readonly string[] args; + + private SettingsOverlay settings; + + // todo: move this to SongSelect once Screen has the ability to unsuspend. + public readonly Bindable> SelectedMods = new Bindable>(new List()); + + public OsuGame(string[] args = null) + { + this.args = args; + } + + public void ToggleSettings() => settings.ToggleVisibility(); + + public void ToggleDirect() => direct.ToggleVisibility(); + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => + dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfig) + { + this.frameworkConfig = frameworkConfig; + + ScoreStore.ScoreImported += score => Schedule(() => LoadScore(score)); + + if (!Host.IsPrimaryInstance) + { + Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); + Environment.Exit(0); + } + + if (args?.Length > 0) + { + var paths = args.Where(a => !a.StartsWith(@"-")); + + Task.Run(() => Import(paths.ToArray())); + } + + dependencies.CacheAs(this); + + // bind config int to database RulesetInfo + configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); + Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); + Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; + + // bind config int to database SkinInfo + configSkin = LocalConfig.GetBindable(OsuSetting.Skin); + SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID; + configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == id) ?? SkinInfo.Default; + configSkin.TriggerChange(); + + LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); + } + + private ScheduledDelegate scoreLoad; + + /// + /// Show a beatmap set as an overlay. + /// + /// The set to display. + public void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); + + /// + /// Show a user's profile as an overlay. + /// + /// The user to display. + public void ShowUser(long userId) => userProfile.ShowUser(userId); + + protected void LoadScore(Score s) + { + scoreLoad?.Cancel(); + + var menu = intro.ChildScreen; + + if (menu == null) + { + scoreLoad = Schedule(() => LoadScore(s)); + return; + } + + if (!menu.IsCurrentScreen) + { + menu.MakeCurrent(); + this.Delay(500).Schedule(() => LoadScore(s), out scoreLoad); + return; + } + + if (s.Beatmap == null) + { + notifications.Post(new SimpleNotification + { + Text = @"Tried to load a score for a beatmap we don't have!", + Icon = FontAwesome.fa_life_saver, + }); + return; + } + + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap); + + menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay))); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // The next time this is updated is in UpdateAfterChildren, which occurs too late and results + // in the cursor being shown for a few frames during the intro. + // This prevents the cursor from showing until we have a screen with CursorVisible = true + CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + + // hook up notifications to components. + SkinManager.PostNotification = n => notifications?.Post(n); + BeatmapManager.PostNotification = n => notifications?.Post(n); + + BeatmapManager.GetStableStorage = GetStorageForStableInstall; + + AddRange(new Drawable[] + { + new VolumeControlReceptor + { + RelativeSizeAxes = Axes.Both, + ActionRequested = action => volume.Adjust(action) + }, + mainContent = new Container { RelativeSizeAxes = Axes.Both }, + overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + }); + + loadComponentSingleFile(screenStack = new Loader(), d => + { + screenStack.ModePushed += screenAdded; + screenStack.Exited += screenRemoved; + mainContent.Add(screenStack); + }); + + loadComponentSingleFile(Toolbar = new Toolbar + { + Depth = -5, + OnHome = delegate + { + hideAllOverlays(); + intro?.ChildScreen?.MakeCurrent(); + }, + }, overlayContent.Add); + + loadComponentSingleFile(volume = new VolumeOverlay(), overlayContent.Add); + loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); + loadComponentSingleFile(new ScreenshotManager(), Add); + + //overlay elements + loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); + loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, mainContent.Add); + loadComponentSingleFile(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); + loadComponentSingleFile(settings = new MainSettings + { + GetToolbarHeight = () => ToolbarOffset, + Depth = -1 + }, overlayContent.Add); + loadComponentSingleFile(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); + loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); + loadComponentSingleFile(musicController = new MusicController + { + Depth = -4, + Position = new Vector2(0, Toolbar.HEIGHT), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, overlayContent.Add); + + loadComponentSingleFile(notifications = new NotificationOverlay + { + GetToolbarHeight = () => ToolbarOffset, + Depth = -4, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, overlayContent.Add); + + loadComponentSingleFile(dialogOverlay = new DialogOverlay + { + Depth = -6, + }, overlayContent.Add); + + forwardLoggedErrorsToNotifications(); + + dependencies.Cache(settings); + dependencies.Cache(onscreenDisplay); + dependencies.Cache(social); + dependencies.Cache(direct); + dependencies.Cache(chat); + dependencies.Cache(userProfile); + dependencies.Cache(musicController); + dependencies.Cache(beatmapSetOverlay); + dependencies.Cache(notifications); + dependencies.Cache(dialogOverlay); + + // ensure only one of these overlays are open at once. + var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct }; + foreach (var overlay in singleDisplayOverlays) + { + overlay.StateChanged += state => + { + if (state == Visibility.Hidden) return; + + foreach (var c in singleDisplayOverlays) + { + if (c == overlay) continue; + c.State = Visibility.Hidden; + } + }; + } + + var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications }; + foreach (var overlay in singleDisplaySideOverlays) + { + overlay.StateChanged += state => + { + if (state == Visibility.Hidden) return; + + foreach (var c in singleDisplaySideOverlays) + { + if (c == overlay) continue; + c.State = Visibility.Hidden; + } + }; + } + + // eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time. + var informationalOverlays = new OverlayContainer[] { beatmapSetOverlay, userProfile }; + foreach (var overlay in informationalOverlays) + { + overlay.StateChanged += state => + { + if (state == Visibility.Hidden) return; + + foreach (var c in informationalOverlays) + { + if (c == overlay) continue; + c.State = Visibility.Hidden; + } + }; + } + + void updateScreenOffset() + { + float offset = 0; + + if (settings.State == Visibility.Visible) + offset += ToolbarButton.WIDTH / 2; + if (notifications.State == Visibility.Visible) + offset -= ToolbarButton.WIDTH / 2; + + screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); + } + + settings.StateChanged += _ => updateScreenOffset(); + notifications.StateChanged += _ => updateScreenOffset(); + + notifications.Enabled.BindTo(ShowOverlays); + + ShowOverlays.ValueChanged += show => + { + //central game screen change logic. + if (!show) + { + hideAllOverlays(); + musicController.State = Visibility.Hidden; + Toolbar.State = Visibility.Hidden; + } + else + Toolbar.State = Visibility.Visible; + }; + } + + private void forwardLoggedErrorsToNotifications() + { + int recentErrorCount = 0; + + const double debounce = 5000; + + Logger.NewEntry += entry => + { + if (entry.Level < LogLevel.Error || entry.Target == null) return; + + if (recentErrorCount < 2) + { + notifications.Post(new SimpleNotification + { + Icon = FontAwesome.fa_bomb, + Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.", + Activated = () => + { + Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); + return true; + } + }); + } + + Interlocked.Increment(ref recentErrorCount); + + Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce); + }; + } + + private Task asyncLoadStream; + private int visibleOverlayCount; + + private void loadComponentSingleFile(T d, Action add) + where T : Drawable + { + var focused = d as FocusedOverlayContainer; + if (focused != null) + { + focused.StateChanged += s => + { + visibleOverlayCount += s == Visibility.Visible ? 1 : -1; + screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); + }; + } + + // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). + // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, + // we could avoid the need for scheduling altogether. + Schedule(() => { asyncLoadStream = asyncLoadStream?.ContinueWith(t => LoadComponentAsync(d, add).Wait()) ?? LoadComponentAsync(d, add); }); + } + + public bool OnPressed(GlobalAction action) + { + if (intro == null) return false; + + switch (action) + { + case GlobalAction.ToggleChat: + chat.ToggleVisibility(); + return true; + case GlobalAction.ToggleSocial: + social.ToggleVisibility(); + return true; + case GlobalAction.ResetInputSettings: + var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); + + sensitivity.Disabled = false; + sensitivity.Value = 1; + sensitivity.Disabled = true; + + frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); + frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); + return true; + case GlobalAction.ToggleToolbar: + Toolbar.ToggleVisibility(); + return true; + case GlobalAction.ToggleSettings: + settings.ToggleVisibility(); + return true; + case GlobalAction.ToggleDirect: + direct.ToggleVisibility(); + return true; + } + + return false; + } + + private readonly BindableDouble inactiveVolumeAdjust = new BindableDouble(); + + protected override void OnDeactivated() + { + base.OnDeactivated(); + Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeAdjust); + } + + protected override void OnActivated() + { + base.OnActivated(); + Audio.RemoveAdjustment(AdjustableProperty.Volume, inactiveVolumeAdjust); + } + + public bool OnReleased(GlobalAction action) => false; + + private Container mainContent; + + private Container overlayContent; + + private OsuScreen currentScreen; + private FrameworkConfigManager frameworkConfig; + + private void hideAllOverlays() + { + settings.State = Visibility.Hidden; + chat.State = Visibility.Hidden; + direct.State = Visibility.Hidden; + social.State = Visibility.Hidden; + userProfile.State = Visibility.Hidden; + notifications.State = Visibility.Hidden; + } + + protected override bool OnExiting() + { + if (screenStack.ChildScreen == null) return false; + + if (intro == null) return true; + + if (!intro.DidLoadMenu || intro.ChildScreen != null) + { + Scheduler.Add(intro.MakeCurrent); + return true; + } + + return base.OnExiting(); + } + + /// + /// Use to programatically exit the game as if the user was triggering via alt-f4. + /// Will keep persisting until an exit occurs (exit may be blocked multiple times). + /// + public void GracefullyExit() + { + if (!OnExiting()) + Exit(); + else + Scheduler.AddDelayed(GracefullyExit, 2000); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // we only want to apply these restrictions when we are inside a screen stack. + // the use case for not applying is in visual/unit tests. + bool applyRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false; + + Ruleset.Disabled = applyRestrictions; + Beatmap.Disabled = applyRestrictions; + + mainContent.Padding = new MarginPadding { Top = ToolbarOffset }; + + CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + } + + private void screenAdded(Screen newScreen) + { + currentScreen = (OsuScreen)newScreen; + + newScreen.ModePushed += screenAdded; + newScreen.Exited += screenRemoved; + } + + private void screenRemoved(Screen newScreen) + { + currentScreen = (OsuScreen)newScreen; + + if (newScreen == null) + Exit(); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 613755ea5d..3c34c0c108 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -1,258 +1,263 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Configuration; -using osu.Framework.Development; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.IO.Stores; -using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Cursor; -using osu.Game.Online.API; -using osu.Framework.Graphics.Performance; -using osu.Framework.Graphics.Textures; -using osu.Framework.Logging; -using osu.Game.Database; -using osu.Game.Graphics.Textures; -using osu.Game.Input; -using osu.Game.Input.Bindings; -using osu.Game.IO; -using osu.Game.Online.Chat; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; - -namespace osu.Game -{ - public class OsuGameBase : Framework.Game, ICanAcceptFiles - { - protected OsuConfigManager LocalConfig; - - protected BeatmapManager BeatmapManager; - - protected SkinManager SkinManager; - - protected RulesetStore RulesetStore; - - protected FileStore FileStore; - - protected ScoreStore ScoreStore; - - protected KeyBindingStore KeyBindingStore; - - protected SettingsStore SettingsStore; - - protected CursorOverrideContainer CursorOverrideContainer; - - protected override string MainResourceFile => @"osu.Game.Resources.dll"; - - private Container content; - - protected override Container Content => content; - - public Bindable Beatmap { get; private set; } - - private Bindable fpsDisplayVisible; - - protected AssemblyName AssemblyName => Assembly.GetEntryAssembly()?.GetName() ?? new AssemblyName { Version = new Version() }; - - public bool IsDeployedBuild => AssemblyName.Version.Major > 0; - - public string Version - { - get - { - if (!IsDeployedBuild) - return @"local " + (DebugUtils.IsDebug ? @"debug" : @"release"); - - var assembly = AssemblyName; - return $@"{assembly.Version.Major}.{assembly.Version.Minor}.{assembly.Version.Build}"; - } - } - - public OsuGameBase() - { - Name = @"osu!lazer"; - } - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - - private DatabaseContextFactory contextFactory; - - [BackgroundDependencyLoader] - private void load() - { - dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); - - dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); - - dependencies.CacheAs(this); - dependencies.Cache(LocalConfig); - - runMigrations(); - - dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); - dependencies.CacheAs(SkinManager); - - var api = new APIAccess(LocalConfig); - - dependencies.Cache(api); - dependencies.CacheAs(api); - - dependencies.Cache(new ChannelManager()); - - dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); - dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Audio, Host)); - dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore)); - dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); - dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); - dependencies.Cache(new OsuColour()); - - fileImporters.Add(BeatmapManager); - fileImporters.Add(ScoreStore); - fileImporters.Add(SkinManager); - - //this completely overrides the framework default. will need to change once we make a proper FontStore. - dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }); - - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic")); - - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Basic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Hangul")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Basic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Compatibility")); - - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Regular")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-RegularItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBold")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBoldItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Bold")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BoldItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Light")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-LightItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Black")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BlackItalic")); - - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light")); - - var defaultBeatmap = new DummyWorkingBeatmap(this); - Beatmap = new NonNullableBindable(defaultBeatmap); - BeatmapManager.DefaultBeatmap = defaultBeatmap; - - // tracks play so loud our samples can't keep up. - // this adds a global reduction of track volume for the time being. - Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); - - Beatmap.ValueChanged += b => - { - var trackLoaded = lastBeatmap?.TrackLoaded ?? false; - - // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) - if (!trackLoaded || lastBeatmap?.Track != b.Track) - { - if (trackLoaded) - { - Debug.Assert(lastBeatmap != null); - Debug.Assert(lastBeatmap.Track != null); - - lastBeatmap.RecycleTrack(); - } - - Audio.Track.AddItem(b.Track); - } - - lastBeatmap = b; - }; - - FileStore.Cleanup(); - - AddInternal(api); - } - - private void runMigrations() - { - try - { - using (var db = contextFactory.GetForWrite()) - db.Context.Migrate(); - } - catch (MigrationFailedException e) - { - Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); - - // if we failed, let's delete the database and start fresh. - // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. - contextFactory.ResetDatabase(); - Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); - - using (var db = contextFactory.GetForWrite()) - db.Context.Migrate(); - } - } - - private WorkingBeatmap lastBeatmap; - - protected override void LoadComplete() - { - base.LoadComplete(); - - GlobalActionContainer globalBinding; - - CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; - CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this) - { - RelativeSizeAxes = Axes.Both, - Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } - }; - - base.Content.Add(new DrawSizePreservingFillContainer { Child = CursorOverrideContainer }); - - KeyBindingStore.Register(globalBinding); - dependencies.Cache(globalBinding); - - // TODO: This is temporary until we reimplement the local FPS display. - // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. - fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += val => { FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; - fpsDisplayVisible.TriggerChange(); - } - - public override void SetHost(GameHost host) - { - if (LocalConfig == null) - LocalConfig = new OsuConfigManager(host.Storage); - base.SetHost(host); - } - - private readonly List fileImporters = new List(); - - public void Import(params string[] paths) - { - var extension = Path.GetExtension(paths.First()); - - foreach (var importer in fileImporters) - if (importer.HandledExtensions.Contains(extension)) importer.Import(paths); - } - - public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Configuration; +using osu.Framework.Development; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Cursor; +using osu.Game.Online.API; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; +using osu.Game.Database; +using osu.Game.Graphics.Textures; +using osu.Game.Input; +using osu.Game.Input.Bindings; +using osu.Game.IO; +using osu.Game.Online.Chat; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; + +namespace osu.Game +{ + /// + /// The most basic that can be used to host osu! components and systems. + /// Unlike , this class will not load any kind of UI, allowing it to be used + /// for provide dependencies to test cases without interfering with them. + /// + public class OsuGameBase : Framework.Game, ICanAcceptFiles + { + protected OsuConfigManager LocalConfig; + + protected BeatmapManager BeatmapManager; + + protected SkinManager SkinManager; + + protected RulesetStore RulesetStore; + + protected FileStore FileStore; + + protected ScoreStore ScoreStore; + + protected KeyBindingStore KeyBindingStore; + + protected SettingsStore SettingsStore; + + protected CursorOverrideContainer CursorOverrideContainer; + + protected override string MainResourceFile => @"osu.Game.Resources.dll"; + + private Container content; + + protected override Container Content => content; + + public Bindable Beatmap { get; private set; } + + private Bindable fpsDisplayVisible; + + protected AssemblyName AssemblyName => Assembly.GetEntryAssembly()?.GetName() ?? new AssemblyName { Version = new Version() }; + + public bool IsDeployedBuild => AssemblyName.Version.Major > 0; + + public string Version + { + get + { + if (!IsDeployedBuild) + return @"local " + (DebugUtils.IsDebug ? @"debug" : @"release"); + + var assembly = AssemblyName; + return $@"{assembly.Version.Major}.{assembly.Version.Minor}.{assembly.Version.Build}"; + } + } + + public OsuGameBase() + { + Name = @"osu!lazer"; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => + dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + private DatabaseContextFactory contextFactory; + + [BackgroundDependencyLoader] + private void load() + { + dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); + + dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); + + dependencies.CacheAs(this); + dependencies.Cache(LocalConfig); + + runMigrations(); + + dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); + dependencies.CacheAs(SkinManager); + + var api = new APIAccess(LocalConfig); + + dependencies.Cache(api); + dependencies.CacheAs(api); + + dependencies.Cache(new ChannelManager()); + + dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); + dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Audio, Host)); + dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore)); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); + dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); + dependencies.Cache(new OsuColour()); + + fileImporters.Add(BeatmapManager); + fileImporters.Add(ScoreStore); + fileImporters.Add(SkinManager); + + //this completely overrides the framework default. will need to change once we make a proper FontStore. + dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }); + + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic")); + + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Basic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Hangul")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Basic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Compatibility")); + + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Regular")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-RegularItalic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBold")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBoldItalic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Bold")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BoldItalic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Light")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-LightItalic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Black")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BlackItalic")); + + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light")); + + var defaultBeatmap = new DummyWorkingBeatmap(this); + Beatmap = new NonNullableBindable(defaultBeatmap); + BeatmapManager.DefaultBeatmap = defaultBeatmap; + + // tracks play so loud our samples can't keep up. + // this adds a global reduction of track volume for the time being. + Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); + + Beatmap.ValueChanged += b => + { + var trackLoaded = lastBeatmap?.TrackLoaded ?? false; + + // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) + if (!trackLoaded || lastBeatmap?.Track != b.Track) + { + if (trackLoaded) + { + Debug.Assert(lastBeatmap != null); + Debug.Assert(lastBeatmap.Track != null); + + lastBeatmap.RecycleTrack(); + } + + Audio.Track.AddItem(b.Track); + } + + lastBeatmap = b; + }; + + FileStore.Cleanup(); + + AddInternal(api); + } + + private void runMigrations() + { + try + { + using (var db = contextFactory.GetForWrite()) + db.Context.Migrate(); + } + catch (MigrationFailedException e) + { + Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); + + // if we failed, let's delete the database and start fresh. + // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. + contextFactory.ResetDatabase(); + Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); + + using (var db = contextFactory.GetForWrite()) + db.Context.Migrate(); + } + } + + private WorkingBeatmap lastBeatmap; + + protected override void LoadComplete() + { + base.LoadComplete(); + + GlobalActionContainer globalBinding; + + CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; + CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this) + { + RelativeSizeAxes = Axes.Both, + Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } + }; + + base.Content.Add(new DrawSizePreservingFillContainer { Child = CursorOverrideContainer }); + + KeyBindingStore.Register(globalBinding); + dependencies.Cache(globalBinding); + + // TODO: This is temporary until we reimplement the local FPS display. + // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. + fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); + fpsDisplayVisible.ValueChanged += val => { FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; + fpsDisplayVisible.TriggerChange(); + } + + public override void SetHost(GameHost host) + { + if (LocalConfig == null) + LocalConfig = new OsuConfigManager(host.Storage); + base.SetHost(host); + } + + private readonly List fileImporters = new List(); + + public void Import(params string[] paths) + { + var extension = Path.GetExtension(paths.First()); + + foreach (var importer in fileImporters) + if (importer.HandledExtensions.Contains(extension)) importer.Import(paths); + } + + public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); + } +} diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index eec69214de..8b19bca671 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -1,131 +1,131 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Game.Graphics.Containers; -using osu.Framework.Graphics.Cursor; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class AuthorInfo : Container - { - private const float height = 50; - - private readonly UpdateableAvatar avatar; - private readonly ClickableArea clickableArea; - private readonly FillFlowContainer fields; - - private UserProfileOverlay profile; - - private BeatmapSetInfo beatmapSet; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - var i = BeatmapSet.OnlineInfo; - - avatar.User = BeatmapSet.Metadata.Author; - clickableArea.Action = () => profile?.ShowUser(avatar.User); - - fields.Children = new Drawable[] - { - new Field("made by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"), - new Field("submitted on", i.Submitted.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold") - { - Margin = new MarginPadding { Top = 5 }, - }, - }; - - if (i.Ranked.HasValue) - { - fields.Add(new Field("ranked on ", i.Ranked.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); - } - else if (i.LastUpdated.HasValue) - { - fields.Add(new Field("last updated on ", i.LastUpdated.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); - } - } - } - - public AuthorInfo() - { - RelativeSizeAxes = Axes.X; - Height = height; - - Children = new Drawable[] - { - clickableArea = new ClickableArea - { - AutoSizeAxes = Axes.Both, - CornerRadius = 3, - Masking = true, - Child = avatar = new UpdateableAvatar - { - Size = new Vector2(height), - }, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }, - }, - fields = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Left = height + 5 }, - }, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) - { - this.profile = profile; - clickableArea.Action = () => profile?.ShowUser(avatar.User); - } - - private class Field : FillFlowContainer - { - public Field(string first, string second, string secondFont) - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - - Children = new[] - { - new OsuSpriteText - { - Text = $"{first} ", - TextSize = 13, - }, - new OsuSpriteText - { - Text = second, - TextSize = 13, - Font = secondFont, - }, - }; - } - } - - private class ClickableArea : OsuClickableContainer, IHasTooltip - { - public string TooltipText => @"View Profile"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class AuthorInfo : Container + { + private const float height = 50; + + private readonly UpdateableAvatar avatar; + private readonly ClickableArea clickableArea; + private readonly FillFlowContainer fields; + + private UserProfileOverlay profile; + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + var i = BeatmapSet.OnlineInfo; + + avatar.User = BeatmapSet.Metadata.Author; + clickableArea.Action = () => profile?.ShowUser(avatar.User); + + fields.Children = new Drawable[] + { + new Field("made by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"), + new Field("submitted on", i.Submitted.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold") + { + Margin = new MarginPadding { Top = 5 }, + }, + }; + + if (i.Ranked.HasValue) + { + fields.Add(new Field("ranked on ", i.Ranked.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); + } + else if (i.LastUpdated.HasValue) + { + fields.Add(new Field("last updated on ", i.LastUpdated.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); + } + } + } + + public AuthorInfo() + { + RelativeSizeAxes = Axes.X; + Height = height; + + Children = new Drawable[] + { + clickableArea = new ClickableArea + { + AutoSizeAxes = Axes.Both, + CornerRadius = 3, + Masking = true, + Child = avatar = new UpdateableAvatar + { + Size = new Vector2(height), + }, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }, + }, + fields = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Left = height + 5 }, + }, + }; + } + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profile) + { + this.profile = profile; + clickableArea.Action = () => profile?.ShowUser(avatar.User); + } + + private class Field : FillFlowContainer + { + public Field(string first, string second, string secondFont) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + + Children = new[] + { + new OsuSpriteText + { + Text = $"{first} ", + TextSize = 13, + }, + new OsuSpriteText + { + Text = second, + TextSize = 13, + Font = secondFont, + }, + }; + } + } + + private class ClickableArea : OsuClickableContainer, IHasTooltip + { + public string TooltipText => @"View Profile"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 1b8fff36ec..8fd34914a7 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -1,130 +1,130 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class BasicStats : Container - { - private readonly Statistic length, bpm, circleCount, sliderCount; - - private BeatmapSetInfo beatmapSet; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - bpm.Value = BeatmapSet.OnlineInfo.BPM.ToString(@"0.##"); - } - } - - private BeatmapInfo beatmap; - public BeatmapInfo Beatmap - { - get { return beatmap; } - set - { - if (value == beatmap) return; - beatmap = value; - - length.Value = TimeSpan.FromSeconds(beatmap.OnlineInfo.Length).ToString(@"m\:ss"); - circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString(); - sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); - } - } - - public BasicStats() - { - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new[] - { - length = new Statistic(FontAwesome.fa_clock_o, "Length") { Width = 0.25f }, - bpm = new Statistic(FontAwesome.fa_circle, "BPM") { Width = 0.25f }, - circleCount = new Statistic(FontAwesome.fa_circle_o, "Circle Count") { Width = 0.25f }, - sliderCount = new Statistic(FontAwesome.fa_circle, "Slider Count") { Width = 0.25f }, - }, - }; - } - - private class Statistic : Container, IHasTooltip - { - private readonly string name; - private readonly OsuSpriteText value; - - public string TooltipText => name; - public string Value - { - get { return value.Text; } - set { this.value.Text = value; } - } - - public Statistic(FontAwesome icon, string name) - { - this.name = name; - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Children = new Drawable[] - { - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - Icon = FontAwesome.fa_square, - Size = new Vector2(13), - Rotation = 45, - Colour = OsuColour.FromHex(@"441288"), - }, - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - Icon = icon, - Size = new Vector2(13), - Colour = OsuColour.FromHex(@"f7dd55"), - Scale = new Vector2(0.8f), - }, - value = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = 13, - Font = @"Exo2.0-Bold", - Margin = new MarginPadding { Left = 10 }, - }, - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - value.Colour = colour.Yellow; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BasicStats : Container + { + private readonly Statistic length, bpm, circleCount, sliderCount; + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + bpm.Value = BeatmapSet.OnlineInfo.BPM.ToString(@"0.##"); + } + } + + private BeatmapInfo beatmap; + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + if (value == beatmap) return; + beatmap = value; + + length.Value = TimeSpan.FromSeconds(beatmap.OnlineInfo.Length).ToString(@"m\:ss"); + circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString(); + sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); + } + } + + public BasicStats() + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new[] + { + length = new Statistic(FontAwesome.fa_clock_o, "Length") { Width = 0.25f }, + bpm = new Statistic(FontAwesome.fa_circle, "BPM") { Width = 0.25f }, + circleCount = new Statistic(FontAwesome.fa_circle_o, "Circle Count") { Width = 0.25f }, + sliderCount = new Statistic(FontAwesome.fa_circle, "Slider Count") { Width = 0.25f }, + }, + }; + } + + private class Statistic : Container, IHasTooltip + { + private readonly string name; + private readonly OsuSpriteText value; + + public string TooltipText => name; + public string Value + { + get { return value.Text; } + set { this.value.Text = value; } + } + + public Statistic(FontAwesome icon, string name) + { + this.name = name; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_square, + Size = new Vector2(13), + Rotation = 45, + Colour = OsuColour.FromHex(@"441288"), + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Icon = icon, + Size = new Vector2(13), + Colour = OsuColour.FromHex(@"f7dd55"), + Scale = new Vector2(0.8f), + }, + value = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = 13, + Font = @"Exo2.0-Bold", + Margin = new MarginPadding { Left = 10 }, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + value.Colour = colour.Yellow; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index b09e151ebc..87dc9b104b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -1,312 +1,312 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class BeatmapPicker : Container - { - private const float tile_icon_padding = 7; - private const float tile_spacing = 2; - - private readonly DifficultiesContainer difficulties; - private readonly OsuSpriteText version, starRating; - private readonly Statistic plays, favourites; - - public readonly Bindable Beatmap = new Bindable(); - - private BeatmapSetInfo beatmapSet; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - Beatmap.Value = BeatmapSet.Beatmaps.First(); - plays.Value = BeatmapSet.OnlineInfo.PlayCount; - favourites.Value = BeatmapSet.OnlineInfo.FavouriteCount; - difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Select(b => new DifficultySelectorButton(b) - { - State = DifficultySelectorState.NotSelected, - OnHovered = beatmap => - { - showBeatmap(beatmap); - starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); - starRating.FadeIn(100); - }, - OnClicked = beatmap => - { - Beatmap.Value = beatmap; - }, - }); - - updateDifficultyButtons(); - } - } - - public BeatmapPicker() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - difficulties = new DifficultiesContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2) }, - OnLostHover = () => - { - showBeatmap(Beatmap.Value); - starRating.FadeOut(100); - }, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 10 }, - Spacing = new Vector2(5f), - Children = new[] - { - version = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - TextSize = 20, - Font = @"Exo2.0-Bold", - }, - starRating = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - TextSize = 13, - Font = @"Exo2.0-Bold", - Text = "Star Difficulty", - Alpha = 0, - Margin = new MarginPadding { Bottom = 1 }, - }, - }, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10f), - Margin = new MarginPadding { Top = 5 }, - Children = new[] - { - plays = new Statistic(FontAwesome.fa_play_circle), - favourites = new Statistic(FontAwesome.fa_heart), - }, - }, - }, - }, - }; - - Beatmap.ValueChanged += b => - { - showBeatmap(b); - updateDifficultyButtons(); - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - starRating.Colour = colours.Yellow; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // done here so everything can bind in intialization and get the first trigger - Beatmap.TriggerChange(); - } - - private void showBeatmap(BeatmapInfo beatmap) => version.Text = beatmap.Version; - - private void updateDifficultyButtons() - { - difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); - } - - private class DifficultiesContainer : FillFlowContainer - { - public Action OnLostHover; - - protected override void OnHoverLost(InputState state) - { - base.OnHoverLost(state); - OnLostHover?.Invoke(); - } - } - - private class DifficultySelectorButton : OsuClickableContainer, IStateful - { - private const float transition_duration = 100; - private const float size = 52; - - private readonly Container bg; - private readonly DifficultyIcon icon; - - public readonly BeatmapInfo Beatmap; - - public Action OnHovered; - public Action OnClicked; - public event Action StateChanged; - - private DifficultySelectorState state; - public DifficultySelectorState State - { - get { return state; } - set - { - if (value == state) return; - state = value; - - StateChanged?.Invoke(State); - if (value == DifficultySelectorState.Selected) - fadeIn(); - else - fadeOut(); - } - } - - public DifficultySelectorButton(BeatmapInfo beatmap) - { - Beatmap = beatmap; - Size = new Vector2(size); - Margin = new MarginPadding { Horizontal = tile_spacing / 2 }; - - Children = new Drawable[] - { - bg = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - }, - icon = new DifficultyIcon(beatmap) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(size - tile_icon_padding * 2), - Margin = new MarginPadding { Bottom = 1 }, - }, - }; - } - - protected override bool OnHover(InputState state) - { - fadeIn(); - OnHovered?.Invoke(Beatmap); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - if (State == DifficultySelectorState.NotSelected) - fadeOut(); - base.OnHoverLost(state); - } - - protected override bool OnClick(InputState state) - { - OnClicked?.Invoke(Beatmap); - return base.OnClick(state); - } - - private void fadeIn() - { - bg.FadeIn(transition_duration); - icon.FadeIn(transition_duration); - } - - private void fadeOut() - { - bg.FadeOut(); - icon.FadeTo(0.7f, transition_duration); - } - } - - private class Statistic : FillFlowContainer - { - private readonly OsuSpriteText text; - - private int value; - public int Value - { - get { return value; } - set - { - this.value = value; - text.Text = Value.ToString(@"N0"); - } - } - - public Statistic(FontAwesome icon) - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - Spacing = new Vector2(2f); - - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = icon, - Shadow = true, - Size = new Vector2(13), - }, - text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = @"Exo2.0-SemiBoldItalic", - TextSize = 14, - }, - }; - } - } - - private enum DifficultySelectorState - { - Selected, - NotSelected, - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapPicker : Container + { + private const float tile_icon_padding = 7; + private const float tile_spacing = 2; + + private readonly DifficultiesContainer difficulties; + private readonly OsuSpriteText version, starRating; + private readonly Statistic plays, favourites; + + public readonly Bindable Beatmap = new Bindable(); + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + Beatmap.Value = BeatmapSet.Beatmaps.First(); + plays.Value = BeatmapSet.OnlineInfo.PlayCount; + favourites.Value = BeatmapSet.OnlineInfo.FavouriteCount; + difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty).Select(b => new DifficultySelectorButton(b) + { + State = DifficultySelectorState.NotSelected, + OnHovered = beatmap => + { + showBeatmap(beatmap); + starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); + starRating.FadeIn(100); + }, + OnClicked = beatmap => + { + Beatmap.Value = beatmap; + }, + }); + + updateDifficultyButtons(); + } + } + + public BeatmapPicker() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + difficulties = new DifficultiesContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2) }, + OnLostHover = () => + { + showBeatmap(Beatmap.Value); + starRating.FadeOut(100); + }, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 10 }, + Spacing = new Vector2(5f), + Children = new[] + { + version = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + TextSize = 20, + Font = @"Exo2.0-Bold", + }, + starRating = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + TextSize = 13, + Font = @"Exo2.0-Bold", + Text = "Star Difficulty", + Alpha = 0, + Margin = new MarginPadding { Bottom = 1 }, + }, + }, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10f), + Margin = new MarginPadding { Top = 5 }, + Children = new[] + { + plays = new Statistic(FontAwesome.fa_play_circle), + favourites = new Statistic(FontAwesome.fa_heart), + }, + }, + }, + }, + }; + + Beatmap.ValueChanged += b => + { + showBeatmap(b); + updateDifficultyButtons(); + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + starRating.Colour = colours.Yellow; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // done here so everything can bind in intialization and get the first trigger + Beatmap.TriggerChange(); + } + + private void showBeatmap(BeatmapInfo beatmap) => version.Text = beatmap.Version; + + private void updateDifficultyButtons() + { + difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); + } + + private class DifficultiesContainer : FillFlowContainer + { + public Action OnLostHover; + + protected override void OnHoverLost(InputState state) + { + base.OnHoverLost(state); + OnLostHover?.Invoke(); + } + } + + private class DifficultySelectorButton : OsuClickableContainer, IStateful + { + private const float transition_duration = 100; + private const float size = 52; + + private readonly Container bg; + private readonly DifficultyIcon icon; + + public readonly BeatmapInfo Beatmap; + + public Action OnHovered; + public Action OnClicked; + public event Action StateChanged; + + private DifficultySelectorState state; + public DifficultySelectorState State + { + get { return state; } + set + { + if (value == state) return; + state = value; + + StateChanged?.Invoke(State); + if (value == DifficultySelectorState.Selected) + fadeIn(); + else + fadeOut(); + } + } + + public DifficultySelectorButton(BeatmapInfo beatmap) + { + Beatmap = beatmap; + Size = new Vector2(size); + Margin = new MarginPadding { Horizontal = tile_spacing / 2 }; + + Children = new Drawable[] + { + bg = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + }, + icon = new DifficultyIcon(beatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(size - tile_icon_padding * 2), + Margin = new MarginPadding { Bottom = 1 }, + }, + }; + } + + protected override bool OnHover(InputState state) + { + fadeIn(); + OnHovered?.Invoke(Beatmap); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if (State == DifficultySelectorState.NotSelected) + fadeOut(); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + OnClicked?.Invoke(Beatmap); + return base.OnClick(state); + } + + private void fadeIn() + { + bg.FadeIn(transition_duration); + icon.FadeIn(transition_duration); + } + + private void fadeOut() + { + bg.FadeOut(); + icon.FadeTo(0.7f, transition_duration); + } + } + + private class Statistic : FillFlowContainer + { + private readonly OsuSpriteText text; + + private int value; + public int Value + { + get { return value; } + set + { + this.value = value; + text.Text = Value.ToString(@"N0"); + } + } + + public Statistic(FontAwesome icon) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(2f); + + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = icon, + Shadow = true, + Size = new Vector2(13), + }, + text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = @"Exo2.0-SemiBoldItalic", + TextSize = 14, + }, + }; + } + } + + private enum DifficultySelectorState + { + Selected, + NotSelected, + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 7f052f443f..7f3b6d1584 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -1,120 +1,120 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Screens.Select.Details; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class Details : FillFlowContainer - { - private readonly PreviewButton preview; - private readonly BasicStats basic; - private readonly AdvancedStats advanced; - private readonly UserRatings ratings; - - private BeatmapSetInfo beatmapSet; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - basic.BeatmapSet = preview.BeatmapSet = BeatmapSet; - } - } - - private BeatmapInfo beatmap; - public BeatmapInfo Beatmap - { - get { return beatmap; } - set - { - if (value == beatmap) return; - beatmap = value; - - basic.Beatmap = advanced.Beatmap = Beatmap; - ratings.Metrics = Beatmap.Metrics; - } - } - - public Details() - { - Width = BeatmapSetOverlay.RIGHT_WIDTH; - AutoSizeAxes = Axes.Y; - Spacing = new Vector2(1f); - - Children = new Drawable[] - { - preview = new PreviewButton - { - RelativeSizeAxes = Axes.X, - }, - new DetailBox - { - Child = basic = new BasicStats - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Vertical = 10 }, - }, - }, - new DetailBox - { - Child = advanced = new AdvancedStats - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Vertical = 7.5f }, - }, - }, - new DetailBox - { - Child = ratings = new UserRatings - { - RelativeSizeAxes = Axes.X, - Height = 95, - Margin = new MarginPadding { Top = 10 }, - }, - }, - }; - } - - public void StopPreview() => preview.Playing.Value = false; - - private class DetailBox : Container - { - private readonly Container content; - protected override Container Content => content; - - public DetailBox() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 15 }, - }, - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Details; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class Details : FillFlowContainer + { + private readonly PreviewButton preview; + private readonly BasicStats basic; + private readonly AdvancedStats advanced; + private readonly UserRatings ratings; + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + basic.BeatmapSet = preview.BeatmapSet = BeatmapSet; + } + } + + private BeatmapInfo beatmap; + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + if (value == beatmap) return; + beatmap = value; + + basic.Beatmap = advanced.Beatmap = Beatmap; + ratings.Metrics = Beatmap.Metrics; + } + } + + public Details() + { + Width = BeatmapSetOverlay.RIGHT_WIDTH; + AutoSizeAxes = Axes.Y; + Spacing = new Vector2(1f); + + Children = new Drawable[] + { + preview = new PreviewButton + { + RelativeSizeAxes = Axes.X, + }, + new DetailBox + { + Child = basic = new BasicStats + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Vertical = 10 }, + }, + }, + new DetailBox + { + Child = advanced = new AdvancedStats + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Vertical = 7.5f }, + }, + }, + new DetailBox + { + Child = ratings = new UserRatings + { + RelativeSizeAxes = Axes.X, + Height = 95, + Margin = new MarginPadding { Top = 10 }, + }, + }, + }; + } + + public void StopPreview() => preview.Playing.Value = false; + + private class DetailBox : Container + { + private readonly Container content; + protected override Container Content => content; + + public DetailBox() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 15 }, + }, + }; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/DownloadButton.cs index 9691036b87..0c6414c718 100644 --- a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/DownloadButton.cs @@ -1,59 +1,59 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class DownloadButton : HeaderButton - { - public DownloadButton(string title, string subtitle) - { - Width = 120; - - Add(new Container - { - Depth = -1, - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 10 }, - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Text = title, - TextSize = 13, - Font = @"Exo2.0-Bold", - }, - new OsuSpriteText - { - Text = subtitle, - TextSize = 11, - Font = @"Exo2.0-Bold", - }, - }, - }, - new SpriteIcon - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Icon = FontAwesome.fa_download, - Size = new Vector2(16), - Margin = new MarginPadding { Right = 5 }, - }, - }, - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class DownloadButton : HeaderButton + { + public DownloadButton(string title, string subtitle) + { + Width = 120; + + Add(new Container + { + Depth = -1, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 10 }, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new[] + { + new OsuSpriteText + { + Text = title, + TextSize = 13, + Font = @"Exo2.0-Bold", + }, + new OsuSpriteText + { + Text = subtitle, + TextSize = 11, + Font = @"Exo2.0-Bold", + }, + }, + }, + new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Icon = FontAwesome.fa_download, + Size = new Vector2(16), + Margin = new MarginPadding { Right = 5 }, + }, + }, + }); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs index 51492fff44..29bc00038c 100644 --- a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs @@ -1,80 +1,80 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using OpenTK; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class FavouriteButton : HeaderButton - { - public readonly Bindable Favourited = new Bindable(); - - [BackgroundDependencyLoader] - private void load() - { - Container pink; - SpriteIcon icon; - AddRange(new Drawable[] - { - pink = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0f, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"9f015f"), - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourLight = OsuColour.FromHex(@"cb2187"), - ColourDark = OsuColour.FromHex(@"9f015f"), - TriangleScale = 1.5f, - }, - }, - }, - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.fa_heart_o, - Size = new Vector2(18), - Shadow = false, - }, - }); - - Favourited.ValueChanged += value => - { - if (value) - { - pink.FadeIn(200); - icon.Icon = FontAwesome.fa_heart; - } - else - { - pink.FadeOut(200); - icon.Icon = FontAwesome.fa_heart_o; - } - }; - - Action = () => Favourited.Value = !Favourited.Value; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - Width = DrawHeight; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using OpenTK; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class FavouriteButton : HeaderButton + { + public readonly Bindable Favourited = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { + Container pink; + SpriteIcon icon; + AddRange(new Drawable[] + { + pink = new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"9f015f"), + }, + new Triangles + { + RelativeSizeAxes = Axes.Both, + ColourLight = OsuColour.FromHex(@"cb2187"), + ColourDark = OsuColour.FromHex(@"9f015f"), + TriangleScale = 1.5f, + }, + }, + }, + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_heart_o, + Size = new Vector2(18), + Shadow = false, + }, + }); + + Favourited.ValueChanged += value => + { + if (value) + { + pink.FadeIn(200); + icon.Icon = FontAwesome.fa_heart; + } + else + { + pink.FadeOut(200); + icon.Icon = FontAwesome.fa_heart_o; + } + }; + + Action = () => Favourited.Value = !Favourited.Value; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Width = DrawHeight; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 8056dbff3c..755039e7bc 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -1,270 +1,270 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class Header : Container - { - private const float transition_duration = 250; - private const float tabs_height = 50; - private const float buttons_height = 45; - private const float buttons_spacing = 5; - - private readonly Box tabsBg; - private readonly Container coverContainer; - private readonly OsuSpriteText title, artist; - private readonly Container noVideoButtons; - private readonly FillFlowContainer videoButtons; - private readonly AuthorInfo author; - private readonly Container downloadButtonsContainer; - private readonly BeatmapSetOnlineStatusPill onlineStatusPill; - public Details Details; - - private BeatmapManager beatmaps; - - private DelayedLoadWrapper cover; - - public readonly BeatmapPicker Picker; - - private BeatmapSetInfo beatmapSet; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - Picker.BeatmapSet = author.BeatmapSet = Details.BeatmapSet = BeatmapSet; - title.Text = BeatmapSet.Metadata.Title; - artist.Text = BeatmapSet.Metadata.Artist; - onlineStatusPill.Status = BeatmapSet.OnlineInfo.Status; - - downloadButtonsContainer.FadeIn(); - noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration); - videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration); - - cover?.FadeOut(400, Easing.Out); - coverContainer.Add(cover = new DelayedLoadWrapper( - new BeatmapSetCover(BeatmapSet) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), - }, 300) - { - RelativeSizeAxes = Axes.Both, - }); - } - } - - public Header() - { - RelativeSizeAxes = Axes.X; - Height = 400; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }; - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = tabs_height, - Children = new[] - { - tabsBg = new Box - { - RelativeSizeAxes = Axes.Both, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = tabs_height }, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - coverContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.3f), Color4.Black.Opacity(0.8f)), - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 20, Bottom = 30, Horizontal = BeatmapSetOverlay.X_PADDING }, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = 113, - Child = Picker = new BeatmapPicker(), - }, - title = new OsuSpriteText - { - Font = @"Exo2.0-BoldItalic", - TextSize = 37, - }, - artist = new OsuSpriteText - { - Font = @"Exo2.0-SemiBoldItalic", - TextSize = 25, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 20 }, - Child = author = new AuthorInfo(), - }, - new Container - { - RelativeSizeAxes = Axes.X, - Height = buttons_height, - Margin = new MarginPadding { Top = 10 }, - Children = new Drawable[] - { - new FavouriteButton(), - downloadButtonsContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = buttons_height + buttons_spacing }, - Children = new Drawable[] - { - noVideoButtons = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0f, - Child = new DownloadButton("Download", @"") - { - Action = () => download(false), - }, - }, - videoButtons = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Spacing = new Vector2(buttons_spacing), - Alpha = 0f, - Children = new[] - { - new DownloadButton("Download", "with Video") - { - Action = () => download(false), - }, - new DownloadButton("Download", "without Video") - { - Action = () => download(true), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - new FillFlowContainer - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = BeatmapSetOverlay.X_PADDING }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(10), - Children = new Drawable[] - { - onlineStatusPill = new BeatmapSetOnlineStatusPill(14, new MarginPadding { Horizontal = 25, Vertical = 8 }) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - Details = new Details(), - }, - }, - }, - }, - }; - - Picker.Beatmap.ValueChanged += b => Details.Beatmap = b; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapManager beatmaps) - { - tabsBg.Colour = colours.Gray3; - this.beatmaps = beatmaps; - - beatmaps.ItemAdded += handleBeatmapAdd; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd; - } - - private void handleBeatmapAdd(BeatmapSetInfo beatmap) => Schedule(() => - { - if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID) - downloadButtonsContainer.FadeOut(transition_duration); - }); - - private void download(bool noVideo) - { - if (beatmaps.GetExistingDownload(BeatmapSet) != null) - { - downloadButtonsContainer.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine).Then(); - - return; - } - - beatmaps.Download(BeatmapSet, noVideo); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class Header : Container + { + private const float transition_duration = 250; + private const float tabs_height = 50; + private const float buttons_height = 45; + private const float buttons_spacing = 5; + + private readonly Box tabsBg; + private readonly Container coverContainer; + private readonly OsuSpriteText title, artist; + private readonly Container noVideoButtons; + private readonly FillFlowContainer videoButtons; + private readonly AuthorInfo author; + private readonly Container downloadButtonsContainer; + private readonly BeatmapSetOnlineStatusPill onlineStatusPill; + public Details Details; + + private BeatmapManager beatmaps; + + private DelayedLoadWrapper cover; + + public readonly BeatmapPicker Picker; + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + Picker.BeatmapSet = author.BeatmapSet = Details.BeatmapSet = BeatmapSet; + title.Text = BeatmapSet.Metadata.Title; + artist.Text = BeatmapSet.Metadata.Artist; + onlineStatusPill.Status = BeatmapSet.OnlineInfo.Status; + + downloadButtonsContainer.FadeIn(); + noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration); + videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration); + + cover?.FadeOut(400, Easing.Out); + coverContainer.Add(cover = new DelayedLoadWrapper( + new BeatmapSetCover(BeatmapSet) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), + }, 300) + { + RelativeSizeAxes = Axes.Both, + }); + } + } + + public Header() + { + RelativeSizeAxes = Axes.X; + Height = 400; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }; + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = tabs_height, + Children = new[] + { + tabsBg = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = tabs_height }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + coverContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.3f), Color4.Black.Opacity(0.8f)), + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 20, Bottom = 30, Horizontal = BeatmapSetOverlay.X_PADDING }, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 113, + Child = Picker = new BeatmapPicker(), + }, + title = new OsuSpriteText + { + Font = @"Exo2.0-BoldItalic", + TextSize = 37, + }, + artist = new OsuSpriteText + { + Font = @"Exo2.0-SemiBoldItalic", + TextSize = 25, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 20 }, + Child = author = new AuthorInfo(), + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = buttons_height, + Margin = new MarginPadding { Top = 10 }, + Children = new Drawable[] + { + new FavouriteButton(), + downloadButtonsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = buttons_height + buttons_spacing }, + Children = new Drawable[] + { + noVideoButtons = new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0f, + Child = new DownloadButton("Download", @"") + { + Action = () => download(false), + }, + }, + videoButtons = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Spacing = new Vector2(buttons_spacing), + Alpha = 0f, + Children = new[] + { + new DownloadButton("Download", "with Video") + { + Action = () => download(false), + }, + new DownloadButton("Download", "without Video") + { + Action = () => download(true), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = BeatmapSetOverlay.X_PADDING }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Children = new Drawable[] + { + onlineStatusPill = new BeatmapSetOnlineStatusPill(14, new MarginPadding { Horizontal = 25, Vertical = 8 }) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + Details = new Details(), + }, + }, + }, + }, + }; + + Picker.Beatmap.ValueChanged += b => Details.Beatmap = b; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, BeatmapManager beatmaps) + { + tabsBg.Colour = colours.Gray3; + this.beatmaps = beatmaps; + + beatmaps.ItemAdded += handleBeatmapAdd; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd; + } + + private void handleBeatmapAdd(BeatmapSetInfo beatmap) => Schedule(() => + { + if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID) + downloadButtonsContainer.FadeOut(transition_duration); + }); + + private void download(bool noVideo) + { + if (beatmaps.GetExistingDownload(BeatmapSet) != null) + { + downloadButtonsContainer.MoveToX(-5, 50, Easing.OutSine).Then() + .MoveToX(5, 100, Easing.InOutSine).Then() + .MoveToX(-5, 100, Easing.InOutSine).Then() + .MoveToX(0, 50, Easing.InSine).Then(); + + return; + } + + beatmaps.Download(BeatmapSet, noVideo); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs b/osu.Game/Overlays/BeatmapSet/HeaderButton.cs index f859c8f692..e1c4f5cc67 100644 --- a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs +++ b/osu.Game/Overlays/BeatmapSet/HeaderButton.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Framework.Allocation; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class HeaderButton : TriangleButton - { - public HeaderButton() - { - Height = 0; - RelativeSizeAxes = Axes.Y; - } - - [BackgroundDependencyLoader] - private void load() - { - BackgroundColour = OsuColour.FromHex(@"094c5f"); - Triangles.ColourLight = OsuColour.FromHex(@"0f7c9b"); - Triangles.ColourDark = OsuColour.FromHex(@"094c5f"); - Triangles.TriangleScale = 1.5f; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class HeaderButton : TriangleButton + { + public HeaderButton() + { + Height = 0; + RelativeSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load() + { + BackgroundColour = OsuColour.FromHex(@"094c5f"); + Triangles.ColourLight = OsuColour.FromHex(@"0f7c9b"); + Triangles.ColourDark = OsuColour.FromHex(@"094c5f"); + Triangles.TriangleScale = 1.5f; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index a0b6d9cefa..9a402515ae 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -1,195 +1,195 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class Info : Container - { - private const float transition_duration = 250; - private const float metadata_width = 225; - private const float spacing = 20; - - private readonly MetadataSection description, source, tags; - private readonly Box successRateBackground; - private readonly SuccessRate successRate; - - private BeatmapSetInfo beatmapSet; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - source.Text = BeatmapSet.Metadata.Source; - tags.Text = BeatmapSet.Metadata.Tags; - } - } - - public BeatmapInfo Beatmap - { - get { return successRate.Beatmap; } - set { successRate.Beatmap = value; } - } - - public Info() - { - RelativeSizeAxes = Axes.X; - Height = 220; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 15, Horizontal = BeatmapSetOverlay.X_PADDING }, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = metadata_width + BeatmapSetOverlay.RIGHT_WIDTH + spacing * 2 }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Child = description = new MetadataSection("Description"), - }, - }, - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = metadata_width, - Padding = new MarginPadding { Horizontal = 10 }, - Margin = new MarginPadding { Right = BeatmapSetOverlay.RIGHT_WIDTH + spacing }, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = transition_duration, - Children = new[] - { - source = new MetadataSection("Source"), - tags = new MetadataSection("Tags"), - }, - }, - }, - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = BeatmapSetOverlay.RIGHT_WIDTH, - Children = new Drawable[] - { - successRateBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - successRate = new SuccessRate - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 20, Horizontal = 15 }, - }, - }, - }, - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - successRateBackground.Colour = colours.GrayE; - source.TextColour = description.TextColour = colours.Gray5; - tags.TextColour = colours.BlueDark; - } - - private class MetadataSection : FillFlowContainer - { - private readonly OsuSpriteText header; - private readonly TextFlowContainer textFlow; - - public string Text - { - set - { - if (string.IsNullOrEmpty(value)) - { - this.FadeOut(transition_duration); - return; - } - - this.FadeIn(transition_duration); - textFlow.Clear(); - textFlow.AddText(value, s => s.TextSize = 14); - } - } - - public Color4 TextColour - { - get { return textFlow.Colour; } - set { textFlow.Colour = value; } - } - - public MetadataSection(string title) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Spacing = new Vector2(5f); - - InternalChildren = new Drawable[] - { - header = new OsuSpriteText - { - Text = title, - Font = @"Exo2.0-Bold", - TextSize = 14, - Shadow = false, - Margin = new MarginPadding { Top = 20 }, - }, - textFlow = new OsuTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - header.Colour = colours.Gray5; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class Info : Container + { + private const float transition_duration = 250; + private const float metadata_width = 225; + private const float spacing = 20; + + private readonly MetadataSection description, source, tags; + private readonly Box successRateBackground; + private readonly SuccessRate successRate; + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + source.Text = BeatmapSet.Metadata.Source; + tags.Text = BeatmapSet.Metadata.Tags; + } + } + + public BeatmapInfo Beatmap + { + get { return successRate.Beatmap; } + set { successRate.Beatmap = value; } + } + + public Info() + { + RelativeSizeAxes = Axes.X; + Height = 220; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 15, Horizontal = BeatmapSetOverlay.X_PADDING }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = metadata_width + BeatmapSetOverlay.RIGHT_WIDTH + spacing * 2 }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Child = description = new MetadataSection("Description"), + }, + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = metadata_width, + Padding = new MarginPadding { Horizontal = 10 }, + Margin = new MarginPadding { Right = BeatmapSetOverlay.RIGHT_WIDTH + spacing }, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = transition_duration, + Children = new[] + { + source = new MetadataSection("Source"), + tags = new MetadataSection("Tags"), + }, + }, + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = BeatmapSetOverlay.RIGHT_WIDTH, + Children = new Drawable[] + { + successRateBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + successRate = new SuccessRate + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 20, Horizontal = 15 }, + }, + }, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + successRateBackground.Colour = colours.GrayE; + source.TextColour = description.TextColour = colours.Gray5; + tags.TextColour = colours.BlueDark; + } + + private class MetadataSection : FillFlowContainer + { + private readonly OsuSpriteText header; + private readonly TextFlowContainer textFlow; + + public string Text + { + set + { + if (string.IsNullOrEmpty(value)) + { + this.FadeOut(transition_duration); + return; + } + + this.FadeIn(transition_duration); + textFlow.Clear(); + textFlow.AddText(value, s => s.TextSize = 14); + } + } + + public Color4 TextColour + { + get { return textFlow.Colour; } + set { textFlow.Colour = value; } + } + + public MetadataSection(string title) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Spacing = new Vector2(5f); + + InternalChildren = new Drawable[] + { + header = new OsuSpriteText + { + Text = title, + Font = @"Exo2.0-Bold", + TextSize = 14, + Shadow = false, + Margin = new MarginPadding { Top = 20 }, + }, + textFlow = new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + header.Colour = colours.Gray5; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs index 4f5a6ba718..6b5ffa57ad 100644 --- a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs @@ -1,108 +1,108 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Overlays.Direct; -using osu.Framework.Configuration; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class PreviewButton : OsuClickableContainer - { - private const float transition_duration = 500; - - private readonly Box bg, progress; - private readonly PlayButton playButton; - - private Track preview => playButton.Preview; - public Bindable Playing => playButton.Playing; - - public BeatmapSetInfo BeatmapSet - { - get { return playButton.BeatmapSet; } - set { playButton.BeatmapSet = value; } - } - - public PreviewButton() - { - Height = 42; - - Children = new Drawable[] - { - bg = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.25f), - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 3, - Child = progress = new Box - { - RelativeSizeAxes = Axes.Both, - Width = 0f, - Alpha = 0f, - }, - }, - playButton = new PlayButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(18), - }, - }; - - Action = () => Playing.Value = !Playing.Value; - Playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - progress.Colour = colours.Yellow; - } - - protected override void Update() - { - base.Update(); - - if (Playing.Value && preview != null) - { - // prevent negative (potential infinite) width if a track without length was loaded - progress.Width = preview.Length > 0 ? (float)(preview.CurrentTime / preview.Length) : 0f; - } - } - - protected override void Dispose(bool isDisposing) - { - Playing.Value = false; - base.Dispose(isDisposing); - } - - protected override bool OnHover(InputState state) - { - bg.FadeColour(Color4.Black.Opacity(0.5f), 100); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - bg.FadeColour(Color4.Black.Opacity(0.25f), 100); - base.OnHoverLost(state); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Overlays.Direct; +using osu.Framework.Configuration; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class PreviewButton : OsuClickableContainer + { + private const float transition_duration = 500; + + private readonly Box bg, progress; + private readonly PlayButton playButton; + + private Track preview => playButton.Preview; + public Bindable Playing => playButton.Playing; + + public BeatmapSetInfo BeatmapSet + { + get { return playButton.BeatmapSet; } + set { playButton.BeatmapSet = value; } + } + + public PreviewButton() + { + Height = 42; + + Children = new Drawable[] + { + bg = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.25f), + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 3, + Child = progress = new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0f, + Alpha = 0f, + }, + }, + playButton = new PlayButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(18), + }, + }; + + Action = () => Playing.Value = !Playing.Value; + Playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + progress.Colour = colours.Yellow; + } + + protected override void Update() + { + base.Update(); + + if (Playing.Value && preview != null) + { + // prevent negative (potential infinite) width if a track without length was loaded + progress.Width = preview.Length > 0 ? (float)(preview.CurrentTime / preview.Length) : 0f; + } + } + + protected override void Dispose(bool isDisposing) + { + Playing.Value = false; + base.Dispose(isDisposing); + } + + protected override bool OnHover(InputState state) + { + bg.FadeColour(Color4.Black.Opacity(0.5f), 100); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + bg.FadeColour(Color4.Black.Opacity(0.25f), 100); + base.OnHoverLost(state); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs index 826a7c53f3..1f1a2a68d2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs @@ -1,62 +1,62 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osu.Framework.Input; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ClickableUsername : OsuHoverContainer - { - private readonly OsuSpriteText text; - private UserProfileOverlay profile; - - private User user; - public User User - { - get { return user; } - set - { - if (user == value) return; - user = value; - - text.Text = user.Username; - } - } - - public float TextSize - { - set - { - if (text.TextSize == value) return; - text.TextSize = value; - } - get { return text.TextSize; } - } - - public ClickableUsername() - { - AutoSizeAxes = Axes.Both; - Child = text = new OsuSpriteText - { - Font = @"Exo2.0-BoldItalic", - }; - } - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) - { - this.profile = profile; - } - - protected override bool OnClick(InputState state) - { - profile?.ShowUser(user); - return true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using osu.Framework.Input; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ClickableUsername : OsuHoverContainer + { + private readonly OsuSpriteText text; + private UserProfileOverlay profile; + + private User user; + public User User + { + get { return user; } + set + { + if (user == value) return; + user = value; + + text.Text = user.Username; + } + } + + public float TextSize + { + set + { + if (text.TextSize == value) return; + text.TextSize = value; + } + get { return text.TextSize; } + } + + public ClickableUsername() + { + AutoSizeAxes = Axes.Both; + Child = text = new OsuSpriteText + { + Font = @"Exo2.0-BoldItalic", + }; + } + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profile) + { + this.profile = profile; + } + + protected override bool OnClick(InputState state) + { + profile?.ShowUser(user); + return true; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 7ef86d349a..10e689698d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -1,142 +1,142 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class DrawableScore : Container - { - private const int fade_duration = 100; - private const float side_margin = 20; - - private readonly Box background; - - public DrawableScore(int index, OnlineScore score) - { - ScoreModsContainer modsContainer; - - RelativeSizeAxes = Axes.X; - Height = 30; - CornerRadius = 3; - Masking = true; - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"#{index + 1}", - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding { Left = side_margin } - }, - new DrawableFlag(score.User.Country) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - Margin = new MarginPadding { Left = 60 } - }, - new ClickableUsername - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - User = score.User, - Margin = new MarginPadding { Left = 100 } - }, - modsContainer = new ScoreModsContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0.06f, - RelativePositionAxes = Axes.X, - X = 0.42f - }, - new DrawableRank(score.Rank) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - FillMode = FillMode.Fit, - RelativePositionAxes = Axes.X, - X = 0.55f - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = $@"{score.TotalScore:N0}", - Font = @"Venera", - RelativePositionAxes = Axes.X, - X = 0.75f, - FixedWidth = true, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = $@"{score.Accuracy:P2}", - Font = @"Exo2.0-RegularItalic", - RelativePositionAxes = Axes.X, - X = 0.85f - }, - new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}", - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding { Right = side_margin } - }, - }; - - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.35f), - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray4; - } - - protected override bool OnHover(InputState state) - { - background.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - background.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(state); - } - - protected override bool OnClick(InputState state) => true; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Users; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class DrawableScore : Container + { + private const int fade_duration = 100; + private const float side_margin = 20; + + private readonly Box background; + + public DrawableScore(int index, OnlineScore score) + { + ScoreModsContainer modsContainer; + + RelativeSizeAxes = Axes.X; + Height = 30; + CornerRadius = 3; + Masking = true; + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"#{index + 1}", + Font = @"Exo2.0-RegularItalic", + Margin = new MarginPadding { Left = side_margin } + }, + new DrawableFlag(score.User.Country) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(30, 20), + Margin = new MarginPadding { Left = 60 } + }, + new ClickableUsername + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + User = score.User, + Margin = new MarginPadding { Left = 100 } + }, + modsContainer = new ScoreModsContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Width = 0.06f, + RelativePositionAxes = Axes.X, + X = 0.42f + }, + new DrawableRank(score.Rank) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(30, 20), + FillMode = FillMode.Fit, + RelativePositionAxes = Axes.X, + X = 0.55f + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Text = $@"{score.TotalScore:N0}", + Font = @"Venera", + RelativePositionAxes = Axes.X, + X = 0.75f, + FixedWidth = true, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Text = $@"{score.Accuracy:P2}", + Font = @"Exo2.0-RegularItalic", + RelativePositionAxes = Axes.X, + X = 0.85f + }, + new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}", + Font = @"Exo2.0-RegularItalic", + Margin = new MarginPadding { Right = side_margin } + }, + }; + + foreach (Mod mod in score.Mods) + modsContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.35f), + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray4; + } + + protected override bool OnHover(InputState state) + { + background.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + background.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) => true; + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 66d7a9e4e1..8ac79eabb4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -1,243 +1,243 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class DrawableTopScore : Container - { - private const float fade_duration = 100; - private const float height = 200; - private const float avatar_size = 80; - private const float margin = 10; - - private readonly Box background; - private readonly Box bottomBackground; - private readonly Box middleLine; - private readonly UpdateableAvatar avatar; - private readonly DrawableFlag flag; - private readonly ClickableUsername username; - private readonly OsuSpriteText rankText; - private readonly OsuSpriteText date; - private readonly DrawableRank rank; - private readonly InfoColumn totalScore; - private readonly InfoColumn accuracy; - private readonly InfoColumn statistics; - private readonly ScoreModsContainer modsContainer; - - private OnlineScore score; - public OnlineScore Score - { - get { return score; } - set - { - if (score == value) return; - score = value; - - avatar.User = username.User = score.User; - flag.Country = score.User.Country; - date.Text = $@"achieved {score.Date:MMM d, yyyy}"; - rank.UpdateRank(score.Rank); - - totalScore.Value = $@"{score.TotalScore:N0}"; - accuracy.Value = $@"{score.Accuracy:P2}"; - statistics.Value = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}"; - - modsContainer.Clear(); - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.45f), - }); - } - } - - public DrawableTopScore() - { - RelativeSizeAxes = Axes.X; - Height = height; - CornerRadius = 5; - BorderThickness = 4; - Masking = true; - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, //used for correct border representation - }, - avatar = new UpdateableAvatar - { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), - Radius = 1, - }, - Margin = new MarginPadding { Top = margin, Left = margin } - }, - flag = new DrawableFlag - { - Size = new Vector2(30, 20), - Position = new Vector2(margin * 2 + avatar_size, height / 4), - }, - username = new ClickableUsername - { - Origin = Anchor.BottomLeft, - TextSize = 30, - Position = new Vector2(margin * 2 + avatar_size, height / 4), - Margin = new MarginPadding { Bottom = 4 } - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.BottomRight, - Text = "#1", - TextSize = 40, - Font = @"Exo2.0-BoldItalic", - Y = height / 4, - Margin = new MarginPadding { Right = margin } - }, - date = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = height / 4, - Margin = new MarginPadding { Right = margin } - }, - new Container - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Children = new Drawable[] - { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, - middleLine = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - }, - rank = new DrawableRank(ScoreRank.F) - { - Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size, 40), - FillMode = FillMode.Fit, - Y = height / 4, - Margin = new MarginPadding { Left = margin } - }, - new FillFlowContainer - { - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Position = new Vector2(height / 2, height / 4), - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new[] - { - totalScore = new InfoColumn("Score"), - accuracy = new InfoColumn("Accuracy"), - statistics = new InfoColumn("300/100/50"), - }, - }, - modsContainer = new ScoreModsContainer - { - AutoSizeAxes = Axes.Y, - Width = 80, - Position = new Vector2(height / 2, height / 4), - } - } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = bottomBackground.Colour = colours.Gray4; - middleLine.Colour = colours.Gray2; - date.Colour = colours.Gray9; - BorderColour = rankText.Colour = colours.Yellow; - } - - protected override bool OnHover(InputState state) - { - background.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - background.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(state); - } - - private class InfoColumn : FillFlowContainer - { - private readonly OsuSpriteText headerText; - private readonly OsuSpriteText valueText; - - public string Value - { - set - { - if (valueText.Text == value) - return; - valueText.Text = value; - } - get { return valueText.Text; } - } - - public InfoColumn(string header) - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 3); - Children = new Drawable[] - { - headerText = new OsuSpriteText - { - TextSize = 14, - Text = header, - Font = @"Exo2.0-Bold", - }, - valueText = new OsuSpriteText - { - TextSize = 25, - Font = @"Exo2.0-RegularItalic", - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - headerText.Colour = colours.Gray9; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Users; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class DrawableTopScore : Container + { + private const float fade_duration = 100; + private const float height = 200; + private const float avatar_size = 80; + private const float margin = 10; + + private readonly Box background; + private readonly Box bottomBackground; + private readonly Box middleLine; + private readonly UpdateableAvatar avatar; + private readonly DrawableFlag flag; + private readonly ClickableUsername username; + private readonly OsuSpriteText rankText; + private readonly OsuSpriteText date; + private readonly DrawableRank rank; + private readonly InfoColumn totalScore; + private readonly InfoColumn accuracy; + private readonly InfoColumn statistics; + private readonly ScoreModsContainer modsContainer; + + private OnlineScore score; + public OnlineScore Score + { + get { return score; } + set + { + if (score == value) return; + score = value; + + avatar.User = username.User = score.User; + flag.Country = score.User.Country; + date.Text = $@"achieved {score.Date:MMM d, yyyy}"; + rank.UpdateRank(score.Rank); + + totalScore.Value = $@"{score.TotalScore:N0}"; + accuracy.Value = $@"{score.Accuracy:P2}"; + statistics.Value = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}"; + + modsContainer.Clear(); + foreach (Mod mod in score.Mods) + modsContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.45f), + }); + } + } + + public DrawableTopScore() + { + RelativeSizeAxes = Axes.X; + Height = height; + CornerRadius = 5; + BorderThickness = 4; + Masking = true; + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, //used for correct border representation + }, + avatar = new UpdateableAvatar + { + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + Radius = 1, + }, + Margin = new MarginPadding { Top = margin, Left = margin } + }, + flag = new DrawableFlag + { + Size = new Vector2(30, 20), + Position = new Vector2(margin * 2 + avatar_size, height / 4), + }, + username = new ClickableUsername + { + Origin = Anchor.BottomLeft, + TextSize = 30, + Position = new Vector2(margin * 2 + avatar_size, height / 4), + Margin = new MarginPadding { Bottom = 4 } + }, + rankText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.BottomRight, + Text = "#1", + TextSize = 40, + Font = @"Exo2.0-BoldItalic", + Y = height / 4, + Margin = new MarginPadding { Right = margin } + }, + date = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = height / 4, + Margin = new MarginPadding { Right = margin } + }, + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Children = new Drawable[] + { + bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, + middleLine = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + }, + rank = new DrawableRank(ScoreRank.F) + { + Origin = Anchor.BottomLeft, + Size = new Vector2(avatar_size, 40), + FillMode = FillMode.Fit, + Y = height / 4, + Margin = new MarginPadding { Left = margin } + }, + new FillFlowContainer + { + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Position = new Vector2(height / 2, height / 4), + Direction = FillDirection.Horizontal, + Spacing = new Vector2(15, 0), + Children = new[] + { + totalScore = new InfoColumn("Score"), + accuracy = new InfoColumn("Accuracy"), + statistics = new InfoColumn("300/100/50"), + }, + }, + modsContainer = new ScoreModsContainer + { + AutoSizeAxes = Axes.Y, + Width = 80, + Position = new Vector2(height / 2, height / 4), + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = bottomBackground.Colour = colours.Gray4; + middleLine.Colour = colours.Gray2; + date.Colour = colours.Gray9; + BorderColour = rankText.Colour = colours.Yellow; + } + + protected override bool OnHover(InputState state) + { + background.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + background.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(state); + } + + private class InfoColumn : FillFlowContainer + { + private readonly OsuSpriteText headerText; + private readonly OsuSpriteText valueText; + + public string Value + { + set + { + if (valueText.Text == value) + return; + valueText.Text = value; + } + get { return valueText.Text; } + } + + public InfoColumn(string header) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Vertical; + Spacing = new Vector2(0, 3); + Children = new Drawable[] + { + headerText = new OsuSpriteText + { + TextSize = 14, + Text = header, + Font = @"Exo2.0-Bold", + }, + valueText = new OsuSpriteText + { + TextSize = 25, + Font = @"Exo2.0-RegularItalic", + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + headerText.Colour = colours.Gray9; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index b6efd3b3b0..d5c5bd8ddd 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -1,115 +1,115 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ScoresContainer : Container - { - private const int spacing = 15; - private const int fade_duration = 200; - - private readonly FillFlowContainer flow; - private readonly DrawableTopScore topScore; - private readonly LoadingAnimation loadingAnimation; - private readonly Box foreground; - - private bool isLoading; - public bool IsLoading - { - get { return isLoading; } - set - { - if (isLoading == value) return; - isLoading = value; - - foreground.FadeTo(isLoading ? 1 : 0, fade_duration); - loadingAnimation.FadeTo(isLoading ? 1 : 0, fade_duration); - } - } - - private IEnumerable scores; - public IEnumerable Scores - { - get { return scores; } - set - { - scores = value; - var scoresAmount = scores.Count(); - if (scoresAmount == 0) - { - CleanAllScores(); - return; - } - - topScore.Score = scores.FirstOrDefault(); - topScore.Show(); - - flow.Clear(); - - if (scoresAmount < 2) - return; - - for (int i = 1; i < scoresAmount; i++) - flow.Add(new DrawableScore(i, scores.ElementAt(i))); - } - } - - public ScoresContainer() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.95f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), - Margin = new MarginPadding { Vertical = spacing }, - Children = new Drawable[] - { - topScore = new DrawableTopScore(), - flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 1), - }, - } - }, - foreground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - Alpha = 0, - }, - loadingAnimation = new LoadingAnimation - { - Alpha = 0, - }, - }; - } - - public void CleanAllScores() - { - topScore.Hide(); - flow.Clear(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoresContainer : Container + { + private const int spacing = 15; + private const int fade_duration = 200; + + private readonly FillFlowContainer flow; + private readonly DrawableTopScore topScore; + private readonly LoadingAnimation loadingAnimation; + private readonly Box foreground; + + private bool isLoading; + public bool IsLoading + { + get { return isLoading; } + set + { + if (isLoading == value) return; + isLoading = value; + + foreground.FadeTo(isLoading ? 1 : 0, fade_duration); + loadingAnimation.FadeTo(isLoading ? 1 : 0, fade_duration); + } + } + + private IEnumerable scores; + public IEnumerable Scores + { + get { return scores; } + set + { + scores = value; + var scoresAmount = scores.Count(); + if (scoresAmount == 0) + { + CleanAllScores(); + return; + } + + topScore.Score = scores.FirstOrDefault(); + topScore.Show(); + + flow.Clear(); + + if (scoresAmount < 2) + return; + + for (int i = 1; i < scoresAmount; i++) + flow.Add(new DrawableScore(i, scores.ElementAt(i))); + } + } + + public ScoresContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.95f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Margin = new MarginPadding { Vertical = spacing }, + Children = new Drawable[] + { + topScore = new DrawableTopScore(), + flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 1), + }, + } + }, + foreground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + Alpha = 0, + }, + loadingAnimation = new LoadingAnimation + { + Alpha = 0, + }, + }; + } + + public void CleanAllScores() + { + topScore.Hide(); + flow.Clear(); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index bee1e9284b..c64d3988a6 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -1,115 +1,115 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Select.Details; - -namespace osu.Game.Overlays.BeatmapSet -{ - public class SuccessRate : Container - { - private readonly FillFlowContainer header; - private readonly OsuSpriteText successRateLabel, successPercent, graphLabel; - private readonly Bar successRate; - private readonly Container percentContainer; - private readonly FailRetryGraph graph; - - private BeatmapInfo beatmap; - public BeatmapInfo Beatmap - { - get { return beatmap; } - set - { - if (value == beatmap) return; - beatmap = value; - - int passCount = beatmap.OnlineInfo.PassCount; - int playCount = beatmap.OnlineInfo.PlayCount; - - var rate = playCount != 0 ? (float)passCount / playCount : 0; - successPercent.Text = rate.ToString("P0"); - successRate.Length = rate; - percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); - - graph.Metrics = Beatmap.Metrics; - } - } - - public SuccessRate() - { - Children = new Drawable[] - { - header = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - successRateLabel = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Success Rate", - TextSize = 13, - }, - successRate = new Bar - { - RelativeSizeAxes = Axes.X, - Height = 5, - Margin = new MarginPadding { Top = 5 }, - }, - percentContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0f, - Child = successPercent = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopCentre, - Text = @"0%", - TextSize = 13, - }, - }, - graphLabel = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Points of Failure", - TextSize = 13, - Margin = new MarginPadding { Vertical = 20 }, - }, - }, - }, - graph = new FailRetryGraph - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - successRateLabel.Colour = successPercent.Colour = graphLabel.Colour = colours.Gray5; - successRate.AccentColour = colours.Green; - successRate.BackgroundColour = colours.GrayD; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - graph.Padding = new MarginPadding { Top = header.DrawHeight }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Select.Details; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class SuccessRate : Container + { + private readonly FillFlowContainer header; + private readonly OsuSpriteText successRateLabel, successPercent, graphLabel; + private readonly Bar successRate; + private readonly Container percentContainer; + private readonly FailRetryGraph graph; + + private BeatmapInfo beatmap; + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + if (value == beatmap) return; + beatmap = value; + + int passCount = beatmap.OnlineInfo.PassCount; + int playCount = beatmap.OnlineInfo.PlayCount; + + var rate = playCount != 0 ? (float)passCount / playCount : 0; + successPercent.Text = rate.ToString("P0"); + successRate.Length = rate; + percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); + + graph.Metrics = Beatmap.Metrics; + } + } + + public SuccessRate() + { + Children = new Drawable[] + { + header = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + successRateLabel = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Success Rate", + TextSize = 13, + }, + successRate = new Bar + { + RelativeSizeAxes = Axes.X, + Height = 5, + Margin = new MarginPadding { Top = 5 }, + }, + percentContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0f, + Child = successPercent = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopCentre, + Text = @"0%", + TextSize = 13, + }, + }, + graphLabel = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Points of Failure", + TextSize = 13, + Margin = new MarginPadding { Vertical = 20 }, + }, + }, + }, + graph = new FailRetryGraph + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + successRateLabel.Colour = successPercent.Colour = graphLabel.Colour = colours.Gray5; + successRate.AccentColour = colours.Green; + successRate.BackgroundColour = colours.GrayD; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + graph.Padding = new MarginPadding { Top = header.DrawHeight }; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index f0f8a6ef10..93e10751d9 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -1,157 +1,157 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.BeatmapSet; -using osu.Game.Rulesets; -using osu.Game.Overlays.BeatmapSet.Scores; - -namespace osu.Game.Overlays -{ - public class BeatmapSetOverlay : WaveOverlayContainer - { - public const float X_PADDING = 40; - public const float RIGHT_WIDTH = 275; - - private readonly Header header; - private readonly Info info; - private readonly ScoresContainer scores; - - private APIAccess api; - private RulesetStore rulesets; - private GetScoresRequest getScoresRequest; - - private readonly ScrollContainer scroll; - - // receive input outside our bounds so we can trigger a close event on ourselves. - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - - public BeatmapSetOverlay() - { - FirstWaveColour = OsuColour.Gray(0.4f); - SecondWaveColour = OsuColour.Gray(0.3f); - ThirdWaveColour = OsuColour.Gray(0.2f); - FourthWaveColour = OsuColour.Gray(0.1f); - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - RelativeSizeAxes = Axes.Both; - Width = 0.85f; - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, - scroll = new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - header = new Header(), - info = new Info(), - scores = new ScoresContainer(), - }, - }, - }, - }; - - header.Picker.Beatmap.ValueChanged += b => - { - info.Beatmap = b; - updateScores(b); - }; - } - - private void updateScores(BeatmapInfo beatmap) - { - getScoresRequest?.Cancel(); - - if (!beatmap.OnlineBeatmapID.HasValue) - { - scores.CleanAllScores(); - return; - } - - scores.IsLoading = true; - - getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += r => - { - scores.Scores = r.Scores; - scores.IsLoading = false; - }; - api.Queue(getScoresRequest); - } - - [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) - { - this.api = api; - this.rulesets = rulesets; - } - - protected override void PopIn() - { - base.PopIn(); - FadeEdgeEffectTo(0.25f, APPEAR_DURATION, Easing.In); - } - - protected override void PopOut() - { - base.PopOut(); - header.Details.StopPreview(); - FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out); - } - - protected override bool OnClick(InputState state) - { - State = Visibility.Hidden; - return true; - } - - public void ShowBeatmapSet(int beatmapSetId) - { - // todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work. - var req = new GetBeatmapSetRequest(beatmapSetId); - req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); - api.Queue(req); - } - - public void ShowBeatmapSet(BeatmapSetInfo set) - { - header.BeatmapSet = info.BeatmapSet = set; - Show(); - scroll.ScrollTo(0); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Rulesets; +using osu.Game.Overlays.BeatmapSet.Scores; + +namespace osu.Game.Overlays +{ + public class BeatmapSetOverlay : WaveOverlayContainer + { + public const float X_PADDING = 40; + public const float RIGHT_WIDTH = 275; + + private readonly Header header; + private readonly Info info; + private readonly ScoresContainer scores; + + private APIAccess api; + private RulesetStore rulesets; + private GetScoresRequest getScoresRequest; + + private readonly ScrollContainer scroll; + + // receive input outside our bounds so we can trigger a close event on ourselves. + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + + public BeatmapSetOverlay() + { + FirstWaveColour = OsuColour.Gray(0.4f); + SecondWaveColour = OsuColour.Gray(0.3f); + ThirdWaveColour = OsuColour.Gray(0.2f); + FourthWaveColour = OsuColour.Gray(0.1f); + + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + RelativeSizeAxes = Axes.Both; + Width = 0.85f; + + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + scroll = new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new ReverseChildIDFillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + header = new Header(), + info = new Info(), + scores = new ScoresContainer(), + }, + }, + }, + }; + + header.Picker.Beatmap.ValueChanged += b => + { + info.Beatmap = b; + updateScores(b); + }; + } + + private void updateScores(BeatmapInfo beatmap) + { + getScoresRequest?.Cancel(); + + if (!beatmap.OnlineBeatmapID.HasValue) + { + scores.CleanAllScores(); + return; + } + + scores.IsLoading = true; + + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); + getScoresRequest.Success += r => + { + scores.Scores = r.Scores; + scores.IsLoading = false; + }; + api.Queue(getScoresRequest); + } + + [BackgroundDependencyLoader] + private void load(APIAccess api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + } + + protected override void PopIn() + { + base.PopIn(); + FadeEdgeEffectTo(0.25f, APPEAR_DURATION, Easing.In); + } + + protected override void PopOut() + { + base.PopOut(); + header.Details.StopPreview(); + FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out); + } + + protected override bool OnClick(InputState state) + { + State = Visibility.Hidden; + return true; + } + + public void ShowBeatmapSet(int beatmapSetId) + { + // todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work. + var req = new GetBeatmapSetRequest(beatmapSetId); + req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + api.Queue(req); + } + + public void ShowBeatmapSet(BeatmapSetInfo set) + { + header.BeatmapSet = info.BeatmapSet = set; + Show(); + scroll.ScrollTo(0); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelListItem.cs index 19418c63a8..910c87e0a8 100644 --- a/osu.Game/Overlays/Chat/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelListItem.cs @@ -1,191 +1,191 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Chat -{ - public class ChannelListItem : OsuClickableContainer, IFilterable - { - private const float width_padding = 5; - private const float channel_width = 150; - private const float text_size = 15; - private const float transition_duration = 100; - - private readonly Channel channel; - - private readonly Bindable joinedBind = new Bindable(); - private readonly OsuSpriteText name; - private readonly OsuSpriteText topic; - private readonly SpriteIcon joinedCheckmark; - - private Color4 joinedColour; - private Color4 topicColour; - private Color4 hoverColour; - - public IEnumerable FilterTerms => new[] { channel.Name }; - public bool MatchingFilter - { - set - { - this.FadeTo(value ? 1f : 0f, 100); - } - } - - public Action OnRequestJoin; - public Action OnRequestLeave; - - public ChannelListItem(Channel channel) - { - this.channel = channel; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Action = () => { (channel.Joined ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); }; - - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new Container - { - Children = new[] - { - joinedCheckmark = new SpriteIcon - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Icon = FontAwesome.fa_check_circle, - Size = new Vector2(text_size), - Shadow = false, - Margin = new MarginPadding { Right = 10f }, - }, - }, - }, - new Container - { - Width = channel_width, - AutoSizeAxes = Axes.Y, - Children = new[] - { - name = new OsuSpriteText - { - Text = channel.ToString(), - TextSize = text_size, - Font = @"Exo2.0-Bold", - Shadow = false, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - Width = 0.7f, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = width_padding }, - Children = new[] - { - topic = new OsuSpriteText - { - Text = channel.Topic, - TextSize = text_size, - Font = @"Exo2.0-SemiBold", - Shadow = false, - }, - }, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = width_padding }, - Spacing = new Vector2(3f, 0f), - Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.fa_user, - Size = new Vector2(text_size - 2), - Shadow = false, - Margin = new MarginPadding { Top = 1 }, - }, - new OsuSpriteText - { - Text = @"0", - TextSize = text_size, - Font = @"Exo2.0-SemiBold", - Shadow = false, - }, - }, - }, - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - topicColour = colours.Gray9; - joinedColour = colours.Blue; - hoverColour = colours.Yellow; - - joinedBind.ValueChanged += updateColour; - joinedBind.BindTo(channel.Joined); - - joinedBind.TriggerChange(); - FinishTransforms(true); - } - - protected override bool OnHover(InputState state) - { - if (!channel.Joined.Value) - name.FadeColour(hoverColour, 50, Easing.OutQuint); - - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - if (!channel.Joined.Value) - name.FadeColour(Color4.White, transition_duration); - } - - private void updateColour(bool joined) - { - if (joined) - { - name.FadeColour(Color4.White, transition_duration); - joinedCheckmark.FadeTo(1f, transition_duration); - topic.FadeTo(0.8f, transition_duration); - topic.FadeColour(Color4.White, transition_duration); - this.FadeColour(joinedColour, transition_duration); - } - else - { - joinedCheckmark.FadeTo(0f, transition_duration); - topic.FadeTo(1f, transition_duration); - topic.FadeColour(topicColour, transition_duration); - this.FadeColour(Color4.White, transition_duration); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Chat +{ + public class ChannelListItem : OsuClickableContainer, IFilterable + { + private const float width_padding = 5; + private const float channel_width = 150; + private const float text_size = 15; + private const float transition_duration = 100; + + private readonly Channel channel; + + private readonly Bindable joinedBind = new Bindable(); + private readonly OsuSpriteText name; + private readonly OsuSpriteText topic; + private readonly SpriteIcon joinedCheckmark; + + private Color4 joinedColour; + private Color4 topicColour; + private Color4 hoverColour; + + public IEnumerable FilterTerms => new[] { channel.Name }; + public bool MatchingFilter + { + set + { + this.FadeTo(value ? 1f : 0f, 100); + } + } + + public Action OnRequestJoin; + public Action OnRequestLeave; + + public ChannelListItem(Channel channel) + { + this.channel = channel; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Action = () => { (channel.Joined ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); }; + + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + Children = new[] + { + joinedCheckmark = new SpriteIcon + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Icon = FontAwesome.fa_check_circle, + Size = new Vector2(text_size), + Shadow = false, + Margin = new MarginPadding { Right = 10f }, + }, + }, + }, + new Container + { + Width = channel_width, + AutoSizeAxes = Axes.Y, + Children = new[] + { + name = new OsuSpriteText + { + Text = channel.ToString(), + TextSize = text_size, + Font = @"Exo2.0-Bold", + Shadow = false, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Width = 0.7f, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Left = width_padding }, + Children = new[] + { + topic = new OsuSpriteText + { + Text = channel.Topic, + TextSize = text_size, + Font = @"Exo2.0-SemiBold", + Shadow = false, + }, + }, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = width_padding }, + Spacing = new Vector2(3f, 0f), + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_user, + Size = new Vector2(text_size - 2), + Shadow = false, + Margin = new MarginPadding { Top = 1 }, + }, + new OsuSpriteText + { + Text = @"0", + TextSize = text_size, + Font = @"Exo2.0-SemiBold", + Shadow = false, + }, + }, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + topicColour = colours.Gray9; + joinedColour = colours.Blue; + hoverColour = colours.Yellow; + + joinedBind.ValueChanged += updateColour; + joinedBind.BindTo(channel.Joined); + + joinedBind.TriggerChange(); + FinishTransforms(true); + } + + protected override bool OnHover(InputState state) + { + if (!channel.Joined.Value) + name.FadeColour(hoverColour, 50, Easing.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if (!channel.Joined.Value) + name.FadeColour(Color4.White, transition_duration); + } + + private void updateColour(bool joined) + { + if (joined) + { + name.FadeColour(Color4.White, transition_duration); + joinedCheckmark.FadeTo(1f, transition_duration); + topic.FadeTo(0.8f, transition_duration); + topic.FadeColour(Color4.White, transition_duration); + this.FadeColour(joinedColour, transition_duration); + } + else + { + joinedCheckmark.FadeTo(0f, transition_duration); + topic.FadeTo(1f, transition_duration); + topic.FadeColour(topicColour, transition_duration); + this.FadeColour(Color4.White, transition_duration); + } + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelSection.cs b/osu.Game/Overlays/Chat/ChannelSection.cs index 132891bcc0..89d9d2231c 100644 --- a/osu.Game/Overlays/Chat/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/ChannelSection.cs @@ -1,63 +1,63 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat -{ - public class ChannelSection : Container, IHasFilterableChildren - { - private readonly OsuSpriteText header; - - public readonly FillFlowContainer ChannelFlow; - - public IEnumerable FilterableChildren => ChannelFlow.Children; - public IEnumerable FilterTerms => new[] { Header }; - public bool MatchingFilter - { - set - { - this.FadeTo(value ? 1f : 0f, 100); - } - } - - public string Header - { - get { return header.Text; } - set { header.Text = value.ToUpper(); } - } - - public IEnumerable Channels - { - set { ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c)); } - } - - public ChannelSection() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Children = new Drawable[] - { - header = new OsuSpriteText - { - TextSize = 15, - Font = @"Exo2.0-Bold", - }, - ChannelFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 25 }, - Spacing = new Vector2(0f, 5f), - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat +{ + public class ChannelSection : Container, IHasFilterableChildren + { + private readonly OsuSpriteText header; + + public readonly FillFlowContainer ChannelFlow; + + public IEnumerable FilterableChildren => ChannelFlow.Children; + public IEnumerable FilterTerms => new[] { Header }; + public bool MatchingFilter + { + set + { + this.FadeTo(value ? 1f : 0f, 100); + } + } + + public string Header + { + get { return header.Text; } + set { header.Text = value.ToUpper(); } + } + + public IEnumerable Channels + { + set { ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c)); } + } + + public ChannelSection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + header = new OsuSpriteText + { + TextSize = 15, + Font = @"Exo2.0-Bold", + }, + ChannelFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 25 }, + Spacing = new Vector2(0f, 5f), + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs index 3684c47e40..57f2cd405d 100644 --- a/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs @@ -1,185 +1,185 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Chat -{ - public class ChannelSelectionOverlay : OsuFocusedOverlayContainer - { - public static readonly float WIDTH_PADDING = 170; - - private const float transition_duration = 500; - - private readonly Box bg; - private readonly Triangles triangles; - private readonly Box headerBg; - private readonly SearchTextBox search; - private readonly SearchContainer sectionsFlow; - - public Action OnRequestJoin; - public Action OnRequestLeave; - - public IEnumerable Sections - { - set - { - sectionsFlow.ChildrenEnumerable = value; - - foreach (ChannelSection s in sectionsFlow.Children) - { - foreach (ChannelListItem c in s.ChannelFlow.Children) - { - c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); }; - c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); }; - } - } - } - } - - public ChannelSelectionOverlay() - { - RelativeSizeAxes = Axes.X; - - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - bg = new Box - { - RelativeSizeAxes = Axes.Both, - }, - triangles = new Triangles - { - RelativeSizeAxes = Axes.Both, - TriangleScale = 5, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 85, Right = WIDTH_PADDING }, - Children = new[] - { - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Children = new[] - { - sectionsFlow = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - Spacing = new Vector2(0f, 20f), - Padding = new MarginPadding { Vertical = 20, Left = WIDTH_PADDING }, - }, - }, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - headerBg = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), - Padding = new MarginPadding { Top = 10f, Bottom = 10f, Left = WIDTH_PADDING, Right = WIDTH_PADDING }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"Chat Channels", - TextSize = 20, - Shadow = false, - }, - search = new HeaderSearchTextBox - { - RelativeSizeAxes = Axes.X, - PlaceholderText = @"Search", - Exit = Hide, - }, - }, - }, - }, - }, - }; - - search.Current.ValueChanged += newValue => sectionsFlow.SearchTerm = newValue; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - bg.Colour = colours.Gray3; - triangles.ColourDark = colours.Gray3; - triangles.ColourLight = OsuColour.FromHex(@"353535"); - - headerBg.Colour = colours.Gray2.Opacity(0.75f); - } - - protected override void OnFocus(InputState state) - { - GetContainingInputManager().ChangeFocus(search); - base.OnFocus(state); - } - - protected override void PopIn() - { - if (Alpha == 0) this.MoveToY(DrawHeight); - - this.FadeIn(transition_duration, Easing.OutQuint); - this.MoveToY(0, transition_duration, Easing.OutQuint); - - search.HoldFocus = true; - base.PopIn(); - } - - protected override void PopOut() - { - this.FadeOut(transition_duration, Easing.InSine); - this.MoveToY(DrawHeight, transition_duration, Easing.InSine); - - search.HoldFocus = false; - base.PopOut(); - } - - private class HeaderSearchTextBox : SearchTextBox - { - protected override Color4 BackgroundFocused => Color4.Black.Opacity(0.2f); - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.2f); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Chat +{ + public class ChannelSelectionOverlay : OsuFocusedOverlayContainer + { + public static readonly float WIDTH_PADDING = 170; + + private const float transition_duration = 500; + + private readonly Box bg; + private readonly Triangles triangles; + private readonly Box headerBg; + private readonly SearchTextBox search; + private readonly SearchContainer sectionsFlow; + + public Action OnRequestJoin; + public Action OnRequestLeave; + + public IEnumerable Sections + { + set + { + sectionsFlow.ChildrenEnumerable = value; + + foreach (ChannelSection s in sectionsFlow.Children) + { + foreach (ChannelListItem c in s.ChannelFlow.Children) + { + c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); }; + c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); }; + } + } + } + } + + public ChannelSelectionOverlay() + { + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + bg = new Box + { + RelativeSizeAxes = Axes.Both, + }, + triangles = new Triangles + { + RelativeSizeAxes = Axes.Both, + TriangleScale = 5, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 85, Right = WIDTH_PADDING }, + Children = new[] + { + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + sectionsFlow = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + Spacing = new Vector2(0f, 20f), + Padding = new MarginPadding { Vertical = 20, Left = WIDTH_PADDING }, + }, + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + headerBg = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Padding = new MarginPadding { Top = 10f, Bottom = 10f, Left = WIDTH_PADDING, Right = WIDTH_PADDING }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"Chat Channels", + TextSize = 20, + Shadow = false, + }, + search = new HeaderSearchTextBox + { + RelativeSizeAxes = Axes.X, + PlaceholderText = @"Search", + Exit = Hide, + }, + }, + }, + }, + }, + }; + + search.Current.ValueChanged += newValue => sectionsFlow.SearchTerm = newValue; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bg.Colour = colours.Gray3; + triangles.ColourDark = colours.Gray3; + triangles.ColourLight = OsuColour.FromHex(@"353535"); + + headerBg.Colour = colours.Gray2.Opacity(0.75f); + } + + protected override void OnFocus(InputState state) + { + GetContainingInputManager().ChangeFocus(search); + base.OnFocus(state); + } + + protected override void PopIn() + { + if (Alpha == 0) this.MoveToY(DrawHeight); + + this.FadeIn(transition_duration, Easing.OutQuint); + this.MoveToY(0, transition_duration, Easing.OutQuint); + + search.HoldFocus = true; + base.PopIn(); + } + + protected override void PopOut() + { + this.FadeOut(transition_duration, Easing.InSine); + this.MoveToY(DrawHeight, transition_duration, Easing.InSine); + + search.HoldFocus = false; + base.PopOut(); + } + + private class HeaderSearchTextBox : SearchTextBox + { + protected override Color4 BackgroundFocused => Color4.Black.Opacity(0.2f); + protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.2f); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index b020e13c38..50d09b6ef6 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -1,261 +1,261 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; -using osu.Game.Users; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Chat -{ - public class ChatLine : Container - { - private static readonly Color4[] username_colours = - { - OsuColour.FromHex("588c7e"), - OsuColour.FromHex("b2a367"), - OsuColour.FromHex("c98f65"), - OsuColour.FromHex("bc5151"), - OsuColour.FromHex("5c8bd6"), - OsuColour.FromHex("7f6ab7"), - OsuColour.FromHex("a368ad"), - OsuColour.FromHex("aa6880"), - - OsuColour.FromHex("6fad9b"), - OsuColour.FromHex("f2e394"), - OsuColour.FromHex("f2ae72"), - OsuColour.FromHex("f98f8a"), - OsuColour.FromHex("7daef4"), - OsuColour.FromHex("a691f2"), - OsuColour.FromHex("c894d3"), - OsuColour.FromHex("d895b0"), - - OsuColour.FromHex("53c4a1"), - OsuColour.FromHex("eace5c"), - OsuColour.FromHex("ea8c47"), - OsuColour.FromHex("fc4f4f"), - OsuColour.FromHex("3d94ea"), - OsuColour.FromHex("7760ea"), - OsuColour.FromHex("af52c6"), - OsuColour.FromHex("e25696"), - - OsuColour.FromHex("677c66"), - OsuColour.FromHex("9b8732"), - OsuColour.FromHex("8c5129"), - OsuColour.FromHex("8c3030"), - OsuColour.FromHex("1f5d91"), - OsuColour.FromHex("4335a5"), - OsuColour.FromHex("812a96"), - OsuColour.FromHex("992861"), - }; - - public const float LEFT_PADDING = message_padding + padding * 2; - - private const float padding = 15; - private const float message_padding = 200; - private const float action_padding = 3; - private const float text_size = 20; - - private Color4 customUsernameColour; - - private OsuSpriteText timestamp; - - public ChatLine(Message message) - { - Message = message; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Left = padding, Right = padding }; - } - - private ChannelManager chatManager; - - private Message message; - private OsuSpriteText username; - private LinkFlowContainer contentFlow; - - public LinkFlowContainer ContentFlow => contentFlow; - - public Message Message - { - get => message; - set - { - if (message == value) return; - - message = MessageFormatter.FormatMessage(value); - - if (!IsLoaded) - return; - - updateMessageContent(); - } - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, ChannelManager chatManager) - { - this.chatManager = chatManager; - customUsernameColour = colours.ChatBlue; - } - - private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); - - protected override void LoadComplete() - { - base.LoadComplete(); - - bool hasBackground = senderHasBackground; - - Drawable effectedUsername = username = new OsuSpriteText - { - Font = @"Exo2.0-BoldItalic", - Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], - TextSize = text_size, - }; - - if (hasBackground) - { - // Background effect - effectedUsername = new Container - { - AutoSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters - { - Roundness = 1, - Offset = new Vector2(0, 3), - Radius = 3, - Colour = Color4.Black.Opacity(0.3f), - Type = EdgeEffectType.Shadow, - }, - // Drop shadow effect - Child = new Container - { - AutoSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters - { - Radius = 1, - Colour = OsuColour.FromHex(message.Sender.Colour), - Type = EdgeEffectType.Shadow, - }, - Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 }, - Y = 3, - Child = username, - } - }; - } - - Children = new Drawable[] - { - new Container - { - Size = new Vector2(message_padding, text_size), - Children = new Drawable[] - { - timestamp = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = @"Exo2.0-SemiBold", - FixedWidth = true, - TextSize = text_size * 0.75f, - }, - new MessageSender(message.Sender) - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Child = effectedUsername, - }, - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = message_padding + padding }, - Children = new Drawable[] - { - contentFlow = new LinkFlowContainer(t => - { - if (Message.IsAction) - { - t.Font = @"Exo2.0-MediumItalic"; - - if (senderHasBackground) - t.Colour = OsuColour.FromHex(message.Sender.Colour); - } - - t.TextSize = text_size; - }) - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - } - } - } - }; - - updateMessageContent(); - FinishTransforms(true); - } - - private void updateMessageContent() - { - this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); - timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); - - timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; - username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); - - // remove non-existent channels from the link list - message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true); - - contentFlow.Clear(); - contentFlow.AddLinks(message.DisplayContent, message.Links); - } - - private class MessageSender : OsuClickableContainer, IHasContextMenu - { - private readonly User sender; - - private Action startChatAction; - - public MessageSender(User sender) - { - this.sender = sender; - } - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile, ChannelManager chatManager) - { - Action = () => profile?.ShowUser(sender); - startChatAction = () => chatManager?.OpenUserChannel(sender); - } - - public MenuItem[] ContextMenuItems => new MenuItem[] - { - new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action), - new OsuMenuItem("Start Chat", MenuItemType.Highlighted, startChatAction), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Chat +{ + public class ChatLine : Container + { + private static readonly Color4[] username_colours = + { + OsuColour.FromHex("588c7e"), + OsuColour.FromHex("b2a367"), + OsuColour.FromHex("c98f65"), + OsuColour.FromHex("bc5151"), + OsuColour.FromHex("5c8bd6"), + OsuColour.FromHex("7f6ab7"), + OsuColour.FromHex("a368ad"), + OsuColour.FromHex("aa6880"), + + OsuColour.FromHex("6fad9b"), + OsuColour.FromHex("f2e394"), + OsuColour.FromHex("f2ae72"), + OsuColour.FromHex("f98f8a"), + OsuColour.FromHex("7daef4"), + OsuColour.FromHex("a691f2"), + OsuColour.FromHex("c894d3"), + OsuColour.FromHex("d895b0"), + + OsuColour.FromHex("53c4a1"), + OsuColour.FromHex("eace5c"), + OsuColour.FromHex("ea8c47"), + OsuColour.FromHex("fc4f4f"), + OsuColour.FromHex("3d94ea"), + OsuColour.FromHex("7760ea"), + OsuColour.FromHex("af52c6"), + OsuColour.FromHex("e25696"), + + OsuColour.FromHex("677c66"), + OsuColour.FromHex("9b8732"), + OsuColour.FromHex("8c5129"), + OsuColour.FromHex("8c3030"), + OsuColour.FromHex("1f5d91"), + OsuColour.FromHex("4335a5"), + OsuColour.FromHex("812a96"), + OsuColour.FromHex("992861"), + }; + + public const float LEFT_PADDING = message_padding + padding * 2; + + private const float padding = 15; + private const float message_padding = 200; + private const float action_padding = 3; + private const float text_size = 20; + + private Color4 customUsernameColour; + + private OsuSpriteText timestamp; + + public ChatLine(Message message) + { + Message = message; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Left = padding, Right = padding }; + } + + private ChannelManager chatManager; + + private Message message; + private OsuSpriteText username; + private LinkFlowContainer contentFlow; + + public LinkFlowContainer ContentFlow => contentFlow; + + public Message Message + { + get => message; + set + { + if (message == value) return; + + message = MessageFormatter.FormatMessage(value); + + if (!IsLoaded) + return; + + updateMessageContent(); + } + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, ChannelManager chatManager) + { + this.chatManager = chatManager; + customUsernameColour = colours.ChatBlue; + } + + private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); + + protected override void LoadComplete() + { + base.LoadComplete(); + + bool hasBackground = senderHasBackground; + + Drawable effectedUsername = username = new OsuSpriteText + { + Font = @"Exo2.0-BoldItalic", + Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], + TextSize = text_size, + }; + + if (hasBackground) + { + // Background effect + effectedUsername = new Container + { + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Roundness = 1, + Offset = new Vector2(0, 3), + Radius = 3, + Colour = Color4.Black.Opacity(0.3f), + Type = EdgeEffectType.Shadow, + }, + // Drop shadow effect + Child = new Container + { + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Radius = 1, + Colour = OsuColour.FromHex(message.Sender.Colour), + Type = EdgeEffectType.Shadow, + }, + Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 }, + Y = 3, + Child = username, + } + }; + } + + Children = new Drawable[] + { + new Container + { + Size = new Vector2(message_padding, text_size), + Children = new Drawable[] + { + timestamp = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = @"Exo2.0-SemiBold", + FixedWidth = true, + TextSize = text_size * 0.75f, + }, + new MessageSender(message.Sender) + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Child = effectedUsername, + }, + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = message_padding + padding }, + Children = new Drawable[] + { + contentFlow = new LinkFlowContainer(t => + { + if (Message.IsAction) + { + t.Font = @"Exo2.0-MediumItalic"; + + if (senderHasBackground) + t.Colour = OsuColour.FromHex(message.Sender.Colour); + } + + t.TextSize = text_size; + }) + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + } + } + } + }; + + updateMessageContent(); + FinishTransforms(true); + } + + private void updateMessageContent() + { + this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); + timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); + + timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; + username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); + + // remove non-existent channels from the link list + message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true); + + contentFlow.Clear(); + contentFlow.AddLinks(message.DisplayContent, message.Links); + } + + private class MessageSender : OsuClickableContainer, IHasContextMenu + { + private readonly User sender; + + private Action startChatAction; + + public MessageSender(User sender) + { + this.sender = sender; + } + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profile, ChannelManager chatManager) + { + Action = () => profile?.ShowUser(sender); + startChatAction = () => chatManager?.OpenUserChannel(sender); + } + + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action), + new OsuMenuItem("Start Chat", MenuItemType.Highlighted, startChatAction), + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs index 5d33843f97..a95b96b8fb 100644 --- a/osu.Game/Overlays/Chat/ChatTabControl.cs +++ b/osu.Game/Overlays/Chat/ChatTabControl.cs @@ -1,106 +1,106 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat -{ - public class ChatTabControl : Container, IHasCurrentValue - { - public readonly ChannelTabControl ChannelTabControl; - public readonly UserTabControl UserTabControl; - - public Bindable Current { get; } = new Bindable(); - public Action OnRequestLeave; - - public ChatTabControl() - { - Masking = false; - - Children = new Drawable[] - { - ChannelTabControl = new ChannelTabControl - { - Width = 0.5f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - OnRequestLeave = channel => OnRequestLeave?.Invoke(channel) - }, - UserTabControl = new UserTabControl - { - Width = 0.5f, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - RelativeSizeAxes = Axes.Both, - OnRequestLeave = channel => OnRequestLeave?.Invoke(channel) - }, - }; - - Current.ValueChanged += currentTabChanged; - ChannelTabControl.Current.ValueChanged += channel => - { - if (channel != null) - Current.Value = channel; - }; - UserTabControl.Current.ValueChanged += channel => - { - if (channel != null) - Current.Value = channel; - }; - } - - private void currentTabChanged(Channel channel) - { - switch (channel.Target) - { - case TargetType.User: - UserTabControl.Current.Value = channel; - ChannelTabControl.Current.Value = null; - break; - case TargetType.Channel: - ChannelTabControl.Current.Value = channel; - UserTabControl.Current.Value = null; - break; - } - } - - public void AddItem(Channel channel) - { - switch (channel.Target) - { - case TargetType.User: - UserTabControl.AddItem(channel); - break; - case TargetType.Channel: - ChannelTabControl.AddItem(channel); - break; - } - } - - public void RemoveItem(Channel channel) - { - Channel nextSelectedChannel = null; - - switch (channel.Target) - { - case TargetType.User: - UserTabControl.RemoveItem(channel); - if (Current.Value == channel) - Current.Value = UserTabControl.Items.FirstOrDefault() ?? ChannelTabControl.Items.FirstOrDefault(); - break; - case TargetType.Channel: - ChannelTabControl.RemoveItem(channel); - if (Current.Value == channel) - Current.Value = ChannelTabControl.Items.FirstOrDefault() ?? UserTabControl.Items.FirstOrDefault(); - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat +{ + public class ChatTabControl : Container, IHasCurrentValue + { + public readonly ChannelTabControl ChannelTabControl; + public readonly UserTabControl UserTabControl; + + public Bindable Current { get; } = new Bindable(); + public Action OnRequestLeave; + + public ChatTabControl() + { + Masking = false; + + Children = new Drawable[] + { + ChannelTabControl = new ChannelTabControl + { + Width = 0.5f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + OnRequestLeave = channel => OnRequestLeave?.Invoke(channel) + }, + UserTabControl = new UserTabControl + { + Width = 0.5f, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + RelativeSizeAxes = Axes.Both, + OnRequestLeave = channel => OnRequestLeave?.Invoke(channel) + }, + }; + + Current.ValueChanged += currentTabChanged; + ChannelTabControl.Current.ValueChanged += channel => + { + if (channel != null) + Current.Value = channel; + }; + UserTabControl.Current.ValueChanged += channel => + { + if (channel != null) + Current.Value = channel; + }; + } + + private void currentTabChanged(Channel channel) + { + switch (channel.Target) + { + case TargetType.User: + UserTabControl.Current.Value = channel; + ChannelTabControl.Current.Value = null; + break; + case TargetType.Channel: + ChannelTabControl.Current.Value = channel; + UserTabControl.Current.Value = null; + break; + } + } + + public void AddItem(Channel channel) + { + switch (channel.Target) + { + case TargetType.User: + UserTabControl.AddItem(channel); + break; + case TargetType.Channel: + ChannelTabControl.AddItem(channel); + break; + } + } + + public void RemoveItem(Channel channel) + { + Channel nextSelectedChannel = null; + + switch (channel.Target) + { + case TargetType.User: + UserTabControl.RemoveItem(channel); + if (Current.Value == channel) + Current.Value = UserTabControl.Items.FirstOrDefault() ?? ChannelTabControl.Items.FirstOrDefault(); + break; + case TargetType.Channel: + ChannelTabControl.RemoveItem(channel); + if (Current.Value == channel) + Current.Value = ChannelTabControl.Items.FirstOrDefault() ?? UserTabControl.Items.FirstOrDefault(); + break; + } + } + } +} diff --git a/osu.Game/Overlays/Chat/DrawableChat.cs b/osu.Game/Overlays/Chat/DrawableChat.cs index 5e9f399779..83cabfafa1 100644 --- a/osu.Game/Overlays/Chat/DrawableChat.cs +++ b/osu.Game/Overlays/Chat/DrawableChat.cs @@ -1,134 +1,134 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat -{ - public class DrawableChat : Container - { - public readonly Channel Chat; - private readonly ChatLineContainer flow; - private readonly ScrollContainer scroll; - - public DrawableChat(Channel chat) - { - Chat = chat; - - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - scroll = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - // Some chat lines have effects that slightly protrude to the bottom, - // which we do not want to mask away, hence the padding. - Padding = new MarginPadding { Bottom = 5 }, - Child = new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = flow = new ChatLineContainer - { - Padding = new MarginPadding { Left = 20, Right = 20 }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - } - }, - } - }; - - Chat.NewMessagesArrived += newMessagesArrived; - Chat.MessageRemoved += messageRemoved; - Chat.PendingMessageResolved += pendingMessageResolved; - } - - [BackgroundDependencyLoader] - private void load() - { - Scheduler.Add(() => newMessagesArrived(Chat.Messages)); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - scrollToEnd(); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - Chat.NewMessagesArrived -= newMessagesArrived; - Chat.MessageRemoved -= messageRemoved; - Chat.PendingMessageResolved -= pendingMessageResolved; - } - - private void newMessagesArrived(IEnumerable newMessages) - { - // Add up to last ChatBase.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); - - flow.AddRange(displayMessages.Select(m => new ChatLine(m))); - - if (!IsLoaded) return; - - if (scroll.IsScrolledToEnd(10) || !flow.Children.Any()) - scrollToEnd(); - - var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); - int count = staleMessages.Length - Channel.MAX_HISTORY; - - for (int i = 0; i < count; i++) - { - var d = staleMessages[i]; - if (!scroll.IsScrolledToEnd(10)) - scroll.OffsetScrollPosition(-d.DrawHeight); - d.Expire(); - } - } - - private void pendingMessageResolved(Message existing, Message updated) - { - var found = flow.Children.LastOrDefault(c => c.Message == existing); - if (found != null) - { - Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - - flow.Remove(found); - found.Message = updated; - flow.Add(found); - } - } - - private void messageRemoved(Message removed) - { - flow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); - } - - private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - - private class ChatLineContainer : FillFlowContainer - { - protected override int Compare(Drawable x, Drawable y) - { - var xC = (ChatLine)x; - var yC = (ChatLine)y; - - return xC.Message.CompareTo(yC.Message); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat +{ + public class DrawableChat : Container + { + public readonly Channel Chat; + private readonly ChatLineContainer flow; + private readonly ScrollContainer scroll; + + public DrawableChat(Channel chat) + { + Chat = chat; + + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + scroll = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + // Some chat lines have effects that slightly protrude to the bottom, + // which we do not want to mask away, hence the padding. + Padding = new MarginPadding { Bottom = 5 }, + Child = new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = flow = new ChatLineContainer + { + Padding = new MarginPadding { Left = 20, Right = 20 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + } + }, + } + }; + + Chat.NewMessagesArrived += newMessagesArrived; + Chat.MessageRemoved += messageRemoved; + Chat.PendingMessageResolved += pendingMessageResolved; + } + + [BackgroundDependencyLoader] + private void load() + { + Scheduler.Add(() => newMessagesArrived(Chat.Messages)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + scrollToEnd(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + Chat.NewMessagesArrived -= newMessagesArrived; + Chat.MessageRemoved -= messageRemoved; + Chat.PendingMessageResolved -= pendingMessageResolved; + } + + private void newMessagesArrived(IEnumerable newMessages) + { + // Add up to last ChatBase.MAX_HISTORY messages + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); + + flow.AddRange(displayMessages.Select(m => new ChatLine(m))); + + if (!IsLoaded) return; + + if (scroll.IsScrolledToEnd(10) || !flow.Children.Any()) + scrollToEnd(); + + var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + int count = staleMessages.Length - Channel.MAX_HISTORY; + + for (int i = 0; i < count; i++) + { + var d = staleMessages[i]; + if (!scroll.IsScrolledToEnd(10)) + scroll.OffsetScrollPosition(-d.DrawHeight); + d.Expire(); + } + } + + private void pendingMessageResolved(Message existing, Message updated) + { + var found = flow.Children.LastOrDefault(c => c.Message == existing); + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + flow.Remove(found); + found.Message = updated; + flow.Add(found); + } + } + + private void messageRemoved(Message removed) + { + flow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + } + + private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); + + private class ChatLineContainer : FillFlowContainer + { + protected override int Compare(Drawable x, Drawable y) + { + var xC = (ChatLine)x; + var yC = (ChatLine)y; + + return xC.Message.CompareTo(yC.Message); + } + } + } +} diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 176c01620f..ec8851799a 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -1,386 +1,386 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Chat; - -namespace osu.Game.Overlays -{ - public class ChatOverlay : OsuFocusedOverlayContainer - { - private const float textbox_height = 60; - private const float channel_selection_min_height = 0.3f; - - private ChannelManager channelManager; - - private readonly Container currentChatContainer; - private readonly List loadedChannels = new List(); - - private readonly LoadingAnimation loading; - - private readonly FocusedTextBox textbox; - - private const int transition_length = 500; - - public const float DEFAULT_HEIGHT = 0.4f; - - public const float TAB_AREA_HEIGHT = 50; - - private readonly ChatTabControl chatTabControl; - - private readonly Container chatContainer; - private readonly Container tabsArea; - private readonly Box chatBackground; - private readonly Box tabBackground; - - public Bindable ChatHeight { get; set; } - - private readonly Container channelSelectionContainer; - private readonly ChannelSelectionOverlay channelSelection; - - - public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceiveMouseInputAt(screenSpacePos) || channelSelection.State == Visibility.Visible && channelSelection.ReceiveMouseInputAt(screenSpacePos); - - public ChatOverlay() - { - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - - const float padding = 5; - - Children = new Drawable[] - { - channelSelectionContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 1f - DEFAULT_HEIGHT, - Masking = true, - Children = new[] - { - channelSelection = new ChannelSelectionOverlay - { - RelativeSizeAxes = Axes.Both, - }, - }, - }, - chatContainer = new Container - { - Name = @"chat container", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Height = DEFAULT_HEIGHT, - Children = new[] - { - new Container - { - Name = @"chat area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = TAB_AREA_HEIGHT }, - Children = new Drawable[] - { - chatBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - currentChatContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Bottom = textbox_height - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = textbox_height, - Padding = new MarginPadding - { - Top = padding * 2, - Bottom = padding * 2, - Left = ChatLine.LEFT_PADDING + padding * 2, - Right = padding * 2, - }, - Children = new Drawable[] - { - textbox = new FocusedTextBox - { - RelativeSizeAxes = Axes.Both, - Height = 1, - PlaceholderText = "type your message", - Exit = () => State = Visibility.Hidden, - OnCommit = postMessage, - ReleaseFocusOnCommit = false, - HoldFocus = true, - } - } - }, - loading = new LoadingAnimation(), - } - }, - tabsArea = new Container - { - Name = @"tabs area", - RelativeSizeAxes = Axes.X, - Height = TAB_AREA_HEIGHT, - Children = new Drawable[] - { - tabBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - chatTabControl = new ChatTabControl - { - RelativeSizeAxes = Axes.Both, - OnRequestLeave = channel => channelManager.JoinedChannels.Remove(channel) - } - } - }, - }, - }, - }; - - chatTabControl.Current.ValueChanged += chat => channelManager.CurrentChannel.Value = chat; - chatTabControl.ChannelTabControl.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden; - channelSelection.StateChanged += state => - { - chatTabControl.ChannelTabControl.ChannelSelectorActive.Value = state == Visibility.Visible; - - if (state == Visibility.Visible) - { - textbox.HoldFocus = false; - if (1f - ChatHeight.Value < channel_selection_min_height) - transformChatHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint); - } - else - textbox.HoldFocus = true; - }; - channelSelection.OnRequestJoin = channel => - { - if (!channelManager.JoinedChannels.Contains(channel)) - channelManager.JoinedChannels.Add(channel); - }; - channelSelection.OnRequestLeave = channel => channelManager.JoinedChannels.Remove(channel); - } - - private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) - { - channelSelection.Sections = new[] - { - new ChannelSection - { - Header = "All Channels", - Channels = channelManager.AvailableChannels, - }, - }; - } - - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (Channel newChannel in args.NewItems) - { - chatTabControl.AddItem(newChannel); - newChannel.Joined.Value = true; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (Channel removedChannel in args.OldItems) - { - chatTabControl.RemoveItem(removedChannel); - loadedChannels.Remove(loadedChannels.Find(c => c.Chat == removedChannel )); - removedChannel.Joined.Value = false; - } - break; - } - } - - private void currentChatChanged(Channel chat) - { - if (chat == null) - { - textbox.Current.Disabled = true; - currentChatContainer.Clear(false); - chatTabControl.Current.Value = null; - return; - } - - textbox.Current.Disabled = chat.ReadOnly; - - if (chatTabControl.Current.Value != chat) - Scheduler.Add(() => chatTabControl.Current.Value = chat); - - var loaded = loadedChannels.Find(d => d.Chat == chat); - if (loaded == null) - { - currentChatContainer.FadeOut(500, Easing.OutQuint); - loading.Show(); - - loaded = new DrawableChat(chat); - loadedChannels.Add(loaded); - LoadComponentAsync(loaded, l => - { - loading.Hide(); - - currentChatContainer.Clear(false); - currentChatContainer.Add(loaded); - currentChatContainer.FadeIn(500, Easing.OutQuint); - }); - } - else - { - currentChatContainer.Clear(false); - currentChatContainer.Add(loaded); - } - } - - private double startDragChatHeight; - private bool isDragging; - - protected override bool OnDragStart(InputState state) - { - isDragging = tabsArea.IsHovered; - - if (!isDragging) - return base.OnDragStart(state); - - startDragChatHeight = ChatHeight.Value; - return true; - } - - protected override bool OnDrag(InputState state) - { - if (isDragging) - { - Trace.Assert(state.Mouse.PositionMouseDown != null); - - // ReSharper disable once PossibleInvalidOperationException - double targetChatHeight = startDragChatHeight - (state.Mouse.Position.Y - state.Mouse.PositionMouseDown.Value.Y) / Parent.DrawSize.Y; - - // If the channel selection screen is shown, mind its minimum height - if (channelSelection.State == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) - targetChatHeight = 1f - channel_selection_min_height; - - ChatHeight.Value = targetChatHeight; - } - - return true; - } - - protected override bool OnDragEnd(InputState state) - { - isDragging = false; - return base.OnDragEnd(state); - } - - public override bool AcceptsFocus => true; - - protected override void OnFocus(InputState state) - { - //this is necessary as textbox is masked away and therefore can't get focus :( - GetContainingInputManager().ChangeFocus(textbox); - base.OnFocus(state); - } - - protected override void PopIn() - { - this.MoveToY(0, transition_length, Easing.OutQuint); - this.FadeIn(transition_length, Easing.OutQuint); - - textbox.HoldFocus = true; - base.PopIn(); - } - - protected override void PopOut() - { - this.MoveToY(Height, transition_length, Easing.InSine); - this.FadeOut(transition_length, Easing.InSine); - - textbox.HoldFocus = false; - base.PopOut(); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuColour colours, ChannelManager channelManager) - { - ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); - ChatHeight.ValueChanged += h => - { - chatContainer.Height = (float)h; - channelSelectionContainer.Height = 1f - (float)h; - tabBackground.FadeTo(h == 1 ? 1 : 0.8f, 200); - }; - ChatHeight.TriggerChange(); - - chatBackground.Colour = colours.ChatBlue; - loading.Show(); - - this.channelManager = channelManager; - channelManager.CurrentChannel.ValueChanged += currentChatChanged; - channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; - } - - private void postMessage(TextBox textbox, bool newText) - { - var text = textbox.Text.Trim(); - - if (string.IsNullOrWhiteSpace(text)) - return; - - if (text[0] == '/') - channelManager.PostCommand(text.Substring(1)); - else - channelManager.PostMessage(text); - - textbox.Text = string.Empty; - } - - private void transformChatHeightTo(double newChatHeight, double duration = 0, Easing easing = Easing.None) - { - this.TransformTo(this.PopulateTransform(new TransformChatHeight(), newChatHeight, duration, easing)); - } - - private class TransformChatHeight : Transform - { - private double valueAt(double time) - { - if (time < StartTime) return StartValue; - if (time >= EndTime) return EndValue; - - return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); - } - - public override string TargetMember => "ChatHeight.Value"; - - protected override void Apply(ChatOverlay d, double time) => d.ChatHeight.Value = valueAt(time); - protected override void ReadIntoStartValue(ChatOverlay d) => StartValue = d.ChatHeight.Value; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat; + +namespace osu.Game.Overlays +{ + public class ChatOverlay : OsuFocusedOverlayContainer + { + private const float textbox_height = 60; + private const float channel_selection_min_height = 0.3f; + + private ChannelManager channelManager; + + private readonly Container currentChatContainer; + private readonly List loadedChannels = new List(); + + private readonly LoadingAnimation loading; + + private readonly FocusedTextBox textbox; + + private const int transition_length = 500; + + public const float DEFAULT_HEIGHT = 0.4f; + + public const float TAB_AREA_HEIGHT = 50; + + private readonly ChatTabControl chatTabControl; + + private readonly Container chatContainer; + private readonly Container tabsArea; + private readonly Box chatBackground; + private readonly Box tabBackground; + + public Bindable ChatHeight { get; set; } + + private readonly Container channelSelectionContainer; + private readonly ChannelSelectionOverlay channelSelection; + + + public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceiveMouseInputAt(screenSpacePos) || channelSelection.State == Visibility.Visible && channelSelection.ReceiveMouseInputAt(screenSpacePos); + + public ChatOverlay() + { + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + const float padding = 5; + + Children = new Drawable[] + { + channelSelectionContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Height = 1f - DEFAULT_HEIGHT, + Masking = true, + Children = new[] + { + channelSelection = new ChannelSelectionOverlay + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + chatContainer = new Container + { + Name = @"chat container", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = DEFAULT_HEIGHT, + Children = new[] + { + new Container + { + Name = @"chat area", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = TAB_AREA_HEIGHT }, + Children = new Drawable[] + { + chatBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + currentChatContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Bottom = textbox_height + }, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = textbox_height, + Padding = new MarginPadding + { + Top = padding * 2, + Bottom = padding * 2, + Left = ChatLine.LEFT_PADDING + padding * 2, + Right = padding * 2, + }, + Children = new Drawable[] + { + textbox = new FocusedTextBox + { + RelativeSizeAxes = Axes.Both, + Height = 1, + PlaceholderText = "type your message", + Exit = () => State = Visibility.Hidden, + OnCommit = postMessage, + ReleaseFocusOnCommit = false, + HoldFocus = true, + } + } + }, + loading = new LoadingAnimation(), + } + }, + tabsArea = new Container + { + Name = @"tabs area", + RelativeSizeAxes = Axes.X, + Height = TAB_AREA_HEIGHT, + Children = new Drawable[] + { + tabBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + chatTabControl = new ChatTabControl + { + RelativeSizeAxes = Axes.Both, + OnRequestLeave = channel => channelManager.JoinedChannels.Remove(channel) + } + } + }, + }, + }, + }; + + chatTabControl.Current.ValueChanged += chat => channelManager.CurrentChannel.Value = chat; + chatTabControl.ChannelTabControl.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden; + channelSelection.StateChanged += state => + { + chatTabControl.ChannelTabControl.ChannelSelectorActive.Value = state == Visibility.Visible; + + if (state == Visibility.Visible) + { + textbox.HoldFocus = false; + if (1f - ChatHeight.Value < channel_selection_min_height) + transformChatHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint); + } + else + textbox.HoldFocus = true; + }; + channelSelection.OnRequestJoin = channel => + { + if (!channelManager.JoinedChannels.Contains(channel)) + channelManager.JoinedChannels.Add(channel); + }; + channelSelection.OnRequestLeave = channel => channelManager.JoinedChannels.Remove(channel); + } + + private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) + { + channelSelection.Sections = new[] + { + new ChannelSection + { + Header = "All Channels", + Channels = channelManager.AvailableChannels, + }, + }; + } + + private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (Channel newChannel in args.NewItems) + { + chatTabControl.AddItem(newChannel); + newChannel.Joined.Value = true; + } + break; + case NotifyCollectionChangedAction.Remove: + foreach (Channel removedChannel in args.OldItems) + { + chatTabControl.RemoveItem(removedChannel); + loadedChannels.Remove(loadedChannels.Find(c => c.Chat == removedChannel )); + removedChannel.Joined.Value = false; + } + break; + } + } + + private void currentChatChanged(Channel chat) + { + if (chat == null) + { + textbox.Current.Disabled = true; + currentChatContainer.Clear(false); + chatTabControl.Current.Value = null; + return; + } + + textbox.Current.Disabled = chat.ReadOnly; + + if (chatTabControl.Current.Value != chat) + Scheduler.Add(() => chatTabControl.Current.Value = chat); + + var loaded = loadedChannels.Find(d => d.Chat == chat); + if (loaded == null) + { + currentChatContainer.FadeOut(500, Easing.OutQuint); + loading.Show(); + + loaded = new DrawableChat(chat); + loadedChannels.Add(loaded); + LoadComponentAsync(loaded, l => + { + loading.Hide(); + + currentChatContainer.Clear(false); + currentChatContainer.Add(loaded); + currentChatContainer.FadeIn(500, Easing.OutQuint); + }); + } + else + { + currentChatContainer.Clear(false); + currentChatContainer.Add(loaded); + } + } + + private double startDragChatHeight; + private bool isDragging; + + protected override bool OnDragStart(InputState state) + { + isDragging = tabsArea.IsHovered; + + if (!isDragging) + return base.OnDragStart(state); + + startDragChatHeight = ChatHeight.Value; + return true; + } + + protected override bool OnDrag(InputState state) + { + if (isDragging) + { + Trace.Assert(state.Mouse.PositionMouseDown != null); + + // ReSharper disable once PossibleInvalidOperationException + double targetChatHeight = startDragChatHeight - (state.Mouse.Position.Y - state.Mouse.PositionMouseDown.Value.Y) / Parent.DrawSize.Y; + + // If the channel selection screen is shown, mind its minimum height + if (channelSelection.State == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) + targetChatHeight = 1f - channel_selection_min_height; + + ChatHeight.Value = targetChatHeight; + } + + return true; + } + + protected override bool OnDragEnd(InputState state) + { + isDragging = false; + return base.OnDragEnd(state); + } + + public override bool AcceptsFocus => true; + + protected override void OnFocus(InputState state) + { + //this is necessary as textbox is masked away and therefore can't get focus :( + GetContainingInputManager().ChangeFocus(textbox); + base.OnFocus(state); + } + + protected override void PopIn() + { + this.MoveToY(0, transition_length, Easing.OutQuint); + this.FadeIn(transition_length, Easing.OutQuint); + + textbox.HoldFocus = true; + base.PopIn(); + } + + protected override void PopOut() + { + this.MoveToY(Height, transition_length, Easing.InSine); + this.FadeOut(transition_length, Easing.InSine); + + textbox.HoldFocus = false; + base.PopOut(); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, OsuColour colours, ChannelManager channelManager) + { + ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); + ChatHeight.ValueChanged += h => + { + chatContainer.Height = (float)h; + channelSelectionContainer.Height = 1f - (float)h; + tabBackground.FadeTo(h == 1 ? 1 : 0.8f, 200); + }; + ChatHeight.TriggerChange(); + + chatBackground.Colour = colours.ChatBlue; + loading.Show(); + + this.channelManager = channelManager; + channelManager.CurrentChannel.ValueChanged += currentChatChanged; + channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; + channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; + } + + private void postMessage(TextBox textbox, bool newText) + { + var text = textbox.Text.Trim(); + + if (string.IsNullOrWhiteSpace(text)) + return; + + if (text[0] == '/') + channelManager.PostCommand(text.Substring(1)); + else + channelManager.PostMessage(text); + + textbox.Text = string.Empty; + } + + private void transformChatHeightTo(double newChatHeight, double duration = 0, Easing easing = Easing.None) + { + this.TransformTo(this.PopulateTransform(new TransformChatHeight(), newChatHeight, duration, easing)); + } + + private class TransformChatHeight : Transform + { + private double valueAt(double time) + { + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + + public override string TargetMember => "ChatHeight.Value"; + + protected override void Apply(ChatOverlay d, double time) => d.ChatHeight.Value = valueAt(time); + protected override void ReadIntoStartValue(ChatOverlay d) => StartValue = d.ChatHeight.Value; + } + } +} diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 790c097789..5f90ec67ad 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -1,247 +1,247 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Dialog -{ - public class PopupDialog : OsuFocusedOverlayContainer - { - public static readonly float ENTER_DURATION = 500; - public static readonly float EXIT_DURATION = 200; - private readonly Vector2 ringSize = new Vector2(100f); - private readonly Vector2 ringMinifiedSize = new Vector2(20f); - private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f); - - private readonly Container content; - private readonly Container ring; - private readonly FillFlowContainer buttonsContainer; - private readonly SpriteIcon icon; - private readonly SpriteText header; - private readonly TextFlowContainer body; - - public FontAwesome Icon - { - get { return icon.Icon; } - set { icon.Icon = value; } - } - - public string HeaderText - { - get { return header.Text; } - set { header.Text = value; } - } - - public string BodyText - { - set { body.Text = value; } - } - - public IEnumerable Buttons - { - get { return buttonsContainer.Children; } - set - { - buttonsContainer.ChildrenEnumerable = value; - foreach (PopupDialogButton b in value) - { - var action = b.Action; - b.Action = () => - { - Hide(); - action?.Invoke(); - }; - } - } - } - - private void pressButtonAtIndex(int index) - { - if (index < Buttons.Count()) - Buttons.Skip(index).First().TriggerOnClick(); - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Repeat) return false; - - if (args.Key == Key.Enter || args.Key == Key.KeypadEnter) - { - Buttons.OfType().FirstOrDefault()?.TriggerOnClick(); - return true; - } - - // press button at number if 1-9 on number row or keypad are pressed - var k = args.Key; - if (k >= Key.Number1 && k <= Key.Number9) - { - pressButtonAtIndex(k - Key.Number1); - return true; - } - - if (k >= Key.Keypad1 && k <= Key.Keypad9) - { - pressButtonAtIndex(k - Key.Keypad1); - return true; - } - - return base.OnKeyDown(state, args); - } - - protected override void PopIn() - { - base.PopIn(); - - // Reset various animations but only if the dialog animation fully completed - if (content.Alpha == 0) - { - buttonsContainer.TransformSpacingTo(buttonsEnterSpacing); - buttonsContainer.MoveToY(buttonsEnterSpacing.Y); - ring.ResizeTo(ringMinifiedSize); - } - - content.FadeIn(ENTER_DURATION, Easing.OutQuint); - ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint); - } - - protected override void PopOut() - { - base.PopOut(); - - content.FadeOut(EXIT_DURATION, Easing.InSine); - } - - public PopupDialog() - { - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 0.4f, - Alpha = 0f, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.5f), - Radius = 8, - }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"221a21"), - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourLight = OsuColour.FromHex(@"271e26"), - ColourDark = OsuColour.FromHex(@"1e171e"), - TriangleScale = 4, - }, - }, - }, - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Position = new Vector2(0f, -50f), - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - new Container - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Size = ringSize, - Margin = new MarginPadding - { - Bottom = 30, - }, - Children = new Drawable[] - { - ring = new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 5f, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0), - }, - icon = new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Icon = FontAwesome.fa_close, - Size = new Vector2(50), - }, - }, - }, - }, - }, - header = new OsuSpriteText - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - TextSize = 25, - Shadow = true, - }, - body = new OsuTextFlowContainer(t => t.TextSize = 18) - { - Padding = new MarginPadding(15), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.TopCentre, - }, - }, - }, - buttonsContainer = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - }, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Dialog +{ + public class PopupDialog : OsuFocusedOverlayContainer + { + public static readonly float ENTER_DURATION = 500; + public static readonly float EXIT_DURATION = 200; + private readonly Vector2 ringSize = new Vector2(100f); + private readonly Vector2 ringMinifiedSize = new Vector2(20f); + private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f); + + private readonly Container content; + private readonly Container ring; + private readonly FillFlowContainer buttonsContainer; + private readonly SpriteIcon icon; + private readonly SpriteText header; + private readonly TextFlowContainer body; + + public FontAwesome Icon + { + get { return icon.Icon; } + set { icon.Icon = value; } + } + + public string HeaderText + { + get { return header.Text; } + set { header.Text = value; } + } + + public string BodyText + { + set { body.Text = value; } + } + + public IEnumerable Buttons + { + get { return buttonsContainer.Children; } + set + { + buttonsContainer.ChildrenEnumerable = value; + foreach (PopupDialogButton b in value) + { + var action = b.Action; + b.Action = () => + { + Hide(); + action?.Invoke(); + }; + } + } + } + + private void pressButtonAtIndex(int index) + { + if (index < Buttons.Count()) + Buttons.Skip(index).First().TriggerOnClick(); + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) return false; + + if (args.Key == Key.Enter || args.Key == Key.KeypadEnter) + { + Buttons.OfType().FirstOrDefault()?.TriggerOnClick(); + return true; + } + + // press button at number if 1-9 on number row or keypad are pressed + var k = args.Key; + if (k >= Key.Number1 && k <= Key.Number9) + { + pressButtonAtIndex(k - Key.Number1); + return true; + } + + if (k >= Key.Keypad1 && k <= Key.Keypad9) + { + pressButtonAtIndex(k - Key.Keypad1); + return true; + } + + return base.OnKeyDown(state, args); + } + + protected override void PopIn() + { + base.PopIn(); + + // Reset various animations but only if the dialog animation fully completed + if (content.Alpha == 0) + { + buttonsContainer.TransformSpacingTo(buttonsEnterSpacing); + buttonsContainer.MoveToY(buttonsEnterSpacing.Y); + ring.ResizeTo(ringMinifiedSize); + } + + content.FadeIn(ENTER_DURATION, Easing.OutQuint); + ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint); + buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint); + buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint); + } + + protected override void PopOut() + { + base.PopOut(); + + content.FadeOut(EXIT_DURATION, Easing.InSine); + } + + public PopupDialog() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + content = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 0.4f, + Alpha = 0f, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.5f), + Radius = 8, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"221a21"), + }, + new Triangles + { + RelativeSizeAxes = Axes.Both, + ColourLight = OsuColour.FromHex(@"271e26"), + ColourDark = OsuColour.FromHex(@"1e171e"), + TriangleScale = 4, + }, + }, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Position = new Vector2(0f, -50f), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] + { + new Container + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Size = ringSize, + Margin = new MarginPadding + { + Bottom = 30, + }, + Children = new Drawable[] + { + ring = new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 5f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0), + }, + icon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Icon = FontAwesome.fa_close, + Size = new Vector2(50), + }, + }, + }, + }, + }, + header = new OsuSpriteText + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + TextSize = 25, + Shadow = true, + }, + body = new OsuTextFlowContainer(t => t.TextSize = 18) + { + Padding = new MarginPadding(15), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + }, + }, + }, + buttonsContainer = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Dialog/PopupDialogButton.cs b/osu.Game/Overlays/Dialog/PopupDialogButton.cs index 40200e6417..8c382c6f56 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogButton.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Dialog -{ - public class PopupDialogButton : DialogButton - { - public PopupDialogButton() - { - Height = 50; - BackgroundColour = OsuColour.FromHex(@"150e14"); - TextSize = 18; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Dialog +{ + public class PopupDialogButton : DialogButton + { + public PopupDialogButton() + { + Height = 50; + BackgroundColour = OsuColour.FromHex(@"150e14"); + TextSize = 18; + } + } +} diff --git a/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs b/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs index 38c5ea34c5..45a8dfd29e 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Dialog -{ - public class PopupDialogCancelButton : PopupDialogButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - ButtonColour = colours.Blue; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Dialog +{ + public class PopupDialogCancelButton : PopupDialogButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ButtonColour = colours.Blue; + } + } +} diff --git a/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs b/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs index fb21ffc00a..6c5abbe161 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Dialog -{ - public class PopupDialogOkButton : PopupDialogButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - ButtonColour = colours.Pink; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Dialog +{ + public class PopupDialogOkButton : PopupDialogButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ButtonColour = colours.Pink; + } + } +} diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index ef4d2bdc00..7caab0dd6c 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -1,81 +1,81 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays.Dialog; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays -{ - public class DialogOverlay : OsuFocusedOverlayContainer - { - private readonly Container dialogContainer; - private PopupDialog currentDialog; - - public void Push(PopupDialog dialog) - { - if (dialog == currentDialog) return; - - currentDialog?.Hide(); - currentDialog = dialog; - - dialogContainer.Add(currentDialog); - - currentDialog.Show(); - currentDialog.StateChanged += state => onDialogOnStateChanged(dialog, state); - State = Visibility.Visible; - } - - private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) - { - if (v != Visibility.Hidden) return; - - //handle the dialog being dismissed. - dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); - - if (dialog == currentDialog) - State = Visibility.Hidden; - } - - protected override void PopIn() - { - base.PopIn(); - this.FadeIn(PopupDialog.ENTER_DURATION, Easing.OutQuint); - } - - protected override void PopOut() - { - base.PopOut(); - this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); - } - - public DialogOverlay() - { - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - }, - }, - dialogContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Dialog; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays +{ + public class DialogOverlay : OsuFocusedOverlayContainer + { + private readonly Container dialogContainer; + private PopupDialog currentDialog; + + public void Push(PopupDialog dialog) + { + if (dialog == currentDialog) return; + + currentDialog?.Hide(); + currentDialog = dialog; + + dialogContainer.Add(currentDialog); + + currentDialog.Show(); + currentDialog.StateChanged += state => onDialogOnStateChanged(dialog, state); + State = Visibility.Visible; + } + + private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) + { + if (v != Visibility.Hidden) return; + + //handle the dialog being dismissed. + dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); + + if (dialog == currentDialog) + State = Visibility.Hidden; + } + + protected override void PopIn() + { + base.PopIn(); + this.FadeIn(PopupDialog.ENTER_DURATION, Easing.OutQuint); + } + + protected override void PopOut() + { + base.PopOut(); + this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); + } + + public DialogOverlay() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + }, + }, + dialogContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index d893c027c7..9715615d14 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -1,237 +1,237 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; - -namespace osu.Game.Overlays.Direct -{ - public class DirectGridPanel : DirectPanel - { - private const float horizontal_padding = 10; - private const float vertical_padding = 5; - - private FillFlowContainer bottomPanel, statusContainer; - private PlayButton playButton; - private Box progressBar; - - protected override PlayButton PlayButton => playButton; - protected override Box PreviewBar => progressBar; - - public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) - { - Width = 400; - Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image) - } - - protected override void LoadComplete() - { - base.LoadComplete(); - bottomPanel.LayoutDuration = 200; - bottomPanel.LayoutEasing = Easing.Out; - bottomPanel.Origin = Anchor.BottomLeft; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationEngine localisation) - { - Content.CornerRadius = 4; - - AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - bottomPanel = new FillFlowContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, vertical_padding), - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding }, - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), - TextSize = 18, - Font = @"Exo2.0-BoldItalic", - }, - new OsuSpriteText - { - Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = @"Exo2.0-BoldItalic", - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - }, - progressBar = new Box - { - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - BypassAutoSizeAxes = Axes.Both, - Size = new Vector2(0, 3), - Alpha = 0, - Colour = colours.Yellow, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding - { - Top = vertical_padding, - Bottom = vertical_padding, - Left = horizontal_padding, - Right = horizontal_padding, - }, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "mapped by ", - TextSize = 14, - Shadow = false, - Colour = colours.Gray5, - }, - new OsuSpriteText - { - Text = SetInfo.Metadata.Author.Username, - TextSize = 14, - Font = @"Exo2.0-SemiBoldItalic", - Shadow = false, - Colour = colours.BlueDark, - }, - }, - }, - new Container - { - AutoSizeAxes = Axes.X, - Height = 14, - Children = new[] - { - new OsuSpriteText - { - Text = $"from {SetInfo.Metadata.Source}", - TextSize = 14, - Shadow = false, - Colour = colours.Gray5, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, - }, - }, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), - }, - }, - }, - new DownloadButton - { - Size = new Vector2(30), - Margin = new MarginPadding(horizontal_padding), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Colour = colours.Gray5, - Action = StartDownload - }, - }, - }, - }, - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, - Children = new[] - { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) - { - Margin = new MarginPadding { Right = 1 }, - }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), - }, - }, - playButton = new PlayButton(SetInfo) - { - Margin = new MarginPadding { Top = 5, Left = 10 }, - Size = new Vector2(30), - Alpha = 0, - }, - statusContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5, Left = 5 }, - Spacing = new Vector2(5), - }, - }); - - if (SetInfo.OnlineInfo?.HasVideo ?? false) - { - statusContainer.Add(new IconPill(FontAwesome.fa_film)); - } - - statusContainer.Add(new BeatmapSetOnlineStatusPill(12, new MarginPadding { Horizontal = 10, Vertical = 5 }) - { - Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, - }); - } - - protected override bool OnHover(InputState state) - { - statusContainer.FadeOut(120, Easing.InOutQuint); - - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - base.OnHoverLost(state); - - statusContainer.FadeIn(120, Easing.InOutQuint); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; + +namespace osu.Game.Overlays.Direct +{ + public class DirectGridPanel : DirectPanel + { + private const float horizontal_padding = 10; + private const float vertical_padding = 5; + + private FillFlowContainer bottomPanel, statusContainer; + private PlayButton playButton; + private Box progressBar; + + protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; + + public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) + { + Width = 400; + Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image) + } + + protected override void LoadComplete() + { + base.LoadComplete(); + bottomPanel.LayoutDuration = 200; + bottomPanel.LayoutEasing = Easing.Out; + bottomPanel.Origin = Anchor.BottomLeft; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, LocalisationEngine localisation) + { + Content.CornerRadius = 4; + + AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + bottomPanel = new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, vertical_padding), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding }, + Direction = FillDirection.Vertical, + Children = new[] + { + new OsuSpriteText + { + Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + TextSize = 18, + Font = @"Exo2.0-BoldItalic", + }, + new OsuSpriteText + { + Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = @"Exo2.0-BoldItalic", + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + }, + progressBar = new Box + { + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + BypassAutoSizeAxes = Axes.Both, + Size = new Vector2(0, 3), + Alpha = 0, + Colour = colours.Yellow, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding + { + Top = vertical_padding, + Bottom = vertical_padding, + Left = horizontal_padding, + Right = horizontal_padding, + }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = "mapped by ", + TextSize = 14, + Shadow = false, + Colour = colours.Gray5, + }, + new OsuSpriteText + { + Text = SetInfo.Metadata.Author.Username, + TextSize = 14, + Font = @"Exo2.0-SemiBoldItalic", + Shadow = false, + Colour = colours.BlueDark, + }, + }, + }, + new Container + { + AutoSizeAxes = Axes.X, + Height = 14, + Children = new[] + { + new OsuSpriteText + { + Text = $"from {SetInfo.Metadata.Source}", + TextSize = 14, + Shadow = false, + Colour = colours.Gray5, + Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + }, + }, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = 20, + Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Children = GetDifficultyIcons(), + }, + }, + }, + new DownloadButton + { + Size = new Vector2(30), + Margin = new MarginPadding(horizontal_padding), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Colour = colours.Gray5, + Action = StartDownload + }, + }, + }, + }, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, + Children = new[] + { + new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) + { + Margin = new MarginPadding { Right = 1 }, + }, + new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + }, + }, + playButton = new PlayButton(SetInfo) + { + Margin = new MarginPadding { Top = 5, Left = 10 }, + Size = new Vector2(30), + Alpha = 0, + }, + statusContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5, Left = 5 }, + Spacing = new Vector2(5), + }, + }); + + if (SetInfo.OnlineInfo?.HasVideo ?? false) + { + statusContainer.Add(new IconPill(FontAwesome.fa_film)); + } + + statusContainer.Add(new BeatmapSetOnlineStatusPill(12, new MarginPadding { Horizontal = 10, Vertical = 5 }) + { + Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, + }); + } + + protected override bool OnHover(InputState state) + { + statusContainer.FadeOut(120, Easing.InOutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + base.OnHoverLost(state); + + statusContainer.FadeIn(120, Easing.InOutQuint); + } + } +} diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 403eeb7e10..13398a4a32 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -1,169 +1,169 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Colour; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Allocation; -using osu.Framework.Localisation; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; - -namespace osu.Game.Overlays.Direct -{ - public class DirectListPanel : DirectPanel - { - private const float horizontal_padding = 10; - private const float vertical_padding = 5; - private const float height = 70; - - public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap) - { - RelativeSizeAxes = Axes.X; - Height = height; - } - - private PlayButton playButton; - private Box progressBar; - - protected override PlayButton PlayButton => playButton; - protected override Box PreviewBar => progressBar; - - [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation, OsuColour colours) - { - Content.CornerRadius = 5; - - AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)), - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding }, - Children = new Drawable[] - { - new FillFlowContainer - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - LayoutEasing = Easing.OutQuint, - LayoutDuration = 120, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - playButton = new PlayButton(SetInfo) - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Size = new Vector2(height / 2), - FillMode = FillMode.Fit, - Alpha = 0, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OsuSpriteText - { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), - TextSize = 18, - Font = @"Exo2.0-BoldItalic", - }, - new OsuSpriteText - { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = @"Exo2.0-BoldItalic", - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), - }, - }, - }, - } - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding }, - Children = new Drawable[] - { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) - { - Margin = new MarginPadding { Right = 1 }, - }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "mapped by ", - TextSize = 14, - }, - new OsuSpriteText - { - Text = SetInfo.Metadata.Author.Username, - TextSize = 14, - Font = @"Exo2.0-SemiBoldItalic", - }, - }, - }, - new OsuSpriteText - { - Text = $"from {SetInfo.Metadata.Source}", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - TextSize = 14, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, - }, - }, - }, - new DownloadButton - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Size = new Vector2(height - vertical_padding * 2), - Action = StartDownload - }, - }, - }, - progressBar = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - BypassAutoSizeAxes = Axes.Y, - Size = new Vector2(0, 3), - Alpha = 0, - Colour = colours.Yellow, - }, - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; + +namespace osu.Game.Overlays.Direct +{ + public class DirectListPanel : DirectPanel + { + private const float horizontal_padding = 10; + private const float vertical_padding = 5; + private const float height = 70; + + public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap) + { + RelativeSizeAxes = Axes.X; + Height = height; + } + + private PlayButton playButton; + private Box progressBar; + + protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; + + [BackgroundDependencyLoader] + private void load(LocalisationEngine localisation, OsuColour colours) + { + Content.CornerRadius = 5; + + AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding }, + Children = new Drawable[] + { + new FillFlowContainer + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + LayoutEasing = Easing.OutQuint, + LayoutDuration = 120, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + playButton = new PlayButton(SetInfo) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Size = new Vector2(height / 2), + FillMode = FillMode.Fit, + Alpha = 0, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + TextSize = 18, + Font = @"Exo2.0-BoldItalic", + }, + new OsuSpriteText + { + Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = @"Exo2.0-BoldItalic", + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = 20, + Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Children = GetDifficultyIcons(), + }, + }, + }, + } + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding }, + Children = new Drawable[] + { + new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) + { + Margin = new MarginPadding { Right = 1 }, + }, + new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = "mapped by ", + TextSize = 14, + }, + new OsuSpriteText + { + Text = SetInfo.Metadata.Author.Username, + TextSize = 14, + Font = @"Exo2.0-SemiBoldItalic", + }, + }, + }, + new OsuSpriteText + { + Text = $"from {SetInfo.Metadata.Source}", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + TextSize = 14, + Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + }, + }, + }, + new DownloadButton + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Size = new Vector2(height - vertical_padding * 2), + Action = StartDownload + }, + }, + }, + progressBar = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + BypassAutoSizeAxes = Axes.Y, + Size = new Vector2(0, 3), + Alpha = 0, + Colour = colours.Yellow, + }, + }); + } + } +} diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index cba63b4a49..cc0123dabc 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -1,277 +1,278 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; -using osu.Framework.Input; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests; -using osu.Framework.Configuration; -using osu.Framework.Audio.Track; - -namespace osu.Game.Overlays.Direct -{ - public abstract class DirectPanel : Container - { - public readonly BeatmapSetInfo SetInfo; - - protected Box BlackBackground; - - private const double hover_transition_time = 400; - - private Container content; - - private ProgressBar progressBar; - private BeatmapManager beatmaps; - private BeatmapSetOverlay beatmapSetOverlay; - - public Track Preview => PlayButton.Preview; - public Bindable PreviewPlaying => PlayButton.Playing; - protected abstract PlayButton PlayButton { get; } - protected abstract Box PreviewBar { get; } - - protected override Container Content => content; - - protected DirectPanel(BeatmapSetInfo setInfo) - { - SetInfo = setInfo; - } - - private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 2f, - Colour = Color4.Black.Opacity(0.25f), - }; - - private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 5f), - Radius = 10f, - Colour = Color4.Black.Opacity(0.3f), - }; - - private OsuColour colours; - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay) - { - this.beatmaps = beatmaps; - this.beatmapSetOverlay = beatmapSetOverlay; - this.colours = colours; - - AddInternal(content = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = edgeEffectNormal, - Children = new[] - { - // temporary blackness until the actual background loads. - BlackBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - CreateBackground(), - progressBar = new ProgressBar - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Height = 0, - Alpha = 0, - BackgroundColour = Color4.Black.Opacity(0.7f), - FillColour = colours.Blue, - Depth = -1, - }, - } - }); - - var downloadRequest = beatmaps.GetExistingDownload(SetInfo); - - if (downloadRequest != null) - attachDownload(downloadRequest); - - beatmaps.BeatmapDownloadBegan += attachDownload; - } - - public override bool DisposeOnDeathRemoval => true; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - beatmaps.BeatmapDownloadBegan -= attachDownload; - } - - protected override void Update() - { - base.Update(); - - if (PreviewPlaying && Preview != null && Preview.IsLoaded) - { - PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); - } - } - - protected override bool OnHover(InputState state) - { - content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); - content.MoveToY(-4, hover_transition_time, Easing.OutQuint); - PlayButton.FadeIn(120, Easing.InOutQuint); - - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); - content.MoveToY(0, hover_transition_time, Easing.OutQuint); - if (!PreviewPlaying) - PlayButton.FadeOut(120, Easing.InOutQuint); - - base.OnHoverLost(state); - } - - protected override bool OnClick(InputState state) - { - ShowInformation(); - PreviewPlaying.Value = false; - return true; - } - - protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo); - - protected void StartDownload() - { - if (beatmaps.GetExistingDownload(SetInfo) != null) - { - // we already have an active download running. - content.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine).Then(); - - return; - } - - beatmaps.Download(SetInfo); - } - - private void attachDownload(DownloadBeatmapSetRequest request) - { - if (request.BeatmapSet.OnlineBeatmapSetID != SetInfo.OnlineBeatmapSetID) - return; - - progressBar.FadeIn(400, Easing.OutQuint); - progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); - - progressBar.Current.Value = 0; - - request.Failure += e => - { - progressBar.Current.Value = 0; - progressBar.FadeOut(500); - }; - - request.DownloadProgressed += progress => Schedule(() => progressBar.Current.Value = progress); - - request.Success += data => - { - progressBar.Current.Value = 1; - progressBar.FillColour = colours.Yellow; - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - this.FadeInFromZero(200, Easing.Out); - - PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); - PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); - } - - protected List GetDifficultyIcons() - { - var icons = new List(); - - foreach (var b in SetInfo.Beatmaps) - icons.Add(new DifficultyIcon(b)); - - return icons; - } - - protected Drawable CreateBackground() => new DelayedLoadWrapper( - new BeatmapSetCover(SetInfo) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - OnLoadComplete = d => - { - d.FadeInFromZero(400, Easing.Out); - BlackBackground.Delay(400).FadeOut(); - }, - }, 300) - { - RelativeSizeAxes = Axes.Both, - }; - - public class Statistic : FillFlowContainer - { - private readonly SpriteText text; - - private int value; - - public int Value - { - get { return value; } - set - { - this.value = value; - text.Text = Value.ToString(@"N0"); - } - } - - public Statistic(FontAwesome icon, int value = 0) - { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Horizontal; - Spacing = new Vector2(5f, 0f); - - Children = new Drawable[] - { - text = new OsuSpriteText - { - Font = @"Exo2.0-SemiBoldItalic", - }, - new SpriteIcon - { - Icon = icon, - Shadow = true, - Size = new Vector2(14), - Margin = new MarginPadding { Top = 1 }, - }, - }; - - Value = value; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; +using osu.Framework.Input; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests; +using osu.Framework.Configuration; +using osu.Framework.Audio.Track; + +namespace osu.Game.Overlays.Direct +{ + public abstract class DirectPanel : Container + { + public readonly BeatmapSetInfo SetInfo; + + protected Box BlackBackground; + + private const double hover_transition_time = 400; + + private Container content; + + private ProgressBar progressBar; + private BeatmapManager beatmaps; + private BeatmapSetOverlay beatmapSetOverlay; + + public Track Preview => PlayButton.Preview; + public Bindable PreviewPlaying => PlayButton.Playing; + protected abstract PlayButton PlayButton { get; } + protected abstract Box PreviewBar { get; } + + protected override Container Content => content; + + protected DirectPanel(BeatmapSetInfo setInfo) + { + SetInfo = setInfo; + } + + private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 2f, + Colour = Color4.Black.Opacity(0.25f), + }; + + private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 5f), + Radius = 10f, + Colour = Color4.Black.Opacity(0.3f), + }; + + private OsuColour colours; + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay) + { + this.beatmaps = beatmaps; + this.beatmapSetOverlay = beatmapSetOverlay; + this.colours = colours; + + AddInternal(content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = edgeEffectNormal, + Children = new[] + { + // temporary blackness until the actual background loads. + BlackBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + CreateBackground(), + progressBar = new ProgressBar + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 0, + Alpha = 0, + BackgroundColour = Color4.Black.Opacity(0.7f), + FillColour = colours.Blue, + Depth = -1, + }, + } + }); + + var downloadRequest = beatmaps.GetExistingDownload(SetInfo); + + if (downloadRequest != null) + attachDownload(downloadRequest); + + beatmaps.BeatmapDownloadBegan += attachDownload; + } + + public override bool DisposeOnDeathRemoval => true; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + beatmaps.BeatmapDownloadBegan -= attachDownload; + } + + protected override void Update() + { + base.Update(); + + if (PreviewPlaying && Preview != null && Preview.IsLoaded) + { + PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); + } + } + + protected override bool OnHover(InputState state) + { + content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); + content.MoveToY(-4, hover_transition_time, Easing.OutQuint); + PlayButton.FadeIn(120, Easing.InOutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); + content.MoveToY(0, hover_transition_time, Easing.OutQuint); + if (!PreviewPlaying) + PlayButton.FadeOut(120, Easing.InOutQuint); + + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + ShowInformation(); + PreviewPlaying.Value = false; + return true; + } + + protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo); + + protected void StartDownload() + { + if (beatmaps.GetExistingDownload(SetInfo) != null) + { + // we already have an active download running. + content.MoveToX(-5, 50, Easing.OutSine).Then() + .MoveToX(5, 100, Easing.InOutSine).Then() + .MoveToX(-5, 100, Easing.InOutSine).Then() + .MoveToX(0, 50, Easing.InSine).Then(); + + return; + } + + beatmaps.Download(SetInfo); + } + + private void attachDownload(DownloadBeatmapSetRequest request) + { + if (request.BeatmapSet.OnlineBeatmapSetID != SetInfo.OnlineBeatmapSetID) + return; + + progressBar.FadeIn(400, Easing.OutQuint); + progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); + + progressBar.Current.Value = 0; + + request.Failure += e => + { + progressBar.Current.Value = 0; + progressBar.FadeOut(500); + }; + + request.DownloadProgressed += progress => Schedule(() => progressBar.Current.Value = progress); + + request.Success += data => + { + progressBar.Current.Value = 1; + progressBar.FillColour = colours.Yellow; + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(200, Easing.Out); + + PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); + PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); + } + + protected List GetDifficultyIcons() + { + var icons = new List(); + + foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty)) + icons.Add(new DifficultyIcon(b)); + + return icons; + } + + protected Drawable CreateBackground() => new DelayedLoadWrapper( + new BeatmapSetCover(SetInfo) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + OnLoadComplete = d => + { + d.FadeInFromZero(400, Easing.Out); + BlackBackground.Delay(400).FadeOut(); + }, + }, 300) + { + RelativeSizeAxes = Axes.Both, + }; + + public class Statistic : FillFlowContainer + { + private readonly SpriteText text; + + private int value; + + public int Value + { + get { return value; } + set + { + this.value = value; + text.Text = Value.ToString(@"N0"); + } + } + + public Statistic(FontAwesome icon, int value = 0) + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(5f, 0f); + + Children = new Drawable[] + { + text = new OsuSpriteText + { + Font = @"Exo2.0-SemiBoldItalic", + }, + new SpriteIcon + { + Icon = icon, + Shadow = true, + Size = new Vector2(14), + Margin = new MarginPadding { Top = 1 }, + }, + }; + + Value = value; + } + } + } +} diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index 2b29b9c5f9..f01c9dac59 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -1,53 +1,53 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Overlays.Direct -{ - public class DownloadButton : OsuClickableContainer - { - private readonly SpriteIcon icon; - - public DownloadButton() - { - Children = new Drawable[] - { - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(30), - Icon = FontAwesome.fa_osu_chevron_down_o, - }, - }; - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - icon.ScaleTo(0.9f, 1000, Easing.Out); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - icon.ScaleTo(1f, 500, Easing.OutElastic); - return base.OnMouseUp(state, args); - } - - protected override bool OnHover(InputState state) - { - icon.ScaleTo(1.1f, 500, Easing.OutElastic); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - icon.ScaleTo(1f, 500, Easing.OutElastic); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Overlays.Direct +{ + public class DownloadButton : OsuClickableContainer + { + private readonly SpriteIcon icon; + + public DownloadButton() + { + Children = new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(30), + Icon = FontAwesome.fa_osu_chevron_down_o, + }, + }; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + icon.ScaleTo(0.9f, 1000, Easing.Out); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + icon.ScaleTo(1f, 500, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + + protected override bool OnHover(InputState state) + { + icon.ScaleTo(1.1f, 500, Easing.OutElastic); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + icon.ScaleTo(1f, 500, Easing.OutElastic); + } + } +} diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 84a09547aa..c77994efb2 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -1,117 +1,117 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Overlays.SearchableList; -using osu.Game.Rulesets; - -namespace osu.Game.Overlays.Direct -{ - public class FilterControl : SearchableListFilterControl - { - public readonly Bindable Ruleset = new Bindable(); - private FillFlowContainer modeButtons; - - protected override Color4 BackgroundColour => OsuColour.FromHex(@"384552"); - protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked; - protected override Drawable CreateSupplementaryControls() - { - modeButtons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10f, 0f), - }; - - return modeButtons; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuGame game, RulesetStore rulesets, OsuColour colours) - { - DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark; - - Ruleset.BindTo(game?.Ruleset ?? new Bindable { Value = rulesets.GetRuleset(0) }); - foreach (var r in rulesets.AvailableRulesets) - { - modeButtons.Add(new RulesetToggleButton(Ruleset, r)); - } - } - - private class RulesetToggleButton : OsuClickableContainer - { - private Drawable icon - { - get { return iconContainer.Icon; } - set { iconContainer.Icon = value; } - } - - private RulesetInfo ruleset; - public RulesetInfo Ruleset - { - get { return ruleset; } - set - { - ruleset = value; - icon = Ruleset.CreateInstance().CreateIcon(); - } - } - - private readonly Bindable bindable; - - private readonly ConstrainedIconContainer iconContainer; - - private void Bindable_ValueChanged(RulesetInfo obj) - { - iconContainer.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100); - } - - public RulesetToggleButton(Bindable bindable, RulesetInfo ruleset) - { - this.bindable = bindable; - AutoSizeAxes = Axes.Both; - - Children = new[] - { - iconContainer = new ConstrainedIconContainer - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Size = new Vector2(32), - } - }; - - Ruleset = ruleset; - bindable.ValueChanged += Bindable_ValueChanged; - Bindable_ValueChanged(bindable.Value); - Action = () => bindable.Value = Ruleset; - } - - protected override void Dispose(bool isDisposing) - { - if (bindable != null) - bindable.ValueChanged -= Bindable_ValueChanged; - base.Dispose(isDisposing); - } - } - } - - public enum DirectSortCriteria - { - Relevance, - Title, - Artist, - Creator, - Difficulty, - Ranked, - Rating, - Plays, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays.SearchableList; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.Direct +{ + public class FilterControl : SearchableListFilterControl + { + public readonly Bindable Ruleset = new Bindable(); + private FillFlowContainer modeButtons; + + protected override Color4 BackgroundColour => OsuColour.FromHex(@"384552"); + protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked; + protected override Drawable CreateSupplementaryControls() + { + modeButtons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f, 0f), + }; + + return modeButtons; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame game, RulesetStore rulesets, OsuColour colours) + { + DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark; + + Ruleset.BindTo(game?.Ruleset ?? new Bindable { Value = rulesets.GetRuleset(0) }); + foreach (var r in rulesets.AvailableRulesets) + { + modeButtons.Add(new RulesetToggleButton(Ruleset, r)); + } + } + + private class RulesetToggleButton : OsuClickableContainer + { + private Drawable icon + { + get { return iconContainer.Icon; } + set { iconContainer.Icon = value; } + } + + private RulesetInfo ruleset; + public RulesetInfo Ruleset + { + get { return ruleset; } + set + { + ruleset = value; + icon = Ruleset.CreateInstance().CreateIcon(); + } + } + + private readonly Bindable bindable; + + private readonly ConstrainedIconContainer iconContainer; + + private void Bindable_ValueChanged(RulesetInfo obj) + { + iconContainer.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100); + } + + public RulesetToggleButton(Bindable bindable, RulesetInfo ruleset) + { + this.bindable = bindable; + AutoSizeAxes = Axes.Both; + + Children = new[] + { + iconContainer = new ConstrainedIconContainer + { + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Size = new Vector2(32), + } + }; + + Ruleset = ruleset; + bindable.ValueChanged += Bindable_ValueChanged; + Bindable_ValueChanged(bindable.Value); + Action = () => bindable.Value = Ruleset; + } + + protected override void Dispose(bool isDisposing) + { + if (bindable != null) + bindable.ValueChanged -= Bindable_ValueChanged; + base.Dispose(isDisposing); + } + } + } + + public enum DirectSortCriteria + { + Relevance, + Title, + Artist, + Creator, + Difficulty, + Ranked, + Rating, + Plays, + } +} diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index 252e732614..01180f1fde 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.SearchableList; - -namespace osu.Game.Overlays.Direct -{ - public class Header : SearchableListHeader - { - protected override Color4 BackgroundColour => OsuColour.FromHex(@"252f3a"); - - protected override DirectTab DefaultTab => DirectTab.Search; - protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", TextSize = 25 }; - protected override FontAwesome Icon => FontAwesome.fa_osu_chevron_down_o; - - public Header() - { - Tabs.Current.Value = DirectTab.NewestMaps; - Tabs.Current.TriggerChange(); - } - } - - public enum DirectTab - { - Search, - [Description("Newest Maps")] - NewestMaps = DirectSortCriteria.Ranked, - [Description("Top Rated")] - TopRated = DirectSortCriteria.Rating, - [Description("Most Played")] - MostPlayed = DirectSortCriteria.Plays, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.SearchableList; + +namespace osu.Game.Overlays.Direct +{ + public class Header : SearchableListHeader + { + protected override Color4 BackgroundColour => OsuColour.FromHex(@"252f3a"); + + protected override DirectTab DefaultTab => DirectTab.Search; + protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", TextSize = 25 }; + protected override FontAwesome Icon => FontAwesome.fa_osu_chevron_down_o; + + public Header() + { + Tabs.Current.Value = DirectTab.NewestMaps; + Tabs.Current.TriggerChange(); + } + } + + public enum DirectTab + { + Search, + [Description("Newest Maps")] + NewestMaps = DirectSortCriteria.Ranked, + [Description("Top Rated")] + TopRated = DirectSortCriteria.Rating, + [Description("Most Played")] + MostPlayed = DirectSortCriteria.Plays, + } +} diff --git a/osu.Game/Overlays/Direct/IconPill.cs b/osu.Game/Overlays/Direct/IconPill.cs index 33b67bdf13..61a0c63814 100644 --- a/osu.Game/Overlays/Direct/IconPill.cs +++ b/osu.Game/Overlays/Direct/IconPill.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Direct -{ - public class IconPill : CircularContainer - { - public IconPill(FontAwesome icon) - { - AutoSizeAxes = Axes.Both; - Masking = true; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f, - }, - new Container - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding(5), - Child = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = icon, - Size = new Vector2(12), - }, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Direct +{ + public class IconPill : CircularContainer + { + public IconPill(FontAwesome icon) + { + AutoSizeAxes = Axes.Both; + Masking = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding(5), + Child = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = icon, + Size = new Vector2(12), + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 0fb988ead7..9f36d5acb7 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -1,213 +1,213 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.IO.Stores; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Direct -{ - public class PlayButton : Container - { - public readonly Bindable Playing = new Bindable(); - public Track Preview { get; private set; } - - private BeatmapSetInfo beatmapSet; - - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - Playing.Value = false; - trackLoader = null; - Preview = null; - } - } - - private Color4 hoverColour; - private readonly SpriteIcon icon; - private readonly LoadingAnimation loadingAnimation; - - private readonly BindableDouble muteBindable = new BindableDouble(); - - private const float transition_duration = 500; - - private bool loading - { - set - { - if (value) - { - loadingAnimation.Show(); - icon.FadeOut(transition_duration * 5, Easing.OutQuint); - } - else - { - loadingAnimation.Hide(); - icon.FadeIn(transition_duration, Easing.OutQuint); - } - } - } - - public PlayButton(BeatmapSetInfo setInfo = null) - { - BeatmapSet = setInfo; - AddRange(new Drawable[] - { - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_play, - }, - loadingAnimation = new LoadingAnimation(), - }); - - Playing.ValueChanged += playing => - { - icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play; - icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); - updatePreviewTrack(playing); - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour, AudioManager audio) - { - hoverColour = colour.Yellow; - this.audio = audio; - } - - protected override bool OnClick(InputState state) - { - Playing.Value = !Playing.Value; - return true; - } - - protected override bool OnHover(InputState state) - { - icon.FadeColour(hoverColour, 120, Easing.InOutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - if (!Playing.Value) - icon.FadeColour(Color4.White, 120, Easing.InOutQuint); - base.OnHoverLost(state); - } - - protected override void Update() - { - base.Update(); - - if (Preview?.HasCompleted ?? false) - { - Playing.Value = false; - Preview = null; - } - } - - private void updatePreviewTrack(bool playing) - { - if (playing) - { - if (Preview == null) - { - beginAudioLoad(); - return; - } - - Preview.Restart(); - - audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable); - } - else - { - audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); - - Preview?.Stop(); - loading = false; - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - Playing.Value = false; - } - - private TrackLoader trackLoader; - private AudioManager audio; - - private void beginAudioLoad() - { - if (trackLoader != null) - { - Preview = trackLoader.Preview; - Playing.TriggerChange(); - return; - } - - loading = true; - - LoadComponentAsync(trackLoader = new TrackLoader($"https://b.ppy.sh/preview/{BeatmapSet.OnlineBeatmapSetID}.mp3"), - d => - { - // We may have been replaced by another loader - if (trackLoader != d) return; - - Preview = d?.Preview; - Playing.TriggerChange(); - loading = false; - Add(trackLoader); - }); - } - - private class TrackLoader : Drawable - { - private readonly string preview; - - public Track Preview; - private TrackManager trackManager; - - public TrackLoader(string preview) - { - this.preview = preview; - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio, FrameworkConfigManager config) - { - // create a local trackManager to bypass the mute we are applying above. - audio.AddItem(trackManager = new TrackManager(new OnlineStore())); - - // add back the user's music volume setting (since we are no longer in the global TrackManager's hierarchy). - config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume); - - Preview = trackManager.Get(preview); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - trackManager?.Dispose(); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.IO.Stores; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Direct +{ + public class PlayButton : Container + { + public readonly Bindable Playing = new Bindable(); + public Track Preview { get; private set; } + + private BeatmapSetInfo beatmapSet; + + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + Playing.Value = false; + trackLoader = null; + Preview = null; + } + } + + private Color4 hoverColour; + private readonly SpriteIcon icon; + private readonly LoadingAnimation loadingAnimation; + + private readonly BindableDouble muteBindable = new BindableDouble(); + + private const float transition_duration = 500; + + private bool loading + { + set + { + if (value) + { + loadingAnimation.Show(); + icon.FadeOut(transition_duration * 5, Easing.OutQuint); + } + else + { + loadingAnimation.Hide(); + icon.FadeIn(transition_duration, Easing.OutQuint); + } + } + } + + public PlayButton(BeatmapSetInfo setInfo = null) + { + BeatmapSet = setInfo; + AddRange(new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_play, + }, + loadingAnimation = new LoadingAnimation(), + }); + + Playing.ValueChanged += playing => + { + icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play; + icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); + updatePreviewTrack(playing); + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour, AudioManager audio) + { + hoverColour = colour.Yellow; + this.audio = audio; + } + + protected override bool OnClick(InputState state) + { + Playing.Value = !Playing.Value; + return true; + } + + protected override bool OnHover(InputState state) + { + icon.FadeColour(hoverColour, 120, Easing.InOutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if (!Playing.Value) + icon.FadeColour(Color4.White, 120, Easing.InOutQuint); + base.OnHoverLost(state); + } + + protected override void Update() + { + base.Update(); + + if (Preview?.HasCompleted ?? false) + { + Playing.Value = false; + Preview = null; + } + } + + private void updatePreviewTrack(bool playing) + { + if (playing) + { + if (Preview == null) + { + beginAudioLoad(); + return; + } + + Preview.Restart(); + + audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable); + } + else + { + audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); + + Preview?.Stop(); + loading = false; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Playing.Value = false; + } + + private TrackLoader trackLoader; + private AudioManager audio; + + private void beginAudioLoad() + { + if (trackLoader != null) + { + Preview = trackLoader.Preview; + Playing.TriggerChange(); + return; + } + + loading = true; + + LoadComponentAsync(trackLoader = new TrackLoader($"https://b.ppy.sh/preview/{BeatmapSet.OnlineBeatmapSetID}.mp3"), + d => + { + // We may have been replaced by another loader + if (trackLoader != d) return; + + Preview = d?.Preview; + Playing.TriggerChange(); + loading = false; + Add(trackLoader); + }); + } + + private class TrackLoader : Drawable + { + private readonly string preview; + + public Track Preview; + private TrackManager trackManager; + + public TrackLoader(string preview) + { + this.preview = preview; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, FrameworkConfigManager config) + { + // create a local trackManager to bypass the mute we are applying above. + audio.AddItem(trackManager = new TrackManager(new OnlineStore())); + + // add back the user's music volume setting (since we are no longer in the global TrackManager's hierarchy). + config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume); + + Preview = trackManager.Get(preview); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + trackManager?.Dispose(); + } + } + } +} diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 3f1aa04c36..6c9433836a 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -1,348 +1,348 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Threading; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Direct; -using osu.Game.Overlays.SearchableList; -using osu.Game.Rulesets; -using OpenTK.Graphics; - -namespace osu.Game.Overlays -{ - public class DirectOverlay : SearchableListOverlay - { - private const float panel_padding = 10f; - - private APIAccess api; - private RulesetStore rulesets; - private BeatmapManager beatmaps; - - private readonly FillFlowContainer resultCountsContainer; - private readonly OsuSpriteText resultCountsText; - private FillFlowContainer panels; - private DirectPanel playing; - - protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74"); - protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71"); - protected override Color4 TrianglesColourDark => OsuColour.FromHex(@"3f5265"); - - protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); - - private IEnumerable beatmapSets; - - public IEnumerable BeatmapSets - { - get { return beatmapSets; } - set - { - if (beatmapSets?.Equals(value) ?? false) return; - - beatmapSets = value?.ToList(); - - if (beatmapSets == null) return; - - var artists = new List(); - var songs = new List(); - var tags = new List(); - foreach (var s in beatmapSets) - { - artists.Add(s.Metadata.Artist); - songs.Add(s.Metadata.Title); - tags.AddRange(s.Metadata.Tags.Split(' ')); - } - - ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags)); - } - } - - private ResultCounts resultAmounts; - - public ResultCounts ResultAmounts - { - get { return resultAmounts; } - set - { - if (value == ResultAmounts) return; - resultAmounts = value; - - updateResultCounts(); - } - } - - public DirectOverlay() - { - RelativeSizeAxes = Axes.Both; - - // osu!direct colours are not part of the standard palette - - FirstWaveColour = OsuColour.FromHex(@"19b0e2"); - SecondWaveColour = OsuColour.FromHex(@"2280a2"); - ThirdWaveColour = OsuColour.FromHex(@"005774"); - FourthWaveColour = OsuColour.FromHex(@"003a4e"); - - ScrollFlow.Children = new Drawable[] - { - resultCountsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Found ", - TextSize = 15, - }, - resultCountsText = new OsuSpriteText - { - TextSize = 15, - Font = @"Exo2.0-Bold", - }, - } - }, - }; - - Filter.Search.Current.ValueChanged += text => - { - if (text != string.Empty) - { - Header.Tabs.Current.Value = DirectTab.Search; - - if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked) - Filter.Tabs.Current.Value = DirectSortCriteria.Relevance; - } - else - { - Header.Tabs.Current.Value = DirectTab.NewestMaps; - - if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance) - Filter.Tabs.Current.Value = DirectSortCriteria.Ranked; - } - }; - ((FilterControl)Filter).Ruleset.ValueChanged += ruleset => Scheduler.AddOnce(updateSearch); - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += rankStatus => Scheduler.AddOnce(updateSearch); - - Header.Tabs.Current.ValueChanged += tab => - { - if (tab != DirectTab.Search) - { - currentQuery.Value = string.Empty; - Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value; - Scheduler.AddOnce(updateSearch); - } - }; - - currentQuery.ValueChanged += v => - { - queryChangedDebounce?.Cancel(); - - if (string.IsNullOrEmpty(v)) - Scheduler.AddOnce(updateSearch); - else - { - BeatmapSets = null; - ResultAmounts = null; - - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); - } - }; - - currentQuery.BindTo(Filter.Search.Current); - - Filter.Tabs.Current.ValueChanged += sortCriteria => - { - if (Header.Tabs.Current.Value != DirectTab.Search && sortCriteria != (DirectSortCriteria)Header.Tabs.Current.Value) - Header.Tabs.Current.Value = DirectTab.Search; - - Scheduler.AddOnce(updateSearch); - }; - - updateResultCounts(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, BeatmapManager beatmaps) - { - this.api = api; - this.rulesets = rulesets; - this.beatmaps = beatmaps; - - resultCountsContainer.Colour = colours.Yellow; - - beatmaps.ItemAdded += setAdded; - } - - private void setAdded(BeatmapSetInfo set) => Schedule(() => - { - // if a new map was imported, we should remove it from search results (download completed etc.) - panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire(); - BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID); - }); - - private void updateResultCounts() - { - resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint); - if (ResultAmounts == null) return; - - resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " + - pluralize("Song", ResultAmounts.Songs) + ", " + - pluralize("Tag", ResultAmounts.Tags); - } - - private string pluralize(string prefix, int value) - { - return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s"); - } - - private void recreatePanels(PanelDisplayStyle displayStyle) - { - if (panels != null) - { - panels.FadeOut(200); - panels.Expire(); - panels = null; - - if (playing != null) - { - playing.PreviewPlaying.Value = false; - playing = null; - } - } - - if (BeatmapSets == null) return; - - var newPanels = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(panel_padding), - Margin = new MarginPadding { Top = 10 }, - ChildrenEnumerable = BeatmapSets.Select(b => - { - switch (displayStyle) - { - case PanelDisplayStyle.Grid: - return new DirectGridPanel(b) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; - default: - return new DirectListPanel(b); - } - }) - }; - - LoadComponentAsync(newPanels, p => - { - if (panels != null) ScrollFlow.Remove(panels); - ScrollFlow.Add(panels = newPanels); - - foreach (DirectPanel panel in p.Children) - panel.PreviewPlaying.ValueChanged += newValue => - { - if (newValue) - { - if (playing != null && playing != panel) - playing.PreviewPlaying.Value = false; - playing = panel; - } - }; - }); - } - - private SearchBeatmapSetsRequest getSetsRequest; - - private readonly Bindable currentQuery = new Bindable(); - - private ScheduledDelegate queryChangedDebounce; - - private void updateSearch() - { - queryChangedDebounce?.Cancel(); - - if (!IsLoaded) return; - - BeatmapSets = null; - ResultAmounts = null; - - getSetsRequest?.Cancel(); - - if (api == null) return; - - if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; - - getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, - ((FilterControl)Filter).Ruleset.Value, - Filter.DisplayStyleControl.Dropdown.Current.Value, - Filter.Tabs.Current.Value); //todo: sort direction (?) - - getSetsRequest.Success += response => - { - Task.Run(() => - { - var onlineIds = response.Select(r => r.OnlineBeatmapSetID).ToList(); - var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID) && !s.DeletePending).Select(r => r.OnlineBeatmapSetID).ToList(); - var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList(); - - // may not need scheduling; loads async internally. - Schedule(() => - { - BeatmapSets = sets; - recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); - }); - }); - }; - - api.Queue(getSetsRequest); - } - - protected override void PopOut() - { - base.PopOut(); - - if (playing != null) - playing.PreviewPlaying.Value = false; - } - - private int distinctCount(List list) => list.Distinct().ToArray().Length; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - beatmaps.ItemAdded -= setAdded; - } - - public class ResultCounts - { - public readonly int Artists; - public readonly int Songs; - public readonly int Tags; - - public ResultCounts(int artists, int songs, int tags) - { - Artists = artists; - Songs = songs; - Tags = tags; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Direct; +using osu.Game.Overlays.SearchableList; +using osu.Game.Rulesets; +using OpenTK.Graphics; + +namespace osu.Game.Overlays +{ + public class DirectOverlay : SearchableListOverlay + { + private const float panel_padding = 10f; + + private APIAccess api; + private RulesetStore rulesets; + private BeatmapManager beatmaps; + + private readonly FillFlowContainer resultCountsContainer; + private readonly OsuSpriteText resultCountsText; + private FillFlowContainer panels; + private DirectPanel playing; + + protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74"); + protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71"); + protected override Color4 TrianglesColourDark => OsuColour.FromHex(@"3f5265"); + + protected override SearchableListHeader CreateHeader() => new Header(); + protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); + + private IEnumerable beatmapSets; + + public IEnumerable BeatmapSets + { + get { return beatmapSets; } + set + { + if (beatmapSets?.Equals(value) ?? false) return; + + beatmapSets = value?.ToList(); + + if (beatmapSets == null) return; + + var artists = new List(); + var songs = new List(); + var tags = new List(); + foreach (var s in beatmapSets) + { + artists.Add(s.Metadata.Artist); + songs.Add(s.Metadata.Title); + tags.AddRange(s.Metadata.Tags.Split(' ')); + } + + ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags)); + } + } + + private ResultCounts resultAmounts; + + public ResultCounts ResultAmounts + { + get { return resultAmounts; } + set + { + if (value == ResultAmounts) return; + resultAmounts = value; + + updateResultCounts(); + } + } + + public DirectOverlay() + { + RelativeSizeAxes = Axes.Both; + + // osu!direct colours are not part of the standard palette + + FirstWaveColour = OsuColour.FromHex(@"19b0e2"); + SecondWaveColour = OsuColour.FromHex(@"2280a2"); + ThirdWaveColour = OsuColour.FromHex(@"005774"); + FourthWaveColour = OsuColour.FromHex(@"003a4e"); + + ScrollFlow.Children = new Drawable[] + { + resultCountsContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Found ", + TextSize = 15, + }, + resultCountsText = new OsuSpriteText + { + TextSize = 15, + Font = @"Exo2.0-Bold", + }, + } + }, + }; + + Filter.Search.Current.ValueChanged += text => + { + if (text != string.Empty) + { + Header.Tabs.Current.Value = DirectTab.Search; + + if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked) + Filter.Tabs.Current.Value = DirectSortCriteria.Relevance; + } + else + { + Header.Tabs.Current.Value = DirectTab.NewestMaps; + + if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance) + Filter.Tabs.Current.Value = DirectSortCriteria.Ranked; + } + }; + ((FilterControl)Filter).Ruleset.ValueChanged += ruleset => Scheduler.AddOnce(updateSearch); + Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += rankStatus => Scheduler.AddOnce(updateSearch); + + Header.Tabs.Current.ValueChanged += tab => + { + if (tab != DirectTab.Search) + { + currentQuery.Value = string.Empty; + Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value; + Scheduler.AddOnce(updateSearch); + } + }; + + currentQuery.ValueChanged += v => + { + queryChangedDebounce?.Cancel(); + + if (string.IsNullOrEmpty(v)) + Scheduler.AddOnce(updateSearch); + else + { + BeatmapSets = null; + ResultAmounts = null; + + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); + } + }; + + currentQuery.BindTo(Filter.Search.Current); + + Filter.Tabs.Current.ValueChanged += sortCriteria => + { + if (Header.Tabs.Current.Value != DirectTab.Search && sortCriteria != (DirectSortCriteria)Header.Tabs.Current.Value) + Header.Tabs.Current.Value = DirectTab.Search; + + Scheduler.AddOnce(updateSearch); + }; + + updateResultCounts(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, BeatmapManager beatmaps) + { + this.api = api; + this.rulesets = rulesets; + this.beatmaps = beatmaps; + + resultCountsContainer.Colour = colours.Yellow; + + beatmaps.ItemAdded += setAdded; + } + + private void setAdded(BeatmapSetInfo set) => Schedule(() => + { + // if a new map was imported, we should remove it from search results (download completed etc.) + panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire(); + BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID); + }); + + private void updateResultCounts() + { + resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint); + if (ResultAmounts == null) return; + + resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " + + pluralize("Song", ResultAmounts.Songs) + ", " + + pluralize("Tag", ResultAmounts.Tags); + } + + private string pluralize(string prefix, int value) + { + return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s"); + } + + private void recreatePanels(PanelDisplayStyle displayStyle) + { + if (panels != null) + { + panels.FadeOut(200); + panels.Expire(); + panels = null; + + if (playing != null) + { + playing.PreviewPlaying.Value = false; + playing = null; + } + } + + if (BeatmapSets == null) return; + + var newPanels = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(panel_padding), + Margin = new MarginPadding { Top = 10 }, + ChildrenEnumerable = BeatmapSets.Select(b => + { + switch (displayStyle) + { + case PanelDisplayStyle.Grid: + return new DirectGridPanel(b) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; + default: + return new DirectListPanel(b); + } + }) + }; + + LoadComponentAsync(newPanels, p => + { + if (panels != null) ScrollFlow.Remove(panels); + ScrollFlow.Add(panels = newPanels); + + foreach (DirectPanel panel in p.Children) + panel.PreviewPlaying.ValueChanged += newValue => + { + if (newValue) + { + if (playing != null && playing != panel) + playing.PreviewPlaying.Value = false; + playing = panel; + } + }; + }); + } + + private SearchBeatmapSetsRequest getSetsRequest; + + private readonly Bindable currentQuery = new Bindable(); + + private ScheduledDelegate queryChangedDebounce; + + private void updateSearch() + { + queryChangedDebounce?.Cancel(); + + if (!IsLoaded) return; + + BeatmapSets = null; + ResultAmounts = null; + + getSetsRequest?.Cancel(); + + if (api == null) return; + + if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; + + getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, + ((FilterControl)Filter).Ruleset.Value, + Filter.DisplayStyleControl.Dropdown.Current.Value, + Filter.Tabs.Current.Value); //todo: sort direction (?) + + getSetsRequest.Success += response => + { + Task.Run(() => + { + var onlineIds = response.Select(r => r.OnlineBeatmapSetID).ToList(); + var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID) && !s.DeletePending).Select(r => r.OnlineBeatmapSetID).ToList(); + var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList(); + + // may not need scheduling; loads async internally. + Schedule(() => + { + BeatmapSets = sets; + recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); + }); + }); + }; + + api.Queue(getSetsRequest); + } + + protected override void PopOut() + { + base.PopOut(); + + if (playing != null) + playing.PreviewPlaying.Value = false; + } + + private int distinctCount(List list) => list.Distinct().ToArray().Length; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmaps != null) + beatmaps.ItemAdded -= setAdded; + } + + public class ResultCounts + { + public readonly int Artists; + public readonly int Songs; + public readonly int Tags; + + public ResultCounts(int artists, int songs, int tags) + { + Artists = artists; + Songs = songs; + Tags = tags; + } + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index a4c1621266..b939483cd8 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; -using osu.Game.Input.Bindings; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Overlays.KeyBinding -{ - public class GlobalKeyBindingsSection : SettingsSection - { - public override FontAwesome Icon => FontAwesome.fa_osu_hot; - public override string Header => "Global"; - - public GlobalKeyBindingsSection(GlobalActionContainer manager) - { - Add(new DefaultBindingsSubsection(manager)); - Add(new InGameKeyBindingsSubsection(manager)); - } - - - private class DefaultBindingsSubsection : KeyBindingsSubsection - { - protected override string Header => string.Empty; - - public DefaultBindingsSubsection(GlobalActionContainer manager) - : base(null) - { - Defaults = manager.GlobalKeyBindings; - } - } - - private class InGameKeyBindingsSubsection : KeyBindingsSubsection - { - protected override string Header => "In Game"; - - public InGameKeyBindingsSubsection(GlobalActionContainer manager) : base(null) - { - Defaults = manager.InGameKeyBindings; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Overlays.KeyBinding +{ + public class GlobalKeyBindingsSection : SettingsSection + { + public override FontAwesome Icon => FontAwesome.fa_osu_hot; + public override string Header => "Global"; + + public GlobalKeyBindingsSection(GlobalActionContainer manager) + { + Add(new DefaultBindingsSubsection(manager)); + Add(new InGameKeyBindingsSubsection(manager)); + } + + + private class DefaultBindingsSubsection : KeyBindingsSubsection + { + protected override string Header => string.Empty; + + public DefaultBindingsSubsection(GlobalActionContainer manager) + : base(null) + { + Defaults = manager.GlobalKeyBindings; + } + } + + private class InGameKeyBindingsSubsection : KeyBindingsSubsection + { + protected override string Header => "In Game"; + + public InGameKeyBindingsSubsection(GlobalActionContainer manager) : base(null) + { + Defaults = manager.InGameKeyBindings; + } + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 71c346d404..e80a469f91 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -1,374 +1,374 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Input; -using OpenTK.Graphics; -using OpenTK.Input; - -namespace osu.Game.Overlays.KeyBinding -{ - public class KeyBindingRow : Container, IFilterable - { - private readonly object action; - private readonly IEnumerable bindings; - - private const float transition_time = 150; - - private const float height = 20; - - private const float padding = 5; - - private bool matchingFilter; - - public bool MatchingFilter - { - get { return matchingFilter; } - set - { - matchingFilter = value; - this.FadeTo(!matchingFilter ? 0 : 1); - } - } - - private OsuSpriteText text; - private OsuSpriteText pressAKey; - - private FillFlowContainer buttons; - - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text); - - public KeyBindingRow(object action, IEnumerable bindings) - { - this.action = action; - this.bindings = bindings; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Masking = true; - CornerRadius = padding; - } - - private KeyBindingStore store; - - [BackgroundDependencyLoader] - private void load(OsuColour colours, KeyBindingStore store) - { - this.store = store; - - EdgeEffect = new EdgeEffectParameters - { - Radius = 2, - Colour = colours.YellowDark.Opacity(0), - Type = EdgeEffectType.Shadow, - Hollow = true, - }; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - text = new OsuSpriteText - { - Text = action.GetDescription(), - Margin = new MarginPadding(padding), - }, - buttons = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }, - pressAKey = new OsuSpriteText - { - Text = "Press a key to change binding, DEL to delete, ESC to cancel.", - Y = height, - Margin = new MarginPadding(padding), - Alpha = 0, - Colour = colours.YellowDark - } - }; - - foreach (var b in bindings) - buttons.Add(new KeyButton(b)); - } - - public void RestoreDefaults() - { - int i = 0; - foreach (var d in Defaults) - { - var button = buttons[i++]; - button.UpdateKeyCombination(d); - store.Update(button.KeyBinding); - } - } - - protected override bool OnHover(InputState state) - { - FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); - - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); - - base.OnHoverLost(state); - } - - public override bool AcceptsFocus => bindTarget == null; - - private KeyButton bindTarget; - - public bool AllowMainMouseButtons; - - public IEnumerable Defaults; - - private bool isModifier(Key k) => k < Key.F1; - - protected override bool OnClick(InputState state) => true; - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - if (!HasFocus || !bindTarget.IsHovered) - return base.OnMouseDown(state, args); - - if (!AllowMainMouseButtons) - { - switch (args.Button) - { - case MouseButton.Left: - case MouseButton.Right: - return true; - } - } - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); - return true; - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - // don't do anything until the last button is released. - if (!HasFocus || state.Mouse.Buttons.Any()) - return base.OnMouseUp(state, args); - - if (bindTarget.IsHovered) - finalise(); - else - updateBindTarget(); - return true; - } - - protected override bool OnWheel(InputState state) - { - if (HasFocus) - { - if (bindTarget.IsHovered) - { - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); - finalise(); - return true; - } - } - - return base.OnWheel(state); - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (!HasFocus) - return false; - - switch (args.Key) - { - case Key.Escape: - finalise(); - return true; - case Key.Delete: - bindTarget.UpdateKeyCombination(InputKey.None); - finalise(); - return true; - } - - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); - if (!isModifier(args.Key)) finalise(); - - return true; - } - - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) - { - if (!HasFocus) return base.OnKeyUp(state, args); - - finalise(); - return true; - } - - private void finalise() - { - if (bindTarget != null) - { - store.Update(bindTarget.KeyBinding); - - bindTarget.IsBinding = false; - Schedule(() => - { - // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) - bindTarget = null; - }); - } - - if (HasFocus) - GetContainingInputManager().ChangeFocus(null); - - pressAKey.FadeOut(300, Easing.OutQuint); - pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight }; - } - - protected override void OnFocus(InputState state) - { - AutoSizeDuration = 500; - AutoSizeEasing = Easing.OutQuint; - - pressAKey.FadeIn(300, Easing.OutQuint); - pressAKey.Padding = new MarginPadding(); - - updateBindTarget(); - base.OnFocus(state); - } - - protected override void OnFocusLost(InputState state) - { - finalise(); - base.OnFocusLost(state); - } - - private void updateBindTarget() - { - if (bindTarget != null) bindTarget.IsBinding = false; - bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); - if (bindTarget != null) bindTarget.IsBinding = true; - } - - private class KeyButton : Container - { - public readonly Framework.Input.Bindings.KeyBinding KeyBinding; - - private readonly Box box; - public readonly OsuSpriteText Text; - - private Color4 hoverColour; - - private bool isBinding; - - public bool IsBinding - { - get { return isBinding; } - set - { - if (value == isBinding) return; - isBinding = value; - - updateHoverState(); - } - } - - public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) - { - KeyBinding = keyBinding; - - Margin = new MarginPadding(padding); - - // todo: use this in a meaningful way - // var isDefault = keyBinding.Action is Enum; - - Masking = true; - CornerRadius = padding; - - Height = height; - AutoSizeAxes = Axes.X; - - Children = new Drawable[] - { - new Container - { - AlwaysPresent = true, - Width = 80, - Height = height, - }, - box = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - Text = new OsuSpriteText - { - Font = "Venera", - TextSize = 10, - Margin = new MarginPadding(5), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = keyBinding.KeyCombination.ReadableString(), - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.YellowDark; - } - - protected override bool OnHover(InputState state) - { - updateHoverState(); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - updateHoverState(); - base.OnHoverLost(state); - } - - private void updateHoverState() - { - if (isBinding) - { - box.FadeColour(Color4.White, transition_time, Easing.OutQuint); - Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint); - } - else - { - box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint); - Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint); - } - } - - public void UpdateKeyCombination(KeyCombination newCombination) - { - KeyBinding.KeyCombination = newCombination; - Text.Text = KeyBinding.KeyCombination.ReadableString(); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Input; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace osu.Game.Overlays.KeyBinding +{ + public class KeyBindingRow : Container, IFilterable + { + private readonly object action; + private readonly IEnumerable bindings; + + private const float transition_time = 150; + + private const float height = 20; + + private const float padding = 5; + + private bool matchingFilter; + + public bool MatchingFilter + { + get { return matchingFilter; } + set + { + matchingFilter = value; + this.FadeTo(!matchingFilter ? 0 : 1); + } + } + + private OsuSpriteText text; + private OsuSpriteText pressAKey; + + private FillFlowContainer buttons; + + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text); + + public KeyBindingRow(object action, IEnumerable bindings) + { + this.action = action; + this.bindings = bindings; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = padding; + } + + private KeyBindingStore store; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, KeyBindingStore store) + { + this.store = store; + + EdgeEffect = new EdgeEffectParameters + { + Radius = 2, + Colour = colours.YellowDark.Opacity(0), + Type = EdgeEffectType.Shadow, + Hollow = true, + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + text = new OsuSpriteText + { + Text = action.GetDescription(), + Margin = new MarginPadding(padding), + }, + buttons = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }, + pressAKey = new OsuSpriteText + { + Text = "Press a key to change binding, DEL to delete, ESC to cancel.", + Y = height, + Margin = new MarginPadding(padding), + Alpha = 0, + Colour = colours.YellowDark + } + }; + + foreach (var b in bindings) + buttons.Add(new KeyButton(b)); + } + + public void RestoreDefaults() + { + int i = 0; + foreach (var d in Defaults) + { + var button = buttons[i++]; + button.UpdateKeyCombination(d); + store.Update(button.KeyBinding); + } + } + + protected override bool OnHover(InputState state) + { + FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); + + base.OnHoverLost(state); + } + + public override bool AcceptsFocus => bindTarget == null; + + private KeyButton bindTarget; + + public bool AllowMainMouseButtons; + + public IEnumerable Defaults; + + private bool isModifier(Key k) => k < Key.F1; + + protected override bool OnClick(InputState state) => true; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + if (!HasFocus || !bindTarget.IsHovered) + return base.OnMouseDown(state, args); + + if (!AllowMainMouseButtons) + { + switch (args.Button) + { + case MouseButton.Left: + case MouseButton.Right: + return true; + } + } + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + return true; + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + // don't do anything until the last button is released. + if (!HasFocus || state.Mouse.Buttons.Any()) + return base.OnMouseUp(state, args); + + if (bindTarget.IsHovered) + finalise(); + else + updateBindTarget(); + return true; + } + + protected override bool OnWheel(InputState state) + { + if (HasFocus) + { + if (bindTarget.IsHovered) + { + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + finalise(); + return true; + } + } + + return base.OnWheel(state); + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!HasFocus) + return false; + + switch (args.Key) + { + case Key.Escape: + finalise(); + return true; + case Key.Delete: + bindTarget.UpdateKeyCombination(InputKey.None); + finalise(); + return true; + } + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + if (!isModifier(args.Key)) finalise(); + + return true; + } + + protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + { + if (!HasFocus) return base.OnKeyUp(state, args); + + finalise(); + return true; + } + + private void finalise() + { + if (bindTarget != null) + { + store.Update(bindTarget.KeyBinding); + + bindTarget.IsBinding = false; + Schedule(() => + { + // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) + bindTarget = null; + }); + } + + if (HasFocus) + GetContainingInputManager().ChangeFocus(null); + + pressAKey.FadeOut(300, Easing.OutQuint); + pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight }; + } + + protected override void OnFocus(InputState state) + { + AutoSizeDuration = 500; + AutoSizeEasing = Easing.OutQuint; + + pressAKey.FadeIn(300, Easing.OutQuint); + pressAKey.Padding = new MarginPadding(); + + updateBindTarget(); + base.OnFocus(state); + } + + protected override void OnFocusLost(InputState state) + { + finalise(); + base.OnFocusLost(state); + } + + private void updateBindTarget() + { + if (bindTarget != null) bindTarget.IsBinding = false; + bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); + if (bindTarget != null) bindTarget.IsBinding = true; + } + + private class KeyButton : Container + { + public readonly Framework.Input.Bindings.KeyBinding KeyBinding; + + private readonly Box box; + public readonly OsuSpriteText Text; + + private Color4 hoverColour; + + private bool isBinding; + + public bool IsBinding + { + get { return isBinding; } + set + { + if (value == isBinding) return; + isBinding = value; + + updateHoverState(); + } + } + + public KeyButton(Framework.Input.Bindings.KeyBinding keyBinding) + { + KeyBinding = keyBinding; + + Margin = new MarginPadding(padding); + + // todo: use this in a meaningful way + // var isDefault = keyBinding.Action is Enum; + + Masking = true; + CornerRadius = padding; + + Height = height; + AutoSizeAxes = Axes.X; + + Children = new Drawable[] + { + new Container + { + AlwaysPresent = true, + Width = 80, + Height = height, + }, + box = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + Text = new OsuSpriteText + { + Font = "Venera", + TextSize = 10, + Margin = new MarginPadding(5), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = keyBinding.KeyCombination.ReadableString(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.YellowDark; + } + + protected override bool OnHover(InputState state) + { + updateHoverState(); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + updateHoverState(); + base.OnHoverLost(state); + } + + private void updateHoverState() + { + if (isBinding) + { + box.FadeColour(Color4.White, transition_time, Easing.OutQuint); + Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint); + } + else + { + box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint); + Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint); + } + } + + public void UpdateKeyCombination(KeyCombination newCombination) + { + KeyBinding.KeyCombination = newCombination; + Text.Text = KeyBinding.KeyCombination.ReadableString(); + } + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 6cbc926fc3..895dda872a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -1,75 +1,75 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Input; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; -using OpenTK; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.KeyBinding -{ - public abstract class KeyBindingsSubsection : SettingsSubsection - { - protected IEnumerable Defaults; - - protected RulesetInfo Ruleset; - - private readonly int? variant; - - protected KeyBindingsSubsection(int? variant) - { - this.variant = variant; - - FlowContent.Spacing = new Vector2(0, 1); - FlowContent.Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; - } - - [BackgroundDependencyLoader] - private void load(KeyBindingStore store) - { - var bindings = store.Query(Ruleset?.ID, variant); - - foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) - { - int intKey = (int)defaultGroup.Key; - - // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals(intKey))) - { - AllowMainMouseButtons = Ruleset != null, - Defaults = defaultGroup.Select(d => d.KeyCombination) - }); - } - - Add(new ResetButton - { - Action = () => Children.OfType().ForEach(k => k.RestoreDefaults()) - }); - } - } - - public class ResetButton : TriangleButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Text = "Reset"; - RelativeSizeAxes = Axes.X; - Margin = new MarginPadding { Top = 5 }; - Height = 20; - - Content.CornerRadius = 5; - - BackgroundColour = colours.PinkDark; - Triangles.ColourDark = colours.PinkDarker; - Triangles.ColourLight = colours.Pink; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; +using OpenTK; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.KeyBinding +{ + public abstract class KeyBindingsSubsection : SettingsSubsection + { + protected IEnumerable Defaults; + + protected RulesetInfo Ruleset; + + private readonly int? variant; + + protected KeyBindingsSubsection(int? variant) + { + this.variant = variant; + + FlowContent.Spacing = new Vector2(0, 1); + FlowContent.Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; + } + + [BackgroundDependencyLoader] + private void load(KeyBindingStore store) + { + var bindings = store.Query(Ruleset?.ID, variant); + + foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) + { + int intKey = (int)defaultGroup.Key; + + // one row per valid action. + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals(intKey))) + { + AllowMainMouseButtons = Ruleset != null, + Defaults = defaultGroup.Select(d => d.KeyCombination) + }); + } + + Add(new ResetButton + { + Action = () => Children.OfType().ForEach(k => k.RestoreDefaults()) + }); + } + } + + public class ResetButton : TriangleButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Text = "Reset"; + RelativeSizeAxes = Axes.X; + Margin = new MarginPadding { Top = 5 }; + Height = 20; + + Content.CornerRadius = 5; + + BackgroundColour = colours.PinkDark; + Triangles.ColourDark = colours.PinkDarker; + Triangles.ColourLight = colours.Pink; + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs index c2a4ea8a12..c4405216e3 100644 --- a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; - -namespace osu.Game.Overlays.KeyBinding -{ - public class RulesetBindingsSection : SettingsSection - { - public override FontAwesome Icon => FontAwesome.fa_osu_hot; - public override string Header => ruleset.Name; - - private readonly RulesetInfo ruleset; - - public RulesetBindingsSection(RulesetInfo ruleset) - { - this.ruleset = ruleset; - - var r = ruleset.CreateInstance(); - - foreach (var variant in r.AvailableVariants) - Add(new VariantBindingsSubsection(ruleset, variant)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.KeyBinding +{ + public class RulesetBindingsSection : SettingsSection + { + public override FontAwesome Icon => FontAwesome.fa_osu_hot; + public override string Header => ruleset.Name; + + private readonly RulesetInfo ruleset; + + public RulesetBindingsSection(RulesetInfo ruleset) + { + this.ruleset = ruleset; + + var r = ruleset.CreateInstance(); + + foreach (var variant in r.AvailableVariants) + Add(new VariantBindingsSubsection(ruleset, variant)); + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs index 85a587b003..4f33695e5d 100644 --- a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets; - -namespace osu.Game.Overlays.KeyBinding -{ - public class VariantBindingsSubsection : KeyBindingsSubsection - { - protected override string Header => variantName; - private readonly string variantName; - - public VariantBindingsSubsection(RulesetInfo ruleset, int variant) - : base(variant) - { - Ruleset = ruleset; - - var rulesetInstance = ruleset.CreateInstance(); - - variantName = rulesetInstance.GetVariantName(variant); - Defaults = rulesetInstance.GetDefaultKeyBindings(variant); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.KeyBinding +{ + public class VariantBindingsSubsection : KeyBindingsSubsection + { + protected override string Header => variantName; + private readonly string variantName; + + public VariantBindingsSubsection(RulesetInfo ruleset, int variant) + : base(variant) + { + Ruleset = ruleset; + + var rulesetInstance = ruleset.CreateInstance(); + + variantName = rulesetInstance.GetVariantName(variant); + Defaults = rulesetInstance.GetDefaultKeyBindings(variant); + } + } +} diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs index b311ee68c0..06432cfcea 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Input.Bindings; -using osu.Game.Overlays.KeyBinding; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets; - -namespace osu.Game.Overlays -{ - public class KeyBindingOverlay : SettingsOverlay - { - protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(RulesetStore rulesets, GlobalActionContainer global) - { - AddSection(new GlobalKeyBindingsSection(global)); - - foreach (var ruleset in rulesets.AvailableRulesets) - AddSection(new RulesetBindingsSection(ruleset)); - } - - public KeyBindingOverlay() - : base(false) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.KeyBinding; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays +{ + public class KeyBindingOverlay : SettingsOverlay + { + protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(RulesetStore rulesets, GlobalActionContainer global) + { + AddSection(new GlobalKeyBindingsSection(global)); + + foreach (var ruleset in rulesets.AvailableRulesets) + AddSection(new RulesetBindingsSection(ruleset)); + } + + public KeyBindingOverlay() + : base(false) + { + } + } +} diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index cc784d954d..731c5ee973 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -1,92 +1,92 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.General; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; - -namespace osu.Game.Overlays -{ - public class LoginOverlay : OsuFocusedOverlayContainer - { - private LoginSettings settingsSection; - - private const float transition_time = 400; - - public LoginOverlay() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Children = new Drawable[] - { - new OsuContextMenuContainer - { - Width = 360, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Masking = true, - AutoSizeDuration = transition_time, - AutoSizeEasing = Easing.OutQuint, - Children = new Drawable[] - { - settingsSection = new LoginSettings - { - Padding = new MarginPadding(10), - RequestHide = Hide, - }, - new Box - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Height = 3, - Colour = colours.Yellow, - Alpha = 1, - }, - } - } - } - } - }; - } - - protected override void PopIn() - { - base.PopIn(); - - settingsSection.Bounding = true; - this.FadeIn(transition_time, Easing.OutQuint); - - GetContainingInputManager().ChangeFocus(settingsSection); - } - - protected override void PopOut() - { - base.PopOut(); - - settingsSection.Bounding = false; - this.FadeOut(transition_time); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.General; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; + +namespace osu.Game.Overlays +{ + public class LoginOverlay : OsuFocusedOverlayContainer + { + private LoginSettings settingsSection; + + private const float transition_time = 400; + + public LoginOverlay() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new OsuContextMenuContainer + { + Width = 360, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + AutoSizeDuration = transition_time, + AutoSizeEasing = Easing.OutQuint, + Children = new Drawable[] + { + settingsSection = new LoginSettings + { + Padding = new MarginPadding(10), + RequestHide = Hide, + }, + new Box + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 3, + Colour = colours.Yellow, + Alpha = 1, + }, + } + } + } + } + }; + } + + protected override void PopIn() + { + base.PopIn(); + + settingsSection.Bounding = true; + this.FadeIn(transition_time, Easing.OutQuint); + + GetContainingInputManager().ChangeFocus(settingsSection); + } + + protected override void PopOut() + { + base.PopOut(); + + settingsSection.Bounding = false; + this.FadeOut(transition_time); + } + } +} diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs index be822f9b06..09b5be6a85 100644 --- a/osu.Game/Overlays/MainSettings.cs +++ b/osu.Game/Overlays/MainSettings.cs @@ -1,151 +1,151 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Settings; -using osu.Game.Overlays.Settings.Sections; -using osu.Game.Screens.Ranking; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays -{ - public class MainSettings : SettingsOverlay - { - private readonly KeyBindingOverlay keyBindingOverlay; - private BackButton backButton; - - protected override IEnumerable CreateSections() => new SettingsSection[] - { - new GeneralSection(), - new GraphicsSection(), - new GameplaySection(), - new AudioSection(), - new SkinSection(), - new InputSection(keyBindingOverlay), - new OnlineSection(), - new MaintenanceSection(), - new DebugSection(), - }; - - protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); - protected override Drawable CreateFooter() => new SettingsFooter(); - - public MainSettings() - : base(true) - { - keyBindingOverlay = new KeyBindingOverlay - { - Depth = 1, - Anchor = Anchor.TopRight, - }; - keyBindingOverlay.StateChanged += keyBindingOverlay_StateChanged; - } - - public override bool AcceptsFocus => keyBindingOverlay.State != Visibility.Visible; - - private const float hidden_width = 120; - - private void keyBindingOverlay_StateChanged(Visibility visibility) - { - switch (visibility) - { - case Visibility.Visible: - Background.FadeTo(0.9f, 300, Easing.OutQuint); - Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); - - SectionsContainer.FadeOut(300, Easing.OutQuint); - ContentContainer.MoveToX(hidden_width - WIDTH, 500, Easing.OutQuint); - - backButton.Delay(100).FadeIn(100); - break; - case Visibility.Hidden: - Background.FadeTo(0.6f, 500, Easing.OutQuint); - Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); - - SectionsContainer.FadeIn(500, Easing.OutQuint); - ContentContainer.MoveToX(0, 500, Easing.OutQuint); - - backButton.FadeOut(100); - break; - } - } - - protected override float ExpandedPosition => keyBindingOverlay.State == Visibility.Visible ? hidden_width - WIDTH : base.ExpandedPosition; - - [BackgroundDependencyLoader] - private void load() - { - ContentContainer.Add(keyBindingOverlay); - - ContentContainer.Add(backButton = new BackButton - { - Alpha = 0, - Width = hidden_width, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Action = () => keyBindingOverlay.Hide() - }); - } - - private class BackButton : OsuClickableContainer - { - private AspectContainer aspect; - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - aspect = new AspectContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -15, - Size = new Vector2(15), - Shadow = true, - Icon = FontAwesome.fa_chevron_left - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 15, - TextSize = 12, - Font = @"Exo2.0-Bold", - Text = @"back", - }, - } - } - }; - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - aspect.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osu.Game.Screens.Ranking; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays +{ + public class MainSettings : SettingsOverlay + { + private readonly KeyBindingOverlay keyBindingOverlay; + private BackButton backButton; + + protected override IEnumerable CreateSections() => new SettingsSection[] + { + new GeneralSection(), + new GraphicsSection(), + new GameplaySection(), + new AudioSection(), + new SkinSection(), + new InputSection(keyBindingOverlay), + new OnlineSection(), + new MaintenanceSection(), + new DebugSection(), + }; + + protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); + protected override Drawable CreateFooter() => new SettingsFooter(); + + public MainSettings() + : base(true) + { + keyBindingOverlay = new KeyBindingOverlay + { + Depth = 1, + Anchor = Anchor.TopRight, + }; + keyBindingOverlay.StateChanged += keyBindingOverlay_StateChanged; + } + + public override bool AcceptsFocus => keyBindingOverlay.State != Visibility.Visible; + + private const float hidden_width = 120; + + private void keyBindingOverlay_StateChanged(Visibility visibility) + { + switch (visibility) + { + case Visibility.Visible: + Background.FadeTo(0.9f, 300, Easing.OutQuint); + Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); + + SectionsContainer.FadeOut(300, Easing.OutQuint); + ContentContainer.MoveToX(hidden_width - WIDTH, 500, Easing.OutQuint); + + backButton.Delay(100).FadeIn(100); + break; + case Visibility.Hidden: + Background.FadeTo(0.6f, 500, Easing.OutQuint); + Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); + + SectionsContainer.FadeIn(500, Easing.OutQuint); + ContentContainer.MoveToX(0, 500, Easing.OutQuint); + + backButton.FadeOut(100); + break; + } + } + + protected override float ExpandedPosition => keyBindingOverlay.State == Visibility.Visible ? hidden_width - WIDTH : base.ExpandedPosition; + + [BackgroundDependencyLoader] + private void load() + { + ContentContainer.Add(keyBindingOverlay); + + ContentContainer.Add(backButton = new BackButton + { + Alpha = 0, + Width = hidden_width, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Action = () => keyBindingOverlay.Hide() + }); + } + + private class BackButton : OsuClickableContainer + { + private AspectContainer aspect; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + aspect = new AspectContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -15, + Size = new Vector2(15), + Shadow = true, + Icon = FontAwesome.fa_chevron_left + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 15, + TextSize = 12, + Font = @"Exo2.0-Bold", + Text = @"back", + }, + } + } + }; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + aspect.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + } + } +} diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index bd6adffdc7..97a9217b23 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -1,309 +1,309 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Sprites; -using osu.Game.Users; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Overlays.MedalSplash; -using osu.Framework.Allocation; -using osu.Framework.Audio.Sample; -using osu.Framework.Audio; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input; -using OpenTK.Input; -using System.Linq; -using osu.Framework.Graphics.Shapes; -using System; -using osu.Framework.MathUtils; - -namespace osu.Game.Overlays -{ - public class MedalOverlay : FocusedOverlayContainer - { - public const float DISC_SIZE = 400; - - private const float border_width = 5; - - private readonly Medal medal; - private readonly Box background; - private readonly Container backgroundStrip, particleContainer; - private readonly BackgroundStrip leftStrip, rightStrip; - private readonly CircularContainer disc; - private readonly Sprite innerSpin, outerSpin; - private DrawableMedal drawableMedal; - - private SampleChannel getSample; - - public MedalOverlay(Medal medal) - { - this.medal = medal; - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(60), - }, - outerSpin = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(DISC_SIZE + 500), - Alpha = 0f, - }, - backgroundStrip = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = border_width, - Alpha = 0f, - Children = new[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - Width = 0.5f, - Padding = new MarginPadding { Right = DISC_SIZE / 2 }, - Children = new[] - { - leftStrip = new BackgroundStrip(0f, 1f) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - Width = 0.5f, - Padding = new MarginPadding { Left = DISC_SIZE / 2 }, - Children = new[] - { - rightStrip = new BackgroundStrip(1f, 0f), - }, - }, - }, - }, - particleContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0f, - }, - disc = new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0f, - Masking = true, - AlwaysPresent = true, - BorderColour = Color4.White, - BorderThickness = border_width, - Size = new Vector2(DISC_SIZE), - Scale = new Vector2(0.8f), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"05262f"), - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - TriangleScale = 2, - ColourDark = OsuColour.FromHex(@"04222b"), - ColourLight = OsuColour.FromHex(@"052933"), - }, - innerSpin = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(1.05f), - Alpha = 0.25f, - }, - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, AudioManager audio) - { - getSample = audio.Sample.Get(@"MedalSplash/medal-get"); - innerSpin.Texture = outerSpin.Texture = textures.Get(@"MedalSplash/disc-spin"); - - disc.EdgeEffect = leftStrip.EdgeEffect = rightStrip.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = colours.Blue.Opacity(0.5f), - Radius = 50, - }; - - disc.Add(drawableMedal = new DrawableMedal(medal) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Show(); - } - - protected override void Update() - { - base.Update(); - - particleContainer.Add(new MedalParticle(RNG.Next(0, 359))); - } - - protected override bool OnClick(InputState state) - { - dismiss(); - return true; - } - - protected override void OnFocusLost(InputState state) - { - if (state.Keyboard.Keys.Contains(Key.Escape)) dismiss(); - } - - private const double initial_duration = 400; - private const double step_duration = 900; - - protected override void PopIn() - { - base.PopIn(); - - this.FadeIn(200); - background.FlashColour(Color4.White.Opacity(0.25f), 400); - - getSample.Play(); - - innerSpin.Spin(20000, RotationDirection.Clockwise); - outerSpin.Spin(40000, RotationDirection.Clockwise); - - using (BeginDelayedSequence(200, true)) - { - disc.FadeIn(initial_duration) - .ScaleTo(1f, initial_duration * 2, Easing.OutElastic); - - particleContainer.FadeIn(initial_duration); - outerSpin.FadeTo(0.1f, initial_duration * 2); - - using (BeginDelayedSequence(initial_duration + 200, true)) - { - backgroundStrip.FadeIn(step_duration); - leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); - rightStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); - - this.Animate().Schedule(() => - { - if (drawableMedal.State != DisplayState.Full) - drawableMedal.State = DisplayState.Icon; - }) - .Delay(step_duration).Schedule(() => - { - if (drawableMedal.State != DisplayState.Full) - drawableMedal.State = DisplayState.MedalUnlocked; - }) - .Delay(step_duration).Schedule(() => - { - if (drawableMedal.State != DisplayState.Full) - drawableMedal.State = DisplayState.Full; - }); - } - } - } - - protected override void PopOut() - { - base.PopOut(); - this.FadeOut(200); - } - - private void dismiss() - { - if (drawableMedal.State != DisplayState.Full) - { - // if we haven't yet, play out the animation fully - drawableMedal.State = DisplayState.Full; - FinishTransforms(true); - return; - } - - Hide(); - Expire(); - } - - private class BackgroundStrip : Container - { - public BackgroundStrip(float start, float end) - { - RelativeSizeAxes = Axes.Both; - Width = 0f; - Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(start), Color4.White.Opacity(end)); - Masking = true; - - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - } - }; - } - } - - private class MedalParticle : CircularContainer - { - private readonly float direction; - - private Vector2 positionForOffset(float offset) => new Vector2((float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction))); - - public MedalParticle(float direction) - { - this.direction = direction; - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Position = positionForOffset(DISC_SIZE / 2); - Masking = true; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = colours.Blue.Opacity(0.5f), - Radius = 5, - }; - - this.MoveTo(positionForOffset(DISC_SIZE / 2 + 200), 500); - this.FadeOut(500); - Expire(); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; +using osu.Game.Users; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Overlays.MedalSplash; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Audio; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using OpenTK.Input; +using System.Linq; +using osu.Framework.Graphics.Shapes; +using System; +using osu.Framework.MathUtils; + +namespace osu.Game.Overlays +{ + public class MedalOverlay : FocusedOverlayContainer + { + public const float DISC_SIZE = 400; + + private const float border_width = 5; + + private readonly Medal medal; + private readonly Box background; + private readonly Container backgroundStrip, particleContainer; + private readonly BackgroundStrip leftStrip, rightStrip; + private readonly CircularContainer disc; + private readonly Sprite innerSpin, outerSpin; + private DrawableMedal drawableMedal; + + private SampleChannel getSample; + + public MedalOverlay(Medal medal) + { + this.medal = medal; + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(60), + }, + outerSpin = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(DISC_SIZE + 500), + Alpha = 0f, + }, + backgroundStrip = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = border_width, + Alpha = 0f, + Children = new[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Width = 0.5f, + Padding = new MarginPadding { Right = DISC_SIZE / 2 }, + Children = new[] + { + leftStrip = new BackgroundStrip(0f, 1f) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Width = 0.5f, + Padding = new MarginPadding { Left = DISC_SIZE / 2 }, + Children = new[] + { + rightStrip = new BackgroundStrip(1f, 0f), + }, + }, + }, + }, + particleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0f, + }, + disc = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0f, + Masking = true, + AlwaysPresent = true, + BorderColour = Color4.White, + BorderThickness = border_width, + Size = new Vector2(DISC_SIZE), + Scale = new Vector2(0.8f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"05262f"), + }, + new Triangles + { + RelativeSizeAxes = Axes.Both, + TriangleScale = 2, + ColourDark = OsuColour.FromHex(@"04222b"), + ColourLight = OsuColour.FromHex(@"052933"), + }, + innerSpin = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1.05f), + Alpha = 0.25f, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures, AudioManager audio) + { + getSample = audio.Sample.Get(@"MedalSplash/medal-get"); + innerSpin.Texture = outerSpin.Texture = textures.Get(@"MedalSplash/disc-spin"); + + disc.EdgeEffect = leftStrip.EdgeEffect = rightStrip.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colours.Blue.Opacity(0.5f), + Radius = 50, + }; + + disc.Add(drawableMedal = new DrawableMedal(medal) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Show(); + } + + protected override void Update() + { + base.Update(); + + particleContainer.Add(new MedalParticle(RNG.Next(0, 359))); + } + + protected override bool OnClick(InputState state) + { + dismiss(); + return true; + } + + protected override void OnFocusLost(InputState state) + { + if (state.Keyboard.Keys.Contains(Key.Escape)) dismiss(); + } + + private const double initial_duration = 400; + private const double step_duration = 900; + + protected override void PopIn() + { + base.PopIn(); + + this.FadeIn(200); + background.FlashColour(Color4.White.Opacity(0.25f), 400); + + getSample.Play(); + + innerSpin.Spin(20000, RotationDirection.Clockwise); + outerSpin.Spin(40000, RotationDirection.Clockwise); + + using (BeginDelayedSequence(200, true)) + { + disc.FadeIn(initial_duration) + .ScaleTo(1f, initial_duration * 2, Easing.OutElastic); + + particleContainer.FadeIn(initial_duration); + outerSpin.FadeTo(0.1f, initial_duration * 2); + + using (BeginDelayedSequence(initial_duration + 200, true)) + { + backgroundStrip.FadeIn(step_duration); + leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); + rightStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint); + + this.Animate().Schedule(() => + { + if (drawableMedal.State != DisplayState.Full) + drawableMedal.State = DisplayState.Icon; + }) + .Delay(step_duration).Schedule(() => + { + if (drawableMedal.State != DisplayState.Full) + drawableMedal.State = DisplayState.MedalUnlocked; + }) + .Delay(step_duration).Schedule(() => + { + if (drawableMedal.State != DisplayState.Full) + drawableMedal.State = DisplayState.Full; + }); + } + } + } + + protected override void PopOut() + { + base.PopOut(); + this.FadeOut(200); + } + + private void dismiss() + { + if (drawableMedal.State != DisplayState.Full) + { + // if we haven't yet, play out the animation fully + drawableMedal.State = DisplayState.Full; + FinishTransforms(true); + return; + } + + Hide(); + Expire(); + } + + private class BackgroundStrip : Container + { + public BackgroundStrip(float start, float end) + { + RelativeSizeAxes = Axes.Both; + Width = 0f; + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(start), Color4.White.Opacity(end)); + Masking = true; + + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; + } + } + + private class MedalParticle : CircularContainer + { + private readonly float direction; + + private Vector2 positionForOffset(float offset) => new Vector2((float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction))); + + public MedalParticle(float direction) + { + this.direction = direction; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Position = positionForOffset(DISC_SIZE / 2); + Masking = true; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = colours.Blue.Opacity(0.5f), + Radius = 5, + }; + + this.MoveTo(positionForOffset(DISC_SIZE / 2 + 200), 500); + this.FadeOut(500); + Expire(); + } + } + } +} diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 8edfdf9d95..ce79e70b1c 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -1,196 +1,196 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework; -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; - -namespace osu.Game.Overlays.MedalSplash -{ - public class DrawableMedal : Container, IStateful - { - private const float scale_when_unlocked = 0.76f; - private const float scale_when_full = 0.6f; - - public event Action StateChanged; - - private readonly Medal medal; - private readonly Container medalContainer; - private readonly Sprite medalSprite, medalGlow; - private readonly OsuSpriteText unlocked, name; - private readonly TextFlowContainer description; - private DisplayState state; - public DrawableMedal(Medal medal) - { - this.medal = medal; - Position = new Vector2(0f, MedalOverlay.DISC_SIZE / 2); - - FillFlowContainer infoFlow; - Children = new Drawable[] - { - medalContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Alpha = 0f, - Children = new Drawable[] - { - medalSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.81f), - }, - medalGlow = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - }, - }, - unlocked = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Medal Unlocked".ToUpper(), - TextSize = 24, - Font = @"Exo2.0-Light", - Alpha = 0f, - Scale = new Vector2(1f / scale_when_unlocked), - }, - infoFlow = new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.6f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 5f), - Children = new Drawable[] - { - name = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = medal.Name, - TextSize = 20, - Font = @"Exo2.0-Bold", - Alpha = 0f, - Scale = new Vector2(1f / scale_when_full), - }, - description = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0f, - Scale = new Vector2(1f / scale_when_full), - }, - }, - }, - }; - - description.AddText(medal.Description, s => - { - s.Anchor = Anchor.TopCentre; - s.Origin = Anchor.TopCentre; - s.TextSize = 16; - }); - - medalContainer.OnLoadComplete = d => - { - unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); - infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures) - { - medalSprite.Texture = textures.Get(medal.ImageUrl); - medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); - description.Colour = colours.BlueLight; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateState(); - } - - public DisplayState State - { - get { return state; } - set - { - if (state == value) return; - - state = value; - updateState(); - - StateChanged?.Invoke(State); - } - } - - private void updateState() - { - if (!IsLoaded) return; - - const double duration = 900; - - switch (state) - { - case DisplayState.None: - medalContainer.ScaleTo(0); - break; - case DisplayState.Icon: - medalContainer - .FadeIn(duration) - .ScaleTo(1, duration, Easing.OutElastic); - break; - case DisplayState.MedalUnlocked: - medalContainer - .FadeTo(1) - .ScaleTo(1); - - this.ScaleTo(scale_when_unlocked, duration, Easing.OutExpo); - this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); - unlocked.FadeInFromZero(duration); - break; - case DisplayState.Full: - medalContainer - .FadeTo(1) - .ScaleTo(1); - - this.ScaleTo(scale_when_full, duration, Easing.OutExpo); - this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo); - unlocked.Show(); - name.FadeInFromZero(duration + 100); - description.FadeInFromZero(duration * 2); - break; - } - } - } - - public enum DisplayState - { - None, - Icon, - MedalUnlocked, - Full, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; + +namespace osu.Game.Overlays.MedalSplash +{ + public class DrawableMedal : Container, IStateful + { + private const float scale_when_unlocked = 0.76f; + private const float scale_when_full = 0.6f; + + public event Action StateChanged; + + private readonly Medal medal; + private readonly Container medalContainer; + private readonly Sprite medalSprite, medalGlow; + private readonly OsuSpriteText unlocked, name; + private readonly TextFlowContainer description; + private DisplayState state; + public DrawableMedal(Medal medal) + { + this.medal = medal; + Position = new Vector2(0f, MedalOverlay.DISC_SIZE / 2); + + FillFlowContainer infoFlow; + Children = new Drawable[] + { + medalContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Alpha = 0f, + Children = new Drawable[] + { + medalSprite = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.81f), + }, + medalGlow = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }, + }, + unlocked = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Medal Unlocked".ToUpper(), + TextSize = 24, + Font = @"Exo2.0-Light", + Alpha = 0f, + Scale = new Vector2(1f / scale_when_unlocked), + }, + infoFlow = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.6f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = medal.Name, + TextSize = 20, + Font = @"Exo2.0-Bold", + Alpha = 0f, + Scale = new Vector2(1f / scale_when_full), + }, + description = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0f, + Scale = new Vector2(1f / scale_when_full), + }, + }, + }, + }; + + description.AddText(medal.Description, s => + { + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; + s.TextSize = 16; + }); + + medalContainer.OnLoadComplete = d => + { + unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); + infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + medalSprite.Texture = textures.Get(medal.ImageUrl); + medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); + description.Colour = colours.BlueLight; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + } + + public DisplayState State + { + get { return state; } + set + { + if (state == value) return; + + state = value; + updateState(); + + StateChanged?.Invoke(State); + } + } + + private void updateState() + { + if (!IsLoaded) return; + + const double duration = 900; + + switch (state) + { + case DisplayState.None: + medalContainer.ScaleTo(0); + break; + case DisplayState.Icon: + medalContainer + .FadeIn(duration) + .ScaleTo(1, duration, Easing.OutElastic); + break; + case DisplayState.MedalUnlocked: + medalContainer + .FadeTo(1) + .ScaleTo(1); + + this.ScaleTo(scale_when_unlocked, duration, Easing.OutExpo); + this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); + unlocked.FadeInFromZero(duration); + break; + case DisplayState.Full: + medalContainer + .FadeTo(1) + .ScaleTo(1); + + this.ScaleTo(scale_when_full, duration, Easing.OutExpo); + this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo); + unlocked.Show(); + name.FadeInFromZero(duration + 100); + description.FadeInFromZero(duration * 2); + break; + } + } + } + + public enum DisplayState + { + None, + Icon, + MedalUnlocked, + Full, + } +} diff --git a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs index 1d9fdab8d5..d7d9a90e77 100644 --- a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Input; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Overlays.Mods -{ - public class DifficultyIncreaseSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; - public override ModType ModType => ModType.DifficultyIncrease; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SelectedColour = colours.YellowLight; - } - - public DifficultyIncreaseSection() - { - Header = @"Difficulty Increase"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class DifficultyIncreaseSection : ModSection + { + protected override Key[] ToggleKeys => new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; + public override ModType ModType => ModType.DifficultyIncrease; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.YellowLight; + } + + public DifficultyIncreaseSection() + { + Header = @"Difficulty Increase"; + } + } +} diff --git a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs index 651fc222b5..013deea579 100644 --- a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Input; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Overlays.Mods -{ - public class DifficultyReductionSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; - public override ModType ModType => ModType.DifficultyReduction; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SelectedColour = colours.GreenLight; - } - - public DifficultyReductionSection() - { - Header = @"Difficulty Reduction"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class DifficultyReductionSection : ModSection + { + protected override Key[] ToggleKeys => new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; + public override ModType ModType => ModType.DifficultyReduction; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.GreenLight; + } + + public DifficultyReductionSection() + { + Header = @"Difficulty Reduction"; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index a4cc79bca6..2a4f243606 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -1,276 +1,276 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; -using System; -using System.Linq; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Mods -{ - /// - /// Represents a clickable button which can cycle through one of more mods. - /// - public class ModButton : ModButtonEmpty, IHasTooltip - { - private ModIcon foregroundIcon; - private ModIcon backgroundIcon; - private readonly SpriteText text; - private readonly Container iconsContainer; - - /// - /// Fired when the selection changes. - /// - public Action SelectionChanged; - - public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; - - private const Easing mod_switch_easing = Easing.InOutSine; - private const double mod_switch_duration = 120; - - // A selected index of -1 means not selected. - private int selectedIndex = -1; - - /// - /// Change the selected mod index of this button. - /// - /// The new index. - /// Whether the selection changed. - private bool changeSelectedIndex(int newIndex) - { - if (newIndex == selectedIndex) return false; - - int direction = newIndex < selectedIndex ? -1 : 1; - bool beforeSelected = Selected; - - Mod modBefore = SelectedMod ?? Mods[0]; - - if (newIndex >= Mods.Length) - newIndex = -1; - else if (newIndex < -1) - newIndex = Mods.Length - 1; - - if (newIndex >= 0 && !Mods[newIndex].HasImplementation) - return false; - - selectedIndex = newIndex; - Mod modAfter = SelectedMod ?? Mods[0]; - - if (beforeSelected != Selected) - { - iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); - iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic); - } - - if (modBefore != modAfter) - { - const float rotate_angle = 16; - - foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing); - backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); - - backgroundIcon.Icon = modAfter.Icon; - using (BeginDelayedSequence(mod_switch_duration, true)) - { - foregroundIcon - .RotateTo(-rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - backgroundIcon - .RotateTo(rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - Schedule(() => displayMod(modAfter)); - } - } - - foregroundIcon.Highlighted = Selected; - - SelectionChanged?.Invoke(SelectedMod); - return true; - } - - public bool Selected => selectedIndex != -1; - - private Color4 selectedColour; - - public Color4 SelectedColour - { - get { return selectedColour; } - set - { - if (value == selectedColour) return; - selectedColour = value; - if (Selected) foregroundIcon.Colour = value; - } - } - - private Mod mod; - - public Mod Mod - { - get { return mod; } - set - { - mod = value; - - if (mod == null) - { - Mods = Array.Empty(); - Alpha = 0; - } - else - { - Mods = (mod as MultiMod)?.Mods ?? new[] { mod }; - Alpha = 1; - } - - createIcons(); - if (Mods.Length > 0) - { - displayMod(Mods[0]); - } - } - } - - public Mod[] Mods { get; private set; } - - public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - switch (args.Button) - { - case MouseButton.Left: - SelectNext(1); - break; - case MouseButton.Right: - SelectNext(-1); - break; - } - - return true; - } - - /// - /// Select the next available mod in a specified direction. - /// - /// 1 for forwards, -1 for backwards. - public void SelectNext(int direction) - { - int start = selectedIndex + direction; - // wrap around if we are at an extremity. - if (start >= Mods.Length) - start = -1; - else if (start < -1) - start = Mods.Length - 1; - - for (int i = start; i < Mods.Length && i >= 0; i += direction) - if (SelectAt(i)) return; - - Deselect(); - } - - public bool SelectAt(int index) - { - if (!Mods[index].HasImplementation) return false; - - changeSelectedIndex(index); - return true; - } - - public void Deselect() => changeSelectedIndex(-1); - - private void displayMod(Mod mod) - { - if (backgroundIcon != null) - backgroundIcon.Icon = foregroundIcon.Icon; - foregroundIcon.Icon = mod.Icon; - text.Text = mod.Name; - Colour = mod.HasImplementation ? Color4.White : Color4.Gray; - } - - private void createIcons() - { - iconsContainer.Clear(); - if (Mods.Length > 1) - { - iconsContainer.AddRange(new[] - { - backgroundIcon = new PassThroughTooltipModIcon(Mods[1]) - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Position = new Vector2(1.5f), - }, - foregroundIcon = new PassThroughTooltipModIcon(Mods[0]) - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, - Position = new Vector2(-1.5f), - }, - }); - } - else - { - iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - }); - } - } - - public ModButton(Mod mod) - { - Children = new Drawable[] - { - new Container - { - Size = new Vector2(77f, 80f), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - iconsContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - } - }, - text = new OsuSpriteText - { - Y = 75, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - TextSize = 18, - }, - new HoverClickSounds() - }; - - Mod = mod; - } - - private class PassThroughTooltipModIcon : ModIcon - { - public override string TooltipText => null; - - public PassThroughTooltipModIcon(Mod mod) - : base(mod) - { - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using System; +using System.Linq; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Mods +{ + /// + /// Represents a clickable button which can cycle through one of more mods. + /// + public class ModButton : ModButtonEmpty, IHasTooltip + { + private ModIcon foregroundIcon; + private ModIcon backgroundIcon; + private readonly SpriteText text; + private readonly Container iconsContainer; + + /// + /// Fired when the selection changes. + /// + public Action SelectionChanged; + + public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; + + private const Easing mod_switch_easing = Easing.InOutSine; + private const double mod_switch_duration = 120; + + // A selected index of -1 means not selected. + private int selectedIndex = -1; + + /// + /// Change the selected mod index of this button. + /// + /// The new index. + /// Whether the selection changed. + private bool changeSelectedIndex(int newIndex) + { + if (newIndex == selectedIndex) return false; + + int direction = newIndex < selectedIndex ? -1 : 1; + bool beforeSelected = Selected; + + Mod modBefore = SelectedMod ?? Mods[0]; + + if (newIndex >= Mods.Length) + newIndex = -1; + else if (newIndex < -1) + newIndex = Mods.Length - 1; + + if (newIndex >= 0 && !Mods[newIndex].HasImplementation) + return false; + + selectedIndex = newIndex; + Mod modAfter = SelectedMod ?? Mods[0]; + + if (beforeSelected != Selected) + { + iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); + iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic); + } + + if (modBefore != modAfter) + { + const float rotate_angle = 16; + + foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing); + backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); + + backgroundIcon.Icon = modAfter.Icon; + using (BeginDelayedSequence(mod_switch_duration, true)) + { + foregroundIcon + .RotateTo(-rotate_angle * direction) + .RotateTo(0f, mod_switch_duration, mod_switch_easing); + + backgroundIcon + .RotateTo(rotate_angle * direction) + .RotateTo(0f, mod_switch_duration, mod_switch_easing); + + Schedule(() => displayMod(modAfter)); + } + } + + foregroundIcon.Highlighted = Selected; + + SelectionChanged?.Invoke(SelectedMod); + return true; + } + + public bool Selected => selectedIndex != -1; + + private Color4 selectedColour; + + public Color4 SelectedColour + { + get { return selectedColour; } + set + { + if (value == selectedColour) return; + selectedColour = value; + if (Selected) foregroundIcon.Colour = value; + } + } + + private Mod mod; + + public Mod Mod + { + get { return mod; } + set + { + mod = value; + + if (mod == null) + { + Mods = Array.Empty(); + Alpha = 0; + } + else + { + Mods = (mod as MultiMod)?.Mods ?? new[] { mod }; + Alpha = 1; + } + + createIcons(); + if (Mods.Length > 0) + { + displayMod(Mods[0]); + } + } + } + + public Mod[] Mods { get; private set; } + + public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + switch (args.Button) + { + case MouseButton.Left: + SelectNext(1); + break; + case MouseButton.Right: + SelectNext(-1); + break; + } + + return true; + } + + /// + /// Select the next available mod in a specified direction. + /// + /// 1 for forwards, -1 for backwards. + public void SelectNext(int direction) + { + int start = selectedIndex + direction; + // wrap around if we are at an extremity. + if (start >= Mods.Length) + start = -1; + else if (start < -1) + start = Mods.Length - 1; + + for (int i = start; i < Mods.Length && i >= 0; i += direction) + if (SelectAt(i)) return; + + Deselect(); + } + + public bool SelectAt(int index) + { + if (!Mods[index].HasImplementation) return false; + + changeSelectedIndex(index); + return true; + } + + public void Deselect() => changeSelectedIndex(-1); + + private void displayMod(Mod mod) + { + if (backgroundIcon != null) + backgroundIcon.Icon = foregroundIcon.Icon; + foregroundIcon.Icon = mod.Icon; + text.Text = mod.Name; + Colour = mod.HasImplementation ? Color4.White : Color4.Gray; + } + + private void createIcons() + { + iconsContainer.Clear(); + if (Mods.Length > 1) + { + iconsContainer.AddRange(new[] + { + backgroundIcon = new PassThroughTooltipModIcon(Mods[1]) + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomRight, + Position = new Vector2(1.5f), + }, + foregroundIcon = new PassThroughTooltipModIcon(Mods[0]) + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomRight, + Position = new Vector2(-1.5f), + }, + }); + } + else + { + iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }); + } + } + + public ModButton(Mod mod) + { + Children = new Drawable[] + { + new Container + { + Size = new Vector2(77f, 80f), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + iconsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + } + }, + text = new OsuSpriteText + { + Y = 75, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + TextSize = 18, + }, + new HoverClickSounds() + }; + + Mod = mod; + } + + private class PassThroughTooltipModIcon : ModIcon + { + public override string TooltipText => null; + + public PassThroughTooltipModIcon(Mod mod) + : base(mod) + { + } + } + } +} diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs index e9e2683816..9129856c8f 100644 --- a/osu.Game/Overlays/Mods/ModButtonEmpty.cs +++ b/osu.Game/Overlays/Mods/ModButtonEmpty.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Overlays.Mods -{ - /// - /// A mod button used exclusively for providing an empty space the size of a mod button. - /// - public class ModButtonEmpty : Container - { - public ModButtonEmpty() - { - Size = new Vector2(100f); - AlwaysPresent = true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Overlays.Mods +{ + /// + /// A mod button used exclusively for providing an empty space the size of a mod button. + /// + public class ModButtonEmpty : Container + { + public ModButtonEmpty() + { + Size = new Vector2(100f); + AlwaysPresent = true; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 4765787caf..48f51f72ca 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -1,152 +1,152 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using System; -using System.Linq; -using System.Collections.Generic; - -namespace osu.Game.Overlays.Mods -{ - public abstract class ModSection : Container - { - private readonly OsuSpriteText headerLabel; - - public FillFlowContainer ButtonsContainer { get; } - - public Action Action; - protected abstract Key[] ToggleKeys { get; } - public abstract ModType ModType { get; } - - public string Header - { - get => headerLabel.Text; - set => headerLabel.Text = value; - } - - public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); - - public IEnumerable Mods - { - set - { - var modContainers = value.Select(m => - { - if (m == null) - return new ModButtonEmpty(); - - return new ModButton(m) - { - SelectedColour = selectedColour, - SelectionChanged = Action, - }; - }).ToArray(); - - ButtonsContainer.Children = modContainers; - buttons = modContainers.OfType().ToArray(); - } - } - - private ModButton[] buttons = { }; - - private Color4 selectedColour = Color4.White; - public Color4 SelectedColour - { - get => selectedColour; - set - { - if (value == selectedColour) return; - selectedColour = value; - - foreach (ModButton button in buttons) - button.SelectedColour = value; - } - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - var index = Array.IndexOf(ToggleKeys, args.Key); - if (index > -1 && index < buttons.Length) - buttons[index].SelectNext(state.Keyboard.ShiftPressed ? -1 : 1); - - return base.OnKeyDown(state, args); - } - - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); - - /// - /// Deselect one or more mods in this section. - /// - /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - public void DeselectTypes(IEnumerable modTypes, bool immediate = false) - { - int delay = 0; - foreach (var button in buttons) - { - Mod selected = button.SelectedMod; - if (selected == null) continue; - foreach (var type in modTypes) - if (type.IsInstanceOfType(selected)) - { - if (immediate) - button.Deselect(); - else - Scheduler.AddDelayed(button.Deselect, delay += 50); - } - } - } - - /// - /// Select one or more mods in this section and deselects all other ones. - /// - /// The types of s which should be selected. - public void SelectTypes(IEnumerable modTypes) - { - foreach (var button in buttons) - { - int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m))); - - if (i >= 0) - button.SelectAt(i); - else - button.Deselect(); - } - } - - protected ModSection() - { - AutoSizeAxes = Axes.Y; - - Children = new Drawable[] - { - headerLabel = new OsuSpriteText - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Position = new Vector2(0f, 0f), - Font = @"Exo2.0-Bold" - }, - ButtonsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Spacing = new Vector2(50f, 0f), - Margin = new MarginPadding - { - Top = 6, - }, - AlwaysPresent = true - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using System; +using System.Linq; +using System.Collections.Generic; + +namespace osu.Game.Overlays.Mods +{ + public abstract class ModSection : Container + { + private readonly OsuSpriteText headerLabel; + + public FillFlowContainer ButtonsContainer { get; } + + public Action Action; + protected abstract Key[] ToggleKeys { get; } + public abstract ModType ModType { get; } + + public string Header + { + get => headerLabel.Text; + set => headerLabel.Text = value; + } + + public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); + + public IEnumerable Mods + { + set + { + var modContainers = value.Select(m => + { + if (m == null) + return new ModButtonEmpty(); + + return new ModButton(m) + { + SelectedColour = selectedColour, + SelectionChanged = Action, + }; + }).ToArray(); + + ButtonsContainer.Children = modContainers; + buttons = modContainers.OfType().ToArray(); + } + } + + private ModButton[] buttons = { }; + + private Color4 selectedColour = Color4.White; + public Color4 SelectedColour + { + get => selectedColour; + set + { + if (value == selectedColour) return; + selectedColour = value; + + foreach (ModButton button in buttons) + button.SelectedColour = value; + } + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + var index = Array.IndexOf(ToggleKeys, args.Key); + if (index > -1 && index < buttons.Length) + buttons[index].SelectNext(state.Keyboard.ShiftPressed ? -1 : 1); + + return base.OnKeyDown(state, args); + } + + public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + + /// + /// Deselect one or more mods in this section. + /// + /// The types of s which should be deselected. + /// Set to true to bypass animations and update selections immediately. + public void DeselectTypes(IEnumerable modTypes, bool immediate = false) + { + int delay = 0; + foreach (var button in buttons) + { + Mod selected = button.SelectedMod; + if (selected == null) continue; + foreach (var type in modTypes) + if (type.IsInstanceOfType(selected)) + { + if (immediate) + button.Deselect(); + else + Scheduler.AddDelayed(button.Deselect, delay += 50); + } + } + } + + /// + /// Select one or more mods in this section and deselects all other ones. + /// + /// The types of s which should be selected. + public void SelectTypes(IEnumerable modTypes) + { + foreach (var button in buttons) + { + int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m))); + + if (i >= 0) + button.SelectAt(i); + else + button.Deselect(); + } + } + + protected ModSection() + { + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + headerLabel = new OsuSpriteText + { + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Position = new Vector2(0f, 0f), + Font = @"Exo2.0-Bold" + }, + ButtonsContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Spacing = new Vector2(50f, 0f), + Margin = new MarginPadding + { + Top = 6, + }, + AlwaysPresent = true + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d8c95da94f..48c38c467e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -1,383 +1,383 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Mods -{ - public class ModSelectOverlay : WaveOverlayContainer - { - private const float content_width = 0.8f; - - protected Color4 LowMultiplierColour, HighMultiplierColour; - - protected readonly TriangleButton DeselectAllButton; - protected readonly OsuSpriteText MultiplierLabel; - private readonly FillFlowContainer footerContainer; - - protected override bool BlockPassThroughKeyboard => false; - - protected readonly FillFlowContainer ModSectionsContainer; - - public readonly Bindable> SelectedMods = new Bindable>(); - - public readonly Bindable Ruleset = new Bindable(); - - private void rulesetChanged(RulesetInfo newRuleset) - { - var instance = newRuleset.CreateInstance(); - - foreach (ModSection section in ModSectionsContainer.Children) - section.Mods = instance.GetModsFor(section.ModType); - refreshSelectedMods(); - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets, AudioManager audio) - { - SelectedMods.ValueChanged += selectedModsChanged; - - LowMultiplierColour = colours.Red; - HighMultiplierColour = colours.Green; - - if (osu != null) - Ruleset.BindTo(osu.Ruleset); - else - Ruleset.Value = rulesets.AvailableRulesets.First(); - - Ruleset.ValueChanged += rulesetChanged; - Ruleset.TriggerChange(); - - sampleOn = audio.Sample.Get(@"UI/check-on"); - sampleOff = audio.Sample.Get(@"UI/check-off"); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - Ruleset.UnbindAll(); - SelectedMods.UnbindAll(); - } - - private void selectedModsChanged(IEnumerable obj) - { - foreach (ModSection section in ModSectionsContainer.Children) - section.SelectTypes(obj.Select(m => m.GetType()).ToList()); - - updateMods(); - } - - private void updateMods() - { - double multiplier = 1.0; - bool ranked = true; - - foreach (Mod mod in SelectedMods.Value) - { - multiplier *= mod.ScoreMultiplier; - ranked &= mod.Ranked; - } - - MultiplierLabel.Text = $"{multiplier:N2}x"; - if (!ranked) - MultiplierLabel.Text += " (Unranked)"; - - if (multiplier > 1.0) - MultiplierLabel.FadeColour(HighMultiplierColour, 200); - else if (multiplier < 1.0) - MultiplierLabel.FadeColour(LowMultiplierColour, 200); - else - MultiplierLabel.FadeColour(Color4.White, 200); - } - - protected override void PopOut() - { - base.PopOut(); - - footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine); - footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); - - foreach (ModSection section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); - } - } - - protected override void PopIn() - { - base.PopIn(); - - footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); - footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); - - foreach (ModSection section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); - } - } - - public void DeselectAll() - { - foreach (ModSection section in ModSectionsContainer.Children) - section.DeselectAll(); - - refreshSelectedMods(); - } - - /// - /// Deselect one or more mods. - /// - /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - public void DeselectTypes(Type[] modTypes, bool immediate = false) - { - if (modTypes.Length == 0) return; - foreach (ModSection section in ModSectionsContainer.Children) - section.DeselectTypes(modTypes, immediate); - } - - - private SampleChannel sampleOn, sampleOff; - - private void modButtonPressed(Mod selectedMod) - { - if (selectedMod != null) - { - if (State == Visibility.Visible) sampleOn?.Play(); - DeselectTypes(selectedMod.IncompatibleMods, true); - } - else - { - if (State == Visibility.Visible) sampleOff?.Play(); - } - - refreshSelectedMods(); - } - - private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); - - public ModSelectOverlay() - { - FirstWaveColour = OsuColour.FromHex(@"19b0e2"); - SecondWaveColour = OsuColour.FromHex(@"2280a2"); - ThirdWaveColour = OsuColour.FromHex(@"005774"); - FourthWaveColour = OsuColour.FromHex(@"003a4e"); - - Height = 510; - Content.RelativeSizeAxes = Axes.X; - Content.AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(36, 50, 68, 255) - }, - new Triangles - { - TriangleScale = 5, - RelativeSizeAxes = Axes.X, - Height = Height, //set the height from the start to ensure correct triangle density. - ColourLight = new Color4(53, 66, 82, 255), - ColourDark = new Color4(41, 54, 70, 255), - }, - }, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - // Header - new Container - { - RelativeSizeAxes = Axes.X, - Height = 82, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(10).Opacity(100), - }, - new FillFlowContainer - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Width = content_width, - Padding = new MarginPadding - { - Top = 10, - Bottom = 10, - }, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = @"Exo2.0-Bold", - Text = @"Gameplay Mods", - TextSize = 22, - Shadow = true, - Margin = new MarginPadding - { - Bottom = 4, - }, - }, - new OsuSpriteText - { - Text = @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play.", - TextSize = 18, - Shadow = true, - }, - new OsuSpriteText - { - Text = @"Others are just for fun.", - TextSize = 18, - Shadow = true, - }, - }, - }, - }, - }, - // Body - ModSectionsContainer = new FillFlowContainer - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Width = content_width, - Children = new ModSection[] - { - new DifficultyReductionSection - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Action = modButtonPressed, - }, - new DifficultyIncreaseSection - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Action = modButtonPressed, - }, - new SpecialSection - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Action = modButtonPressed, - }, - } - }, - // Footer - new Container - { - RelativeSizeAxes = Axes.X, - Height = 70, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(172, 20, 116, 255), - Alpha = 0.5f, - }, - footerContainer = new FillFlowContainer - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = content_width, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding - { - Vertical = 15 - }, - Children = new Drawable[] - { - DeselectAllButton = new TriangleButton - { - Width = 180, - Text = "Deselect All", - Action = DeselectAll, - Margin = new MarginPadding - { - Right = 20 - } - }, - new OsuSpriteText - { - Text = @"Score Multiplier: ", - TextSize = 30, - Shadow = true, - Margin = new MarginPadding - { - Top = 5 - } - }, - MultiplierLabel = new OsuSpriteText - { - Font = @"Exo2.0-Bold", - TextSize = 30, - Shadow = true, - Margin = new MarginPadding - { - Top = 5 - } - } - } - } - }, - }, - }, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Mods +{ + public class ModSelectOverlay : WaveOverlayContainer + { + private const float content_width = 0.8f; + + protected Color4 LowMultiplierColour, HighMultiplierColour; + + protected readonly TriangleButton DeselectAllButton; + protected readonly OsuSpriteText MultiplierLabel; + private readonly FillFlowContainer footerContainer; + + protected override bool BlockPassThroughKeyboard => false; + + protected readonly FillFlowContainer ModSectionsContainer; + + public readonly Bindable> SelectedMods = new Bindable>(); + + public readonly Bindable Ruleset = new Bindable(); + + private void rulesetChanged(RulesetInfo newRuleset) + { + var instance = newRuleset.CreateInstance(); + + foreach (ModSection section in ModSectionsContainer.Children) + section.Mods = instance.GetModsFor(section.ModType); + refreshSelectedMods(); + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets, AudioManager audio) + { + SelectedMods.ValueChanged += selectedModsChanged; + + LowMultiplierColour = colours.Red; + HighMultiplierColour = colours.Green; + + if (osu != null) + Ruleset.BindTo(osu.Ruleset); + else + Ruleset.Value = rulesets.AvailableRulesets.First(); + + Ruleset.ValueChanged += rulesetChanged; + Ruleset.TriggerChange(); + + sampleOn = audio.Sample.Get(@"UI/check-on"); + sampleOff = audio.Sample.Get(@"UI/check-off"); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + Ruleset.UnbindAll(); + SelectedMods.UnbindAll(); + } + + private void selectedModsChanged(IEnumerable obj) + { + foreach (ModSection section in ModSectionsContainer.Children) + section.SelectTypes(obj.Select(m => m.GetType()).ToList()); + + updateMods(); + } + + private void updateMods() + { + double multiplier = 1.0; + bool ranked = true; + + foreach (Mod mod in SelectedMods.Value) + { + multiplier *= mod.ScoreMultiplier; + ranked &= mod.Ranked; + } + + MultiplierLabel.Text = $"{multiplier:N2}x"; + if (!ranked) + MultiplierLabel.Text += " (Unranked)"; + + if (multiplier > 1.0) + MultiplierLabel.FadeColour(HighMultiplierColour, 200); + else if (multiplier < 1.0) + MultiplierLabel.FadeColour(LowMultiplierColour, 200); + else + MultiplierLabel.FadeColour(Color4.White, 200); + } + + protected override void PopOut() + { + base.PopOut(); + + footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine); + footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); + + foreach (ModSection section in ModSectionsContainer.Children) + { + section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); + } + } + + protected override void PopIn() + { + base.PopIn(); + + footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); + + foreach (ModSection section in ModSectionsContainer.Children) + { + section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); + } + } + + public void DeselectAll() + { + foreach (ModSection section in ModSectionsContainer.Children) + section.DeselectAll(); + + refreshSelectedMods(); + } + + /// + /// Deselect one or more mods. + /// + /// The types of s which should be deselected. + /// Set to true to bypass animations and update selections immediately. + public void DeselectTypes(Type[] modTypes, bool immediate = false) + { + if (modTypes.Length == 0) return; + foreach (ModSection section in ModSectionsContainer.Children) + section.DeselectTypes(modTypes, immediate); + } + + + private SampleChannel sampleOn, sampleOff; + + private void modButtonPressed(Mod selectedMod) + { + if (selectedMod != null) + { + if (State == Visibility.Visible) sampleOn?.Play(); + DeselectTypes(selectedMod.IncompatibleMods, true); + } + else + { + if (State == Visibility.Visible) sampleOff?.Play(); + } + + refreshSelectedMods(); + } + + private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + + public ModSelectOverlay() + { + FirstWaveColour = OsuColour.FromHex(@"19b0e2"); + SecondWaveColour = OsuColour.FromHex(@"2280a2"); + ThirdWaveColour = OsuColour.FromHex(@"005774"); + FourthWaveColour = OsuColour.FromHex(@"003a4e"); + + Height = 510; + Content.RelativeSizeAxes = Axes.X; + Content.AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(36, 50, 68, 255) + }, + new Triangles + { + TriangleScale = 5, + RelativeSizeAxes = Axes.X, + Height = Height, //set the height from the start to ensure correct triangle density. + ColourLight = new Color4(53, 66, 82, 255), + ColourDark = new Color4(41, 54, 70, 255), + }, + }, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] + { + // Header + new Container + { + RelativeSizeAxes = Axes.X, + Height = 82, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(10).Opacity(100), + }, + new FillFlowContainer + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Width = content_width, + Padding = new MarginPadding + { + Top = 10, + Bottom = 10, + }, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = @"Gameplay Mods", + TextSize = 22, + Shadow = true, + Margin = new MarginPadding + { + Bottom = 4, + }, + }, + new OsuSpriteText + { + Text = @"Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play.", + TextSize = 18, + Shadow = true, + }, + new OsuSpriteText + { + Text = @"Others are just for fun.", + TextSize = 18, + Shadow = true, + }, + }, + }, + }, + }, + // Body + ModSectionsContainer = new FillFlowContainer + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Width = content_width, + Children = new ModSection[] + { + new DifficultyReductionSection + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Action = modButtonPressed, + }, + new DifficultyIncreaseSection + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Action = modButtonPressed, + }, + new SpecialSection + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Action = modButtonPressed, + }, + } + }, + // Footer + new Container + { + RelativeSizeAxes = Axes.X, + Height = 70, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(172, 20, 116, 255), + Alpha = 0.5f, + }, + footerContainer = new FillFlowContainer + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Width = content_width, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding + { + Vertical = 15 + }, + Children = new Drawable[] + { + DeselectAllButton = new TriangleButton + { + Width = 180, + Text = "Deselect All", + Action = DeselectAll, + Margin = new MarginPadding + { + Right = 20 + } + }, + new OsuSpriteText + { + Text = @"Score Multiplier: ", + TextSize = 30, + Shadow = true, + Margin = new MarginPadding + { + Top = 5 + } + }, + MultiplierLabel = new OsuSpriteText + { + Font = @"Exo2.0-Bold", + TextSize = 30, + Shadow = true, + Margin = new MarginPadding + { + Top = 5 + } + } + } + } + }, + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Mods/SpecialSection.cs b/osu.Game/Overlays/Mods/SpecialSection.cs index 75b2462ff5..b3540cf915 100644 --- a/osu.Game/Overlays/Mods/SpecialSection.cs +++ b/osu.Game/Overlays/Mods/SpecialSection.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Input; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Overlays.Mods -{ - public class SpecialSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; - public override ModType ModType => ModType.Special; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SelectedColour = colours.BlueLight; - } - - public SpecialSection() - { - Header = @"Special"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class SpecialSection : ModSection + { + protected override Key[] ToggleKeys => new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; + public override ModType ModType => ModType.Special; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.BlueLight; + } + + public SpecialSection() + { + Header = @"Special"; + } + } +} diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionsDropdown.cs index 648a00993d..e06688e8f0 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionsDropdown.cs @@ -1,74 +1,74 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Music -{ - public class CollectionsDropdown : OsuDropdown - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = colours.Gray6; - } - - protected override DropdownHeader CreateHeader() => new CollectionsHeader(); - - protected override DropdownMenu CreateMenu() => new CollectionsMenu(); - - private class CollectionsMenu : OsuDropdownMenu - { - public CollectionsMenu() - { - CornerRadius = 5; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.3f), - Radius = 3, - Offset = new Vector2(0f, 1f), - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Gray4; - } - } - - private class CollectionsHeader : OsuDropdownHeader - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Gray4; - } - - public CollectionsHeader() - { - CornerRadius = 5; - Height = 30; - Icon.Size = new Vector2(14); - Icon.Margin = new MarginPadding(0); - Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 10, Right = 10 }; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.3f), - Radius = 3, - Offset = new Vector2(0f, 1f), - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Music +{ + public class CollectionsDropdown : OsuDropdown + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Gray6; + } + + protected override DropdownHeader CreateHeader() => new CollectionsHeader(); + + protected override DropdownMenu CreateMenu() => new CollectionsMenu(); + + private class CollectionsMenu : OsuDropdownMenu + { + public CollectionsMenu() + { + CornerRadius = 5; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.3f), + Radius = 3, + Offset = new Vector2(0f, 1f), + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray4; + } + } + + private class CollectionsHeader : OsuDropdownHeader + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray4; + } + + public CollectionsHeader() + { + CornerRadius = 5; + Height = 30; + Icon.Size = new Vector2(14); + Icon.Margin = new MarginPadding(0); + Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 10, Right = 10 }; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.3f), + Radius = 3, + Offset = new Vector2(0f, 1f), + }; + } + } + } +} diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index fa437fc2e8..2d492dcf1e 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -1,76 +1,76 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; -using System; - -namespace osu.Game.Overlays.Music -{ - public class FilterControl : Container - { - public readonly FilterTextBox Search; - - public FilterControl() - { - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - Search = new FilterTextBox - { - RelativeSizeAxes = Axes.X, - Height = 40, - Exit = () => ExitRequested?.Invoke(), - }, - new CollectionsDropdown - { - RelativeSizeAxes = Axes.X, - Items = new[] { new KeyValuePair(@"All", PlaylistCollection.All) }, - } - }, - }, - }; - - Search.Current.ValueChanged += current_ValueChanged; - } - - private void current_ValueChanged(string newValue) => FilterChanged?.Invoke(newValue); - - public Action ExitRequested; - - public Action FilterChanged; - - public class FilterTextBox : SearchTextBox - { - private Color4 backgroundColour; - - protected override Color4 BackgroundUnfocused => backgroundColour; - protected override Color4 BackgroundFocused => backgroundColour; - protected override bool AllowCommit => true; - - public FilterTextBox() - { - Masking = true; - CornerRadius = 5; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - backgroundColour = colours.Gray2; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using OpenTK; +using OpenTK.Graphics; +using System; + +namespace osu.Game.Overlays.Music +{ + public class FilterControl : Container + { + public readonly FilterTextBox Search; + + public FilterControl() + { + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] + { + Search = new FilterTextBox + { + RelativeSizeAxes = Axes.X, + Height = 40, + Exit = () => ExitRequested?.Invoke(), + }, + new CollectionsDropdown + { + RelativeSizeAxes = Axes.X, + Items = new[] { new KeyValuePair(@"All", PlaylistCollection.All) }, + } + }, + }, + }; + + Search.Current.ValueChanged += current_ValueChanged; + } + + private void current_ValueChanged(string newValue) => FilterChanged?.Invoke(newValue); + + public Action ExitRequested; + + public Action FilterChanged; + + public class FilterTextBox : SearchTextBox + { + private Color4 backgroundColour; + + protected override Color4 BackgroundUnfocused => backgroundColour; + protected override Color4 BackgroundFocused => backgroundColour; + protected override bool AllowCommit => true; + + public FilterTextBox() + { + Masking = true; + CornerRadius = 5; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + backgroundColour = colours.Gray2; + } + } + } +} diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 71fdcff6af..a630a86fce 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -1,182 +1,182 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Framework.Localisation; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Overlays.Music -{ - public class PlaylistItem : Container, IFilterable, IDraggable - { - private const float fade_duration = 100; - - private Color4 hoverColour; - private Color4 artistColour; - - private SpriteIcon handle; - private TextFlowContainer text; - private IEnumerable titleSprites; - private UnicodeBindableString titleBind; - private UnicodeBindableString artistBind; - - public readonly BeatmapSetInfo BeatmapSetInfo; - - public Action OnSelect; - - public bool IsDraggable { get; private set; } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - IsDraggable = handle.IsHovered; - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - IsDraggable = false; - return base.OnMouseUp(state, args); - } - - private bool selected; - public bool Selected - { - get { return selected; } - set - { - if (value == selected) return; - selected = value; - - FinishTransforms(true); - foreach (SpriteText s in titleSprites) - s.FadeColour(Selected ? hoverColour : Color4.White, fade_duration); - } - } - - public PlaylistItem(BeatmapSetInfo setInfo) - { - BeatmapSetInfo = setInfo; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Top = 3, Bottom = 3 }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationEngine localisation) - { - hoverColour = colours.Yellow; - artistColour = colours.Gray9; - - var metadata = BeatmapSetInfo.Metadata; - FilterTerms = metadata.SearchableTerms; - - Children = new Drawable[] - { - handle = new PlaylistItemHandle - { - Colour = colours.Gray5 - }, - text = new OsuTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 20 }, - ContentIndent = 10f, - }, - }; - - titleBind = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); - artistBind = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); - - artistBind.ValueChanged += newText => recreateText(); - artistBind.TriggerChange(); - } - - private void recreateText() - { - text.Clear(); - - //space after the title to put a space between the title and artist - titleSprites = text.AddText(titleBind.Value + @" ", sprite => - { - sprite.TextSize = 16; - sprite.Font = @"Exo2.0-Regular"; - }); - - text.AddText(artistBind.Value, sprite => - { - sprite.TextSize = 14; - sprite.Font = @"Exo2.0-Bold"; - sprite.Colour = artistColour; - sprite.Padding = new MarginPadding { Top = 1 }; - }); - } - - protected override bool OnHover(InputState state) - { - handle.FadeIn(fade_duration); - - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - handle.FadeOut(fade_duration); - } - - protected override bool OnClick(InputState state) - { - OnSelect?.Invoke(BeatmapSetInfo); - return true; - } - - public IEnumerable FilterTerms { get; private set; } - - private bool matching = true; - - public bool MatchingFilter - { - get { return matching; } - set - { - if (matching == value) return; - - matching = value; - - this.FadeTo(matching ? 1 : 0, 200); - } - } - - private class PlaylistItemHandle : SpriteIcon - { - public PlaylistItemHandle() - { - Anchor = Anchor.TopLeft; - Origin = Anchor.TopLeft; - Size = new Vector2(12); - Icon = FontAwesome.fa_bars; - Alpha = 0f; - Margin = new MarginPadding { Left = 5, Top = 2 }; - } - } - } - - public interface IDraggable : IDrawable - { - /// - /// Whether this can be dragged in its current state. - /// - bool IsDraggable { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Overlays.Music +{ + public class PlaylistItem : Container, IFilterable, IDraggable + { + private const float fade_duration = 100; + + private Color4 hoverColour; + private Color4 artistColour; + + private SpriteIcon handle; + private TextFlowContainer text; + private IEnumerable titleSprites; + private UnicodeBindableString titleBind; + private UnicodeBindableString artistBind; + + public readonly BeatmapSetInfo BeatmapSetInfo; + + public Action OnSelect; + + public bool IsDraggable { get; private set; } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + IsDraggable = handle.IsHovered; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + IsDraggable = false; + return base.OnMouseUp(state, args); + } + + private bool selected; + public bool Selected + { + get { return selected; } + set + { + if (value == selected) return; + selected = value; + + FinishTransforms(true); + foreach (SpriteText s in titleSprites) + s.FadeColour(Selected ? hoverColour : Color4.White, fade_duration); + } + } + + public PlaylistItem(BeatmapSetInfo setInfo) + { + BeatmapSetInfo = setInfo; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Top = 3, Bottom = 3 }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, LocalisationEngine localisation) + { + hoverColour = colours.Yellow; + artistColour = colours.Gray9; + + var metadata = BeatmapSetInfo.Metadata; + FilterTerms = metadata.SearchableTerms; + + Children = new Drawable[] + { + handle = new PlaylistItemHandle + { + Colour = colours.Gray5 + }, + text = new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 20 }, + ContentIndent = 10f, + }, + }; + + titleBind = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); + artistBind = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); + + artistBind.ValueChanged += newText => recreateText(); + artistBind.TriggerChange(); + } + + private void recreateText() + { + text.Clear(); + + //space after the title to put a space between the title and artist + titleSprites = text.AddText(titleBind.Value + @" ", sprite => + { + sprite.TextSize = 16; + sprite.Font = @"Exo2.0-Regular"; + }); + + text.AddText(artistBind.Value, sprite => + { + sprite.TextSize = 14; + sprite.Font = @"Exo2.0-Bold"; + sprite.Colour = artistColour; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + } + + protected override bool OnHover(InputState state) + { + handle.FadeIn(fade_duration); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + handle.FadeOut(fade_duration); + } + + protected override bool OnClick(InputState state) + { + OnSelect?.Invoke(BeatmapSetInfo); + return true; + } + + public IEnumerable FilterTerms { get; private set; } + + private bool matching = true; + + public bool MatchingFilter + { + get { return matching; } + set + { + if (matching == value) return; + + matching = value; + + this.FadeTo(matching ? 1 : 0, 200); + } + } + + private class PlaylistItemHandle : SpriteIcon + { + public PlaylistItemHandle() + { + Anchor = Anchor.TopLeft; + Origin = Anchor.TopLeft; + Size = new Vector2(12); + Icon = FontAwesome.fa_bars; + Alpha = 0f; + Margin = new MarginPadding { Left = 5, Top = 2 }; + } + } + } + + public interface IDraggable : IDrawable + { + /// + /// Whether this can be dragged in its current state. + /// + bool IsDraggable { get; } + } +} diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 03ce7fd88f..d63babf3b6 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -1,255 +1,255 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Overlays.Music -{ - public class PlaylistList : CompositeDrawable - { - public Action OnSelect; - - private readonly ItemsScrollContainer items; - - public PlaylistList() - { - InternalChild = items = new ItemsScrollContainer - { - RelativeSizeAxes = Axes.Both, - OnSelect = set => OnSelect?.Invoke(set) - }; - } - - public new MarginPadding Padding - { - get { return base.Padding; } - set { base.Padding = value; } - } - - public IEnumerable BeatmapSets - { - get { return items.Sets; } - set { items.Sets = value; } - } - - public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet; - public BeatmapSetInfo NextSet => items.NextSet; - public BeatmapSetInfo PreviousSet => items.PreviousSet; - - public BeatmapSetInfo SelectedSet - { - get { return items.SelectedSet; } - set { items.SelectedSet = value; } - } - - public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet); - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet); - - public void Filter(string searchTerm) => items.SearchTerm = searchTerm; - - private class ItemsScrollContainer : OsuScrollContainer - { - public Action OnSelect; - - private readonly SearchContainer search; - private readonly FillFlowContainer items; - - public ItemsScrollContainer() - { - Children = new Drawable[] - { - search = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - items = new ItemSearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - } - } - }; - } - - public IEnumerable Sets - { - get { return items.Select(x => x.BeatmapSetInfo).ToList(); } - set - { - items.Clear(); - value.ForEach(AddBeatmapSet); - } - } - - public string SearchTerm - { - get { return search.SearchTerm; } - set { search.SearchTerm = value; } - } - - public void AddBeatmapSet(BeatmapSetInfo beatmapSet) - { - var newItem = new PlaylistItem(beatmapSet) { OnSelect = set => OnSelect?.Invoke(set) }; - - items.Add(newItem); - items.SetLayoutPosition(newItem, items.Count); - } - - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) - { - var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID); - if (itemToRemove != null) - items.Remove(itemToRemove); - } - - public BeatmapSetInfo SelectedSet - { - get { return items.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; } - set - { - foreach (PlaylistItem s in items.Children) - s.Selected = s.BeatmapSetInfo.ID == value?.ID; - } - } - - public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo; - public BeatmapSetInfo NextSet => (items.SkipWhile(i => !i.Selected).Skip(1).FirstOrDefault() ?? items.FirstOrDefault())?.BeatmapSetInfo; - public BeatmapSetInfo PreviousSet => (items.TakeWhile(i => !i.Selected).LastOrDefault() ?? items.LastOrDefault())?.BeatmapSetInfo; - - private Vector2 nativeDragPosition; - private PlaylistItem draggedItem; - - protected override bool OnDragStart(InputState state) - { - nativeDragPosition = state.Mouse.NativeState.Position; - draggedItem = items.FirstOrDefault(d => d.IsDraggable); - return draggedItem != null || base.OnDragStart(state); - } - - protected override bool OnDrag(InputState state) - { - nativeDragPosition = state.Mouse.NativeState.Position; - if (draggedItem == null) - return base.OnDrag(state); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - nativeDragPosition = state.Mouse.NativeState.Position; - var handled = draggedItem != null || base.OnDragEnd(state); - draggedItem = null; - - return handled; - } - - protected override void Update() - { - base.Update(); - - if (draggedItem == null) - return; - - updateScrollPosition(); - updateDragPosition(); - } - - private void updateScrollPosition() - { - const float start_offset = 10; - const double max_power = 50; - const double exp_base = 1.05; - - var localPos = ToLocalSpace(nativeDragPosition); - - if (localPos.Y < start_offset) - { - if (Current <= 0) - return; - - var power = Math.Min(max_power, Math.Abs(start_offset - localPos.Y)); - ScrollBy(-(float)Math.Pow(exp_base, power)); - } - else if (localPos.Y > DrawHeight - start_offset) - { - if (IsScrolledToEnd()) - return; - - var power = Math.Min(max_power, Math.Abs(DrawHeight - start_offset - localPos.Y)); - ScrollBy((float)Math.Pow(exp_base, power)); - } - } - - private void updateDragPosition() - { - var itemsPos = items.ToLocalSpace(nativeDragPosition); - - int srcIndex = (int)items.GetLayoutPosition(draggedItem); - - // Find the last item with position < mouse position. Note we can't directly use - // the item positions as they are being transformed - float heightAccumulator = 0; - int dstIndex = 0; - for (; dstIndex < items.Count; dstIndex++) - { - // Using BoundingBox here takes care of scale, paddings, etc... - heightAccumulator += items[dstIndex].BoundingBox.Height; - if (heightAccumulator > itemsPos.Y) - break; - } - - dstIndex = MathHelper.Clamp(dstIndex, 0, items.Count - 1); - - if (srcIndex == dstIndex) - return; - - if (srcIndex < dstIndex) - { - for (int i = srcIndex + 1; i <= dstIndex; i++) - items.SetLayoutPosition(items[i], i - 1); - } - else - { - for (int i = dstIndex; i < srcIndex; i++) - items.SetLayoutPosition(items[i], i + 1); - } - - items.SetLayoutPosition(draggedItem, dstIndex); - } - - private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren - { - public IEnumerable FilterTerms => new string[] { }; - - public bool MatchingFilter - { - set - { - if (value) - InvalidateLayout(); - } - } - - public IEnumerable FilterableChildren => Children; - - public ItemSearchContainer() - { - LayoutDuration = 200; - LayoutEasing = Easing.OutQuint; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Overlays.Music +{ + public class PlaylistList : CompositeDrawable + { + public Action OnSelect; + + private readonly ItemsScrollContainer items; + + public PlaylistList() + { + InternalChild = items = new ItemsScrollContainer + { + RelativeSizeAxes = Axes.Both, + OnSelect = set => OnSelect?.Invoke(set) + }; + } + + public new MarginPadding Padding + { + get { return base.Padding; } + set { base.Padding = value; } + } + + public IEnumerable BeatmapSets + { + get { return items.Sets; } + set { items.Sets = value; } + } + + public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet; + public BeatmapSetInfo NextSet => items.NextSet; + public BeatmapSetInfo PreviousSet => items.PreviousSet; + + public BeatmapSetInfo SelectedSet + { + get { return items.SelectedSet; } + set { items.SelectedSet = value; } + } + + public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet); + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet); + + public void Filter(string searchTerm) => items.SearchTerm = searchTerm; + + private class ItemsScrollContainer : OsuScrollContainer + { + public Action OnSelect; + + private readonly SearchContainer search; + private readonly FillFlowContainer items; + + public ItemsScrollContainer() + { + Children = new Drawable[] + { + search = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + items = new ItemSearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + } + } + }; + } + + public IEnumerable Sets + { + get { return items.Select(x => x.BeatmapSetInfo).ToList(); } + set + { + items.Clear(); + value.ForEach(AddBeatmapSet); + } + } + + public string SearchTerm + { + get { return search.SearchTerm; } + set { search.SearchTerm = value; } + } + + public void AddBeatmapSet(BeatmapSetInfo beatmapSet) + { + var newItem = new PlaylistItem(beatmapSet) { OnSelect = set => OnSelect?.Invoke(set) }; + + items.Add(newItem); + items.SetLayoutPosition(newItem, items.Count); + } + + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) + { + var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID); + if (itemToRemove != null) + items.Remove(itemToRemove); + } + + public BeatmapSetInfo SelectedSet + { + get { return items.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; } + set + { + foreach (PlaylistItem s in items.Children) + s.Selected = s.BeatmapSetInfo.ID == value?.ID; + } + } + + public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo; + public BeatmapSetInfo NextSet => (items.SkipWhile(i => !i.Selected).Skip(1).FirstOrDefault() ?? items.FirstOrDefault())?.BeatmapSetInfo; + public BeatmapSetInfo PreviousSet => (items.TakeWhile(i => !i.Selected).LastOrDefault() ?? items.LastOrDefault())?.BeatmapSetInfo; + + private Vector2 nativeDragPosition; + private PlaylistItem draggedItem; + + protected override bool OnDragStart(InputState state) + { + nativeDragPosition = state.Mouse.NativeState.Position; + draggedItem = items.FirstOrDefault(d => d.IsDraggable); + return draggedItem != null || base.OnDragStart(state); + } + + protected override bool OnDrag(InputState state) + { + nativeDragPosition = state.Mouse.NativeState.Position; + if (draggedItem == null) + return base.OnDrag(state); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + nativeDragPosition = state.Mouse.NativeState.Position; + var handled = draggedItem != null || base.OnDragEnd(state); + draggedItem = null; + + return handled; + } + + protected override void Update() + { + base.Update(); + + if (draggedItem == null) + return; + + updateScrollPosition(); + updateDragPosition(); + } + + private void updateScrollPosition() + { + const float start_offset = 10; + const double max_power = 50; + const double exp_base = 1.05; + + var localPos = ToLocalSpace(nativeDragPosition); + + if (localPos.Y < start_offset) + { + if (Current <= 0) + return; + + var power = Math.Min(max_power, Math.Abs(start_offset - localPos.Y)); + ScrollBy(-(float)Math.Pow(exp_base, power)); + } + else if (localPos.Y > DrawHeight - start_offset) + { + if (IsScrolledToEnd()) + return; + + var power = Math.Min(max_power, Math.Abs(DrawHeight - start_offset - localPos.Y)); + ScrollBy((float)Math.Pow(exp_base, power)); + } + } + + private void updateDragPosition() + { + var itemsPos = items.ToLocalSpace(nativeDragPosition); + + int srcIndex = (int)items.GetLayoutPosition(draggedItem); + + // Find the last item with position < mouse position. Note we can't directly use + // the item positions as they are being transformed + float heightAccumulator = 0; + int dstIndex = 0; + for (; dstIndex < items.Count; dstIndex++) + { + // Using BoundingBox here takes care of scale, paddings, etc... + heightAccumulator += items[dstIndex].BoundingBox.Height; + if (heightAccumulator > itemsPos.Y) + break; + } + + dstIndex = MathHelper.Clamp(dstIndex, 0, items.Count - 1); + + if (srcIndex == dstIndex) + return; + + if (srcIndex < dstIndex) + { + for (int i = srcIndex + 1; i <= dstIndex; i++) + items.SetLayoutPosition(items[i], i - 1); + } + else + { + for (int i = dstIndex; i < srcIndex; i++) + items.SetLayoutPosition(items[i], i + 1); + } + + items.SetLayoutPosition(draggedItem, dstIndex); + } + + private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren + { + public IEnumerable FilterTerms => new string[] { }; + + public bool MatchingFilter + { + set + { + if (value) + InvalidateLayout(); + } + } + + public IEnumerable FilterableChildren => Children; + + public ItemSearchContainer() + { + LayoutDuration = 200; + LayoutEasing = Easing.OutQuint; + } + } + } + } +} diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index c981e5f493..3496c044fb 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -1,177 +1,177 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Music -{ - public class PlaylistOverlay : OverlayContainer - { - private const float transition_duration = 600; - - private const float playlist_height = 510; - - private FilterControl filter; - private PlaylistList list; - - private BeatmapManager beatmaps; - - private readonly Bindable beatmapBacking = new Bindable(); - - public IEnumerable BeatmapSets => list.BeatmapSets; - - [BackgroundDependencyLoader] - private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours) - { - this.beatmaps = beatmaps; - - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - CornerRadius = 5, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(40), - Radius = 5, - }, - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, - }, - list = new PlaylistList - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, - OnSelect = itemSelected, - }, - filter = new FilterControl - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ExitRequested = () => State = Visibility.Hidden, - FilterChanged = search => list.Filter(search), - Padding = new MarginPadding(10), - }, - }, - }, - }; - - beatmaps.ItemAdded += handleBeatmapAdded; - beatmaps.ItemRemoved += handleBeatmapRemoved; - - list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets(); - - beatmapBacking.BindTo(game.Beatmap); - - filter.Search.OnCommit = (sender, newText) => - { - var beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); - if (beatmap != null) playSpecified(beatmap); - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - beatmapBacking.ValueChanged += b => list.SelectedSet = b?.BeatmapSetInfo; - beatmapBacking.TriggerChange(); - } - - private void handleBeatmapAdded(BeatmapSetInfo setInfo) => Schedule(() => list.AddBeatmapSet(setInfo)); - private void handleBeatmapRemoved(BeatmapSetInfo setInfo) => Schedule(() => list.RemoveBeatmapSet(setInfo)); - - protected override void PopIn() - { - filter.Search.HoldFocus = true; - Schedule(() => GetContainingInputManager().ChangeFocus(filter.Search)); - - this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); - this.FadeIn(transition_duration, Easing.OutQuint); - } - - protected override void PopOut() - { - filter.Search.HoldFocus = false; - - this.ResizeTo(new Vector2(1, 0), transition_duration, Easing.OutQuint); - this.FadeOut(transition_duration); - } - - private void itemSelected(BeatmapSetInfo set) - { - if (set.ID == (beatmapBacking.Value?.BeatmapSetInfo?.ID ?? -1)) - { - beatmapBacking.Value?.Track?.Seek(0); - return; - } - - playSpecified(set.Beatmaps.First()); - } - - public void PlayPrevious() - { - var playable = list.PreviousSet; - - if (playable != null) - { - playSpecified(playable.Beatmaps.First()); - list.SelectedSet = playable; - } - } - - public void PlayNext() - { - var playable = list.NextSet; - - if (playable != null) - { - playSpecified(playable.Beatmaps.First()); - list.SelectedSet = playable; - } - } - - private void playSpecified(BeatmapInfo info) - { - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking); - - var track = beatmapBacking.Value.Track; - - track.Restart(); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - { - beatmaps.ItemAdded -= handleBeatmapAdded; - beatmaps.ItemRemoved -= handleBeatmapRemoved; - } - } - } - - //todo: placeholder - public enum PlaylistCollection - { - All - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Music +{ + public class PlaylistOverlay : OverlayContainer + { + private const float transition_duration = 600; + + private const float playlist_height = 510; + + private FilterControl filter; + private PlaylistList list; + + private BeatmapManager beatmaps; + + private readonly Bindable beatmapBacking = new Bindable(); + + public IEnumerable BeatmapSets => list.BeatmapSets; + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours) + { + this.beatmaps = beatmaps; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(40), + Radius = 5, + }, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }, + list = new PlaylistList + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, + OnSelect = itemSelected, + }, + filter = new FilterControl + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ExitRequested = () => State = Visibility.Hidden, + FilterChanged = search => list.Filter(search), + Padding = new MarginPadding(10), + }, + }, + }, + }; + + beatmaps.ItemAdded += handleBeatmapAdded; + beatmaps.ItemRemoved += handleBeatmapRemoved; + + list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets(); + + beatmapBacking.BindTo(game.Beatmap); + + filter.Search.OnCommit = (sender, newText) => + { + var beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (beatmap != null) playSpecified(beatmap); + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + beatmapBacking.ValueChanged += b => list.SelectedSet = b?.BeatmapSetInfo; + beatmapBacking.TriggerChange(); + } + + private void handleBeatmapAdded(BeatmapSetInfo setInfo) => Schedule(() => list.AddBeatmapSet(setInfo)); + private void handleBeatmapRemoved(BeatmapSetInfo setInfo) => Schedule(() => list.RemoveBeatmapSet(setInfo)); + + protected override void PopIn() + { + filter.Search.HoldFocus = true; + Schedule(() => GetContainingInputManager().ChangeFocus(filter.Search)); + + this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); + this.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + filter.Search.HoldFocus = false; + + this.ResizeTo(new Vector2(1, 0), transition_duration, Easing.OutQuint); + this.FadeOut(transition_duration); + } + + private void itemSelected(BeatmapSetInfo set) + { + if (set.ID == (beatmapBacking.Value?.BeatmapSetInfo?.ID ?? -1)) + { + beatmapBacking.Value?.Track?.Seek(0); + return; + } + + playSpecified(set.Beatmaps.First()); + } + + public void PlayPrevious() + { + var playable = list.PreviousSet; + + if (playable != null) + { + playSpecified(playable.Beatmaps.First()); + list.SelectedSet = playable; + } + } + + public void PlayNext() + { + var playable = list.NextSet; + + if (playable != null) + { + playSpecified(playable.Beatmaps.First()); + list.SelectedSet = playable; + } + } + + private void playSpecified(BeatmapInfo info) + { + beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking); + + var track = beatmapBacking.Value.Track; + + track.Restart(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmaps != null) + { + beatmaps.ItemAdded -= handleBeatmapAdded; + beatmaps.ItemRemoved -= handleBeatmapRemoved; + } + } + } + + //todo: placeholder + public enum PlaylistCollection + { + All + } +} diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 74f6e4435d..b4021f2808 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -1,447 +1,447 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using System.Threading.Tasks; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input; -using osu.Framework.Localisation; -using osu.Framework.Threading; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Music; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays -{ - public class MusicController : OsuFocusedOverlayContainer - { - private const float player_height = 130; - - private const float transition_length = 800; - - private const float progress_height = 10; - - private const float bottom_black_area_height = 55; - - private Drawable background; - private ProgressBar progressBar; - - private IconButton prevButton; - private IconButton playButton; - private IconButton nextButton; - private IconButton playlistButton; - - private SpriteText title, artist; - - private PlaylistOverlay playlist; - - private LocalisationEngine localisation; - - private readonly Bindable beatmapBacking = new Bindable(); - - private Container dragContainer; - private Container playerContainer; - - public MusicController() - { - Width = 400; - Margin = new MarginPadding(10); - - // required to let MusicController handle beatmap cycling. - AlwaysPresent = true; - } - - private Vector2 dragStart; - - protected override bool OnDragStart(InputState state) - { - base.OnDragStart(state); - dragStart = state.Mouse.Position; - return true; - } - - protected override bool OnDrag(InputState state) - { - if (base.OnDrag(state)) return true; - - Vector2 change = state.Mouse.Position - dragStart; - - // Diminish the drag distance as we go further to simulate "rubber band" feeling. - change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length; - - dragContainer.MoveTo(change); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - dragContainer.MoveTo(Vector2.Zero, 800, Easing.OutElastic); - return base.OnDragEnd(state); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation) - { - this.localisation = localisation; - - Children = new Drawable[] - { - dragContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - playlist = new PlaylistOverlay - { - RelativeSizeAxes = Axes.X, - Y = player_height + 10, - }, - playerContainer = new Container - { - RelativeSizeAxes = Axes.X, - Height = player_height, - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(40), - Radius = 5, - }, - Children = new[] - { - background = new Background(), - title = new OsuSpriteText - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.TopCentre, - Position = new Vector2(0, 40), - TextSize = 25, - Colour = Color4.White, - Text = @"Nothing to play", - Font = @"Exo2.0-MediumItalic" - }, - artist = new OsuSpriteText - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Position = new Vector2(0, 45), - TextSize = 15, - Colour = Color4.White, - Text = @"Nothing to play", - Font = @"Exo2.0-BoldItalic" - }, - new Container - { - Padding = new MarginPadding { Bottom = progress_height }, - Height = bottom_black_area_height, - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Children = new[] - { - prevButton = new IconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = prev, - Icon = FontAwesome.fa_step_backward, - }, - playButton = new IconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(1.4f), - IconScale = new Vector2(1.4f), - Action = play, - Icon = FontAwesome.fa_play_circle_o, - }, - nextButton = new IconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = next, - Icon = FontAwesome.fa_step_forward, - }, - } - }, - playlistButton = new IconButton - { - Origin = Anchor.Centre, - Anchor = Anchor.CentreRight, - Position = new Vector2(-bottom_black_area_height / 2, 0), - Icon = FontAwesome.fa_bars, - Action = () => playlist.ToggleVisibility(), - }, - } - }, - progressBar = new ProgressBar - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Height = progress_height, - FillColour = colours.Yellow, - OnSeek = progress => current?.Track.Seek(progress) - } - }, - }, - } - } - }; - - beatmapBacking.BindTo(game.Beatmap); - - playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); - } - - protected override void LoadComplete() - { - beatmapBacking.ValueChanged += beatmapChanged; - beatmapBacking.DisabledChanged += beatmapDisabledChanged; - beatmapBacking.TriggerChange(); - - base.LoadComplete(); - } - - private void beatmapDisabledChanged(bool disabled) - { - if (disabled) - playlist.Hide(); - - prevButton.Enabled.Value = !disabled; - nextButton.Enabled.Value = !disabled; - playlistButton.Enabled.Value = !disabled; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - Height = dragContainer.Height; - } - - protected override void Update() - { - base.Update(); - - if (current?.TrackLoaded ?? false) - { - var track = current.Track; - - progressBar.EndTime = track.Length; - progressBar.CurrentTime = track.CurrentTime; - - playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; - - if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any()) - next(); - } - else - playButton.Icon = FontAwesome.fa_play_circle_o; - } - - private void play() - { - var track = current?.Track; - - if (track == null) - { - if (!beatmapBacking.Disabled) - playlist.PlayNext(); - return; - } - - if (track.IsRunning) - track.Stop(); - else - track.Start(); - } - - private void prev() - { - queuedDirection = TransformDirection.Prev; - playlist.PlayPrevious(); - } - - private void next() - { - queuedDirection = TransformDirection.Next; - playlist.PlayNext(); - } - - private WorkingBeatmap current; - private TransformDirection? queuedDirection; - - private void beatmapChanged(WorkingBeatmap beatmap) - { - TransformDirection direction = TransformDirection.None; - - if (current != null) - { - bool audioEquals = beatmap?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false; - - if (audioEquals) - direction = TransformDirection.None; - else if (queuedDirection.HasValue) - { - direction = queuedDirection.Value; - queuedDirection = null; - } - else - { - //figure out the best direction based on order in playlist. - var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); - var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count(); - - direction = last > next ? TransformDirection.Prev : TransformDirection.Next; - } - } - - current = beatmap; - - progressBar.CurrentTime = 0; - - updateDisplay(current, direction); - - queuedDirection = null; - } - - private ScheduledDelegate pendingBeatmapSwitch; - - private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction) - { - //we might be off-screen when this update comes in. - //rather than Scheduling, manually handle this to avoid possible memory contention. - pendingBeatmapSwitch?.Cancel(); - - pendingBeatmapSwitch = Schedule(delegate - { - // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync() - Task.Run(() => - { - if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists - { - title.Current = null; - title.Text = @"Nothing to play"; - - artist.Current = null; - artist.Text = @"Nothing to play"; - } - else - { - BeatmapMetadata metadata = beatmap.Metadata; - title.Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); - artist.Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); - } - }); - - LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground => - { - switch (direction) - { - case TransformDirection.Next: - newBackground.Position = new Vector2(400, 0); - newBackground.MoveToX(0, 500, Easing.OutCubic); - background.MoveToX(-400, 500, Easing.OutCubic); - break; - case TransformDirection.Prev: - newBackground.Position = new Vector2(-400, 0); - newBackground.MoveToX(0, 500, Easing.OutCubic); - background.MoveToX(400, 500, Easing.OutCubic); - break; - } - - background.Expire(); - background = newBackground; - - playerContainer.Add(newBackground); - }); - }); - } - - protected override void PopIn() - { - base.PopIn(); - - this.FadeIn(transition_length, Easing.OutQuint); - dragContainer.ScaleTo(1, transition_length, Easing.OutElastic); - } - - protected override void PopOut() - { - base.PopOut(); - - this.FadeOut(transition_length, Easing.OutQuint); - dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint); - } - - private enum TransformDirection - { - None, - Next, - Prev - } - - private class Background : BufferedContainer - { - private readonly Sprite sprite; - private readonly WorkingBeatmap beatmap; - - public Background(WorkingBeatmap beatmap = null) - { - this.beatmap = beatmap; - CacheDrawnFrameBuffer = true; - Depth = float.MaxValue; - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - sprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(150), - FillMode = FillMode.Fill, - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = bottom_black_area_height, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Colour = Color4.Black.Opacity(0.5f) - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using osu.Framework.Localisation; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Music; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays +{ + public class MusicController : OsuFocusedOverlayContainer + { + private const float player_height = 130; + + private const float transition_length = 800; + + private const float progress_height = 10; + + private const float bottom_black_area_height = 55; + + private Drawable background; + private ProgressBar progressBar; + + private IconButton prevButton; + private IconButton playButton; + private IconButton nextButton; + private IconButton playlistButton; + + private SpriteText title, artist; + + private PlaylistOverlay playlist; + + private LocalisationEngine localisation; + + private readonly Bindable beatmapBacking = new Bindable(); + + private Container dragContainer; + private Container playerContainer; + + public MusicController() + { + Width = 400; + Margin = new MarginPadding(10); + + // required to let MusicController handle beatmap cycling. + AlwaysPresent = true; + } + + private Vector2 dragStart; + + protected override bool OnDragStart(InputState state) + { + base.OnDragStart(state); + dragStart = state.Mouse.Position; + return true; + } + + protected override bool OnDrag(InputState state) + { + if (base.OnDrag(state)) return true; + + Vector2 change = state.Mouse.Position - dragStart; + + // Diminish the drag distance as we go further to simulate "rubber band" feeling. + change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length; + + dragContainer.MoveTo(change); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + dragContainer.MoveTo(Vector2.Zero, 800, Easing.OutElastic); + return base.OnDragEnd(state); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation) + { + this.localisation = localisation; + + Children = new Drawable[] + { + dragContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + playlist = new PlaylistOverlay + { + RelativeSizeAxes = Axes.X, + Y = player_height + 10, + }, + playerContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = player_height, + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(40), + Radius = 5, + }, + Children = new[] + { + background = new Background(), + title = new OsuSpriteText + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.TopCentre, + Position = new Vector2(0, 40), + TextSize = 25, + Colour = Color4.White, + Text = @"Nothing to play", + Font = @"Exo2.0-MediumItalic" + }, + artist = new OsuSpriteText + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Position = new Vector2(0, 45), + TextSize = 15, + Colour = Color4.White, + Text = @"Nothing to play", + Font = @"Exo2.0-BoldItalic" + }, + new Container + { + Padding = new MarginPadding { Bottom = progress_height }, + Height = bottom_black_area_height, + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new[] + { + prevButton = new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = prev, + Icon = FontAwesome.fa_step_backward, + }, + playButton = new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.4f), + IconScale = new Vector2(1.4f), + Action = play, + Icon = FontAwesome.fa_play_circle_o, + }, + nextButton = new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = next, + Icon = FontAwesome.fa_step_forward, + }, + } + }, + playlistButton = new IconButton + { + Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Position = new Vector2(-bottom_black_area_height / 2, 0), + Icon = FontAwesome.fa_bars, + Action = () => playlist.ToggleVisibility(), + }, + } + }, + progressBar = new ProgressBar + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Height = progress_height, + FillColour = colours.Yellow, + OnSeek = progress => current?.Track.Seek(progress) + } + }, + }, + } + } + }; + + beatmapBacking.BindTo(game.Beatmap); + + playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); + } + + protected override void LoadComplete() + { + beatmapBacking.ValueChanged += beatmapChanged; + beatmapBacking.DisabledChanged += beatmapDisabledChanged; + beatmapBacking.TriggerChange(); + + base.LoadComplete(); + } + + private void beatmapDisabledChanged(bool disabled) + { + if (disabled) + playlist.Hide(); + + prevButton.Enabled.Value = !disabled; + nextButton.Enabled.Value = !disabled; + playlistButton.Enabled.Value = !disabled; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + Height = dragContainer.Height; + } + + protected override void Update() + { + base.Update(); + + if (current?.TrackLoaded ?? false) + { + var track = current.Track; + + progressBar.EndTime = track.Length; + progressBar.CurrentTime = track.CurrentTime; + + playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + + if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any()) + next(); + } + else + playButton.Icon = FontAwesome.fa_play_circle_o; + } + + private void play() + { + var track = current?.Track; + + if (track == null) + { + if (!beatmapBacking.Disabled) + playlist.PlayNext(); + return; + } + + if (track.IsRunning) + track.Stop(); + else + track.Start(); + } + + private void prev() + { + queuedDirection = TransformDirection.Prev; + playlist.PlayPrevious(); + } + + private void next() + { + queuedDirection = TransformDirection.Next; + playlist.PlayNext(); + } + + private WorkingBeatmap current; + private TransformDirection? queuedDirection; + + private void beatmapChanged(WorkingBeatmap beatmap) + { + TransformDirection direction = TransformDirection.None; + + if (current != null) + { + bool audioEquals = beatmap?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false; + + if (audioEquals) + direction = TransformDirection.None; + else if (queuedDirection.HasValue) + { + direction = queuedDirection.Value; + queuedDirection = null; + } + else + { + //figure out the best direction based on order in playlist. + var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); + var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count(); + + direction = last > next ? TransformDirection.Prev : TransformDirection.Next; + } + } + + current = beatmap; + + progressBar.CurrentTime = 0; + + updateDisplay(current, direction); + + queuedDirection = null; + } + + private ScheduledDelegate pendingBeatmapSwitch; + + private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction) + { + //we might be off-screen when this update comes in. + //rather than Scheduling, manually handle this to avoid possible memory contention. + pendingBeatmapSwitch?.Cancel(); + + pendingBeatmapSwitch = Schedule(delegate + { + // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync() + Task.Run(() => + { + if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists + { + title.Current = null; + title.Text = @"Nothing to play"; + + artist.Current = null; + artist.Text = @"Nothing to play"; + } + else + { + BeatmapMetadata metadata = beatmap.Metadata; + title.Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); + artist.Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); + } + }); + + LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground => + { + switch (direction) + { + case TransformDirection.Next: + newBackground.Position = new Vector2(400, 0); + newBackground.MoveToX(0, 500, Easing.OutCubic); + background.MoveToX(-400, 500, Easing.OutCubic); + break; + case TransformDirection.Prev: + newBackground.Position = new Vector2(-400, 0); + newBackground.MoveToX(0, 500, Easing.OutCubic); + background.MoveToX(400, 500, Easing.OutCubic); + break; + } + + background.Expire(); + background = newBackground; + + playerContainer.Add(newBackground); + }); + }); + } + + protected override void PopIn() + { + base.PopIn(); + + this.FadeIn(transition_length, Easing.OutQuint); + dragContainer.ScaleTo(1, transition_length, Easing.OutElastic); + } + + protected override void PopOut() + { + base.PopOut(); + + this.FadeOut(transition_length, Easing.OutQuint); + dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint); + } + + private enum TransformDirection + { + None, + Next, + Prev + } + + private class Background : BufferedContainer + { + private readonly Sprite sprite; + private readonly WorkingBeatmap beatmap; + + public Background(WorkingBeatmap beatmap = null) + { + this.beatmap = beatmap; + CacheDrawnFrameBuffer = true; + Depth = float.MaxValue; + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + sprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(150), + FillMode = FillMode.Fill, + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = bottom_black_area_height, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Colour = Color4.Black.Opacity(0.5f) + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); + } + } + } +} diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index f5b281efc1..09b6022ac5 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -1,183 +1,183 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays.Notifications; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using System; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Threading; - -namespace osu.Game.Overlays -{ - public class NotificationOverlay : OsuFocusedOverlayContainer - { - private const float width = 320; - - public const float TRANSITION_LENGTH = 600; - - /// - /// Whether posted notifications should be processed. - /// - public readonly BindableBool Enabled = new BindableBool(true); - - private FlowContainer sections; - - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - - public NotificationOverlay() - { - ScheduledDelegate notificationsEnabler = null; - Enabled.ValueChanged += v => - { - if (!IsLoaded) - { - processingPosts = v; - return; - } - - notificationsEnabler?.Cancel(); - - if (v) - // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. - notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, 1000); - else - processingPosts = false; - }; - } - - [BackgroundDependencyLoader] - private void load() - { - Width = width; - RelativeSizeAxes = Axes.Y; - - AlwaysPresent = true; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f - }, - new OsuScrollContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Children = new[] - { - sections = new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new[] - { - new NotificationSection - { - Title = @"Notifications", - ClearText = @"Clear All", - AcceptTypes = new[] { typeof(SimpleNotification) } - }, - new NotificationSection - { - Title = @"Running Tasks", - ClearText = @"Cancel All", - AcceptTypes = new[] { typeof(ProgressNotification) } - } - } - } - } - } - }; - } - - private int totalCount => sections.Select(c => c.DisplayedCount).Sum(); - private int unreadCount => sections.Select(c => c.UnreadCount).Sum(); - - public readonly BindableInt UnreadCount = new BindableInt(); - - private int runningDepth; - - private void notificationClosed() => updateCounts(); - - private readonly Scheduler postScheduler = new Scheduler(); - - private bool processingPosts = true; - - public void Post(Notification notification) => postScheduler.Add(() => - { - ++runningDepth; - - notification.Closed += notificationClosed; - - var hasCompletionTarget = notification as IHasCompletionTarget; - if (hasCompletionTarget != null) - hasCompletionTarget.CompletionTarget = Post; - - var ourType = notification.GetType(); - - var section = sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType))); - section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth); - - State = Visibility.Visible; - - updateCounts(); - }); - - protected override void Update() - { - base.Update(); - if (processingPosts) - postScheduler.Update(); - } - - protected override void PopIn() - { - base.PopIn(); - - this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); - } - - protected override void PopOut() - { - base.PopOut(); - - markAllRead(); - - this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); - } - - private void updateCounts() - { - UnreadCount.Value = unreadCount; - } - - private void markAllRead() - { - sections.Children.ForEach(s => s.MarkAllRead()); - - updateCounts(); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Notifications; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using System; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Threading; + +namespace osu.Game.Overlays +{ + public class NotificationOverlay : OsuFocusedOverlayContainer + { + private const float width = 320; + + public const float TRANSITION_LENGTH = 600; + + /// + /// Whether posted notifications should be processed. + /// + public readonly BindableBool Enabled = new BindableBool(true); + + private FlowContainer sections; + + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + + public NotificationOverlay() + { + ScheduledDelegate notificationsEnabler = null; + Enabled.ValueChanged += v => + { + if (!IsLoaded) + { + processingPosts = v; + return; + } + + notificationsEnabler?.Cancel(); + + if (v) + // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. + notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, 1000); + else + processingPosts = false; + }; + } + + [BackgroundDependencyLoader] + private void load() + { + Width = width; + RelativeSizeAxes = Axes.Y; + + AlwaysPresent = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f + }, + new OsuScrollContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + sections = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new[] + { + new NotificationSection + { + Title = @"Notifications", + ClearText = @"Clear All", + AcceptTypes = new[] { typeof(SimpleNotification) } + }, + new NotificationSection + { + Title = @"Running Tasks", + ClearText = @"Cancel All", + AcceptTypes = new[] { typeof(ProgressNotification) } + } + } + } + } + } + }; + } + + private int totalCount => sections.Select(c => c.DisplayedCount).Sum(); + private int unreadCount => sections.Select(c => c.UnreadCount).Sum(); + + public readonly BindableInt UnreadCount = new BindableInt(); + + private int runningDepth; + + private void notificationClosed() => updateCounts(); + + private readonly Scheduler postScheduler = new Scheduler(); + + private bool processingPosts = true; + + public void Post(Notification notification) => postScheduler.Add(() => + { + ++runningDepth; + + notification.Closed += notificationClosed; + + var hasCompletionTarget = notification as IHasCompletionTarget; + if (hasCompletionTarget != null) + hasCompletionTarget.CompletionTarget = Post; + + var ourType = notification.GetType(); + + var section = sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType))); + section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth); + + State = Visibility.Visible; + + updateCounts(); + }); + + protected override void Update() + { + base.Update(); + if (processingPosts) + postScheduler.Update(); + } + + protected override void PopIn() + { + base.PopIn(); + + this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + } + + protected override void PopOut() + { + base.PopOut(); + + markAllRead(); + + this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + } + + private void updateCounts() + { + UnreadCount.Value = unreadCount; + } + + private void markAllRead() + { + sections.Children.ForEach(s => s.MarkAllRead()); + + updateCounts(); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } + } +} diff --git a/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs b/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs index ea2928607f..6b91f50aa0 100644 --- a/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs +++ b/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Overlays.Notifications -{ - public interface IHasCompletionTarget - { - Action CompletionTarget { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Overlays.Notifications +{ + public interface IHasCompletionTarget + { + Action CompletionTarget { get; set; } + } +} diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 66db57c18f..d2b5ae1829 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -1,263 +1,263 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Notifications -{ - public abstract class Notification : Container - { - /// - /// User requested close. - /// - public event Action Closed; - - /// - /// Run on user activating the notification. Return true to close. - /// - public Func Activated; - - /// - /// Should we show at the top of our section on display? - /// - public virtual bool DisplayOnTop => true; - - protected NotificationLight Light; - private readonly CloseButton closeButton; - protected Container IconContent; - private readonly Container content; - - protected override Container Content => content; - - protected Container NotificationContent; - - public virtual bool Read { get; set; } - - protected Notification() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - AddRangeInternal(new Drawable[] - { - Light = new NotificationLight - { - Margin = new MarginPadding { Right = 5 }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - }, - NotificationContent = new Container - { - CornerRadius = 8, - Masking = true, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - AutoSizeDuration = 400, - AutoSizeEasing = Easing.OutQuint, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - new Container - { - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding(5), - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - IconContent = new Container - { - Size = new Vector2(40), - Masking = true, - CornerRadius = 5, - }, - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Left = 45, - Right = 30 - }, - } - } - }, - closeButton = new CloseButton - { - Alpha = 0, - Action = Close, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding - { - Right = 5 - }, - } - } - } - }); - } - - protected override bool OnHover(InputState state) - { - closeButton.FadeIn(75); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - closeButton.FadeOut(75); - base.OnHoverLost(state); - } - - protected override bool OnClick(InputState state) - { - if (Activated?.Invoke() ?? true) - Close(); - - return true; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - this.FadeInFromZero(200); - NotificationContent.MoveToX(DrawSize.X); - NotificationContent.MoveToX(0, 500, Easing.OutQuint); - } - - public bool WasClosed; - - public virtual void Close() - { - if (WasClosed) return; - WasClosed = true; - - Closed?.Invoke(); - this.FadeOut(100); - Expire(); - } - - private class CloseButton : OsuClickableContainer - { - private Color4 hoverColour; - - public CloseButton() - { - Colour = OsuColour.Gray(0.2f); - AutoSizeAxes = Axes.Both; - - Children = new[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.fa_times_circle, - Size = new Vector2(20), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.Yellow; - } - - protected override bool OnHover(InputState state) - { - this.FadeColour(hoverColour, 200); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - this.FadeColour(OsuColour.Gray(0.2f), 200); - base.OnHoverLost(state); - } - } - - public class NotificationLight : Container - { - private bool pulsate; - private Container pulsateLayer; - - public bool Pulsate - { - get { return pulsate; } - set - { - if (pulsate == value) return; - - pulsate = value; - - pulsateLayer.ClearTransforms(); - pulsateLayer.Alpha = 1; - - if (pulsate) - { - const float length = 1000; - pulsateLayer.Loop(length / 2, - p => p.FadeTo(0.4f, length, Easing.In).Then().FadeTo(1, length, Easing.Out) - ); - } - } - } - - public new SRGBColour Colour - { - set - { - base.Colour = value; - pulsateLayer.EdgeEffect = new EdgeEffectParameters - { - Colour = ((Color4)value).Opacity(0.5f), //todo: avoid cast - Type = EdgeEffectType.Glow, - Radius = 12, - Roundness = 12, - }; - } - } - - [BackgroundDependencyLoader] - private void load() - { - Size = new Vector2(6, 15); - - Children = new[] - { - pulsateLayer = new CircularContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Masking = true, - RelativeSizeAxes = Axes.Both, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - }, - } - } - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Notifications +{ + public abstract class Notification : Container + { + /// + /// User requested close. + /// + public event Action Closed; + + /// + /// Run on user activating the notification. Return true to close. + /// + public Func Activated; + + /// + /// Should we show at the top of our section on display? + /// + public virtual bool DisplayOnTop => true; + + protected NotificationLight Light; + private readonly CloseButton closeButton; + protected Container IconContent; + private readonly Container content; + + protected override Container Content => content; + + protected Container NotificationContent; + + public virtual bool Read { get; set; } + + protected Notification() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddRangeInternal(new Drawable[] + { + Light = new NotificationLight + { + Margin = new MarginPadding { Right = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + }, + NotificationContent = new Container + { + CornerRadius = 8, + Masking = true, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = 400, + AutoSizeEasing = Easing.OutQuint, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding(5), + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + IconContent = new Container + { + Size = new Vector2(40), + Masking = true, + CornerRadius = 5, + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Left = 45, + Right = 30 + }, + } + } + }, + closeButton = new CloseButton + { + Alpha = 0, + Action = Close, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding + { + Right = 5 + }, + } + } + } + }); + } + + protected override bool OnHover(InputState state) + { + closeButton.FadeIn(75); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + closeButton.FadeOut(75); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + if (Activated?.Invoke() ?? true) + Close(); + + return true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(200); + NotificationContent.MoveToX(DrawSize.X); + NotificationContent.MoveToX(0, 500, Easing.OutQuint); + } + + public bool WasClosed; + + public virtual void Close() + { + if (WasClosed) return; + WasClosed = true; + + Closed?.Invoke(); + this.FadeOut(100); + Expire(); + } + + private class CloseButton : OsuClickableContainer + { + private Color4 hoverColour; + + public CloseButton() + { + Colour = OsuColour.Gray(0.2f); + AutoSizeAxes = Axes.Both; + + Children = new[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_times_circle, + Size = new Vector2(20), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + } + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 200); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(OsuColour.Gray(0.2f), 200); + base.OnHoverLost(state); + } + } + + public class NotificationLight : Container + { + private bool pulsate; + private Container pulsateLayer; + + public bool Pulsate + { + get { return pulsate; } + set + { + if (pulsate == value) return; + + pulsate = value; + + pulsateLayer.ClearTransforms(); + pulsateLayer.Alpha = 1; + + if (pulsate) + { + const float length = 1000; + pulsateLayer.Loop(length / 2, + p => p.FadeTo(0.4f, length, Easing.In).Then().FadeTo(1, length, Easing.Out) + ); + } + } + } + + public new SRGBColour Colour + { + set + { + base.Colour = value; + pulsateLayer.EdgeEffect = new EdgeEffectParameters + { + Colour = ((Color4)value).Opacity(0.5f), //todo: avoid cast + Type = EdgeEffectType.Glow, + Radius = 12, + Roundness = 12, + }; + } + } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(6, 15); + + Children = new[] + { + pulsateLayer = new CircularContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + }, + } + } + }; + } + } + } +} diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 533f5326e3..c166624d2b 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -1,174 +1,174 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Notifications -{ - public class NotificationSection : AlwaysUpdateFillFlowContainer - { - private OsuSpriteText titleText; - private OsuSpriteText countText; - - private ClearAllButton clearButton; - - private FlowContainer notifications; - - public int DisplayedCount => notifications.Count(n => !n.WasClosed); - public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read); - - public void Add(Notification notification, float position) - { - notifications.Add(notification); - notifications.SetLayoutPosition(notification, position); - } - - public IEnumerable AcceptTypes; - - private string clearText; - - public string ClearText - { - get { return clearText; } - set - { - clearText = value; - if (clearButton != null) clearButton.Text = clearText; - } - } - - private string title; - - public string Title - { - get { return title; } - set - { - title = value; - if (titleText != null) titleText.Text = title.ToUpper(); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - - Padding = new MarginPadding - { - Top = 10, - Bottom = 5, - Right = 20, - Left = 20, - }; - - AddRangeInternal(new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - clearButton = new ClearAllButton - { - Text = clearText, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Action = clearAll - }, - new FillFlowContainer - { - Margin = new MarginPadding - { - Bottom = 5 - }, - Spacing = new Vector2(5, 0), - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - titleText = new OsuSpriteText - { - Text = title.ToUpper(), - Font = @"Exo2.0-Black", - }, - countText = new OsuSpriteText - { - Text = "3", - Colour = colours.Yellow, - Font = @"Exo2.0-Black", - }, - } - }, - }, - }, - notifications = new AlwaysUpdateFillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - LayoutDuration = 150, - LayoutEasing = Easing.OutQuart, - Spacing = new Vector2(3), - } - }); - } - - private void clearAll() - { - notifications.Children.ForEach(c => c.Close()); - } - - protected override void Update() - { - base.Update(); - - countText.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString(); - } - - private class ClearAllButton : OsuClickableContainer - { - private readonly OsuSpriteText text; - - public ClearAllButton() - { - AutoSizeAxes = Axes.Both; - - Children = new[] - { - text = new OsuSpriteText() - }; - } - - public string Text - { - get { return text.Text; } - set { text.Text = value.ToUpper(); } - } - } - - public void MarkAllRead() - { - notifications?.Children.ForEach(n => n.Read = true); - } - } - - public class AlwaysUpdateFillFlowContainer : FillFlowContainer - where T : Drawable - { - // this is required to ensure correct layout and scheduling on children. - // the layout portion of this is being tracked as a framework issue (https://github.com/ppy/osu-framework/issues/1297). - protected override bool RequiresChildrenUpdate => true; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Notifications +{ + public class NotificationSection : AlwaysUpdateFillFlowContainer + { + private OsuSpriteText titleText; + private OsuSpriteText countText; + + private ClearAllButton clearButton; + + private FlowContainer notifications; + + public int DisplayedCount => notifications.Count(n => !n.WasClosed); + public int UnreadCount => notifications.Count(n => !n.WasClosed && !n.Read); + + public void Add(Notification notification, float position) + { + notifications.Add(notification); + notifications.SetLayoutPosition(notification, position); + } + + public IEnumerable AcceptTypes; + + private string clearText; + + public string ClearText + { + get { return clearText; } + set + { + clearText = value; + if (clearButton != null) clearButton.Text = clearText; + } + } + + private string title; + + public string Title + { + get { return title; } + set + { + title = value; + if (titleText != null) titleText.Text = title.ToUpper(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + Padding = new MarginPadding + { + Top = 10, + Bottom = 5, + Right = 20, + Left = 20, + }; + + AddRangeInternal(new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + clearButton = new ClearAllButton + { + Text = clearText, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Action = clearAll + }, + new FillFlowContainer + { + Margin = new MarginPadding + { + Bottom = 5 + }, + Spacing = new Vector2(5, 0), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + Text = title.ToUpper(), + Font = @"Exo2.0-Black", + }, + countText = new OsuSpriteText + { + Text = "3", + Colour = colours.Yellow, + Font = @"Exo2.0-Black", + }, + } + }, + }, + }, + notifications = new AlwaysUpdateFillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + LayoutDuration = 150, + LayoutEasing = Easing.OutQuart, + Spacing = new Vector2(3), + } + }); + } + + private void clearAll() + { + notifications.Children.ForEach(c => c.Close()); + } + + protected override void Update() + { + base.Update(); + + countText.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString(); + } + + private class ClearAllButton : OsuClickableContainer + { + private readonly OsuSpriteText text; + + public ClearAllButton() + { + AutoSizeAxes = Axes.Both; + + Children = new[] + { + text = new OsuSpriteText() + }; + } + + public string Text + { + get { return text.Text; } + set { text.Text = value.ToUpper(); } + } + } + + public void MarkAllRead() + { + notifications?.Children.ForEach(n => n.Read = true); + } + } + + public class AlwaysUpdateFillFlowContainer : FillFlowContainer + where T : Drawable + { + // this is required to ensure correct layout and scheduling on children. + // the layout portion of this is being tracked as a framework issue (https://github.com/ppy/osu-framework/issues/1297). + protected override bool RequiresChildrenUpdate => true; + } +} diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index df2be95b40..0711e49608 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Framework.Graphics.Colour; - -namespace osu.Game.Overlays.Notifications -{ - public class ProgressCompletionNotification : SimpleNotification - { - public ProgressCompletionNotification() - { - Icon = FontAwesome.fa_check; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IconBackgound.Colour = ColourInfo.GradientVertical(colours.GreenDark, colours.GreenLight); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Framework.Graphics.Colour; + +namespace osu.Game.Overlays.Notifications +{ + public class ProgressCompletionNotification : SimpleNotification + { + public ProgressCompletionNotification() + { + Icon = FontAwesome.fa_check; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IconBackgound.Colour = ColourInfo.GradientVertical(colours.GreenDark, colours.GreenLight); + } + } +} diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index b36ac6eebc..7a07fb970c 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -1,237 +1,237 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Notifications -{ - public class ProgressNotification : Notification, IHasCompletionTarget - { - public string Text - { - set - { - Schedule(() => textDrawable.Text = value); - } - } - - public string CompletionText { get; set; } = "Task has completed!"; - - public float Progress - { - get { return progressBar.Progress; } - set { Schedule(() => progressBar.Progress = value); } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - //we may have received changes before we were displayed. - State = state; - } - - public virtual ProgressNotificationState State - { - get { return state; } - set - { - Schedule(() => - { - bool stateChanged = state != value; - state = value; - - if (IsLoaded) - { - switch (state) - { - case ProgressNotificationState.Queued: - Light.Colour = colourQueued; - Light.Pulsate = false; - progressBar.Active = false; - break; - case ProgressNotificationState.Active: - Light.Colour = colourActive; - Light.Pulsate = true; - progressBar.Active = true; - break; - case ProgressNotificationState.Cancelled: - Light.Colour = colourCancelled; - Light.Pulsate = false; - progressBar.Active = false; - break; - } - } - - if (stateChanged) - { - switch (state) - { - case ProgressNotificationState.Completed: - NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); - this.FadeOut(200).Finally(d => Completed()); - break; - } - } - }); - } - } - - private ProgressNotificationState state; - - protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification - { - Activated = CompletionClickAction, - Text = CompletionText - }; - - protected virtual void Completed() - { - CompletionTarget?.Invoke(CreateCompletionNotification()); - base.Close(); - } - - public override bool DisplayOnTop => false; - - private readonly ProgressBar progressBar; - private Color4 colourQueued; - private Color4 colourActive; - private Color4 colourCancelled; - - private readonly TextFlowContainer textDrawable; - - public ProgressNotification() - { - IconContent.Add(new Box - { - RelativeSizeAxes = Axes.Both, - }); - - Content.Add(textDrawable = new OsuTextFlowContainer(t => - { - t.TextSize = 16; - }) - { - Colour = OsuColour.Gray(128), - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }); - - NotificationContent.Add(progressBar = new ProgressBar - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }); - - State = ProgressNotificationState.Queued; - - // don't close on click by default. - Activated = () => false; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - colourQueued = colours.YellowDark; - colourActive = colours.Blue; - colourCancelled = colours.Red; - } - - public override void Close() - { - switch (State) - { - case ProgressNotificationState.Cancelled: - base.Close(); - break; - case ProgressNotificationState.Active: - case ProgressNotificationState.Queued: - if (CancelRequested?.Invoke() != false) - State = ProgressNotificationState.Cancelled; - break; - } - } - - public Func CancelRequested { get; set; } - - /// - /// The function to post completion notifications back to. - /// - public Action CompletionTarget { get; set; } - - /// - /// An action to complete when the completion notification is clicked. - /// - public Func CompletionClickAction; - - private class ProgressBar : Container - { - private readonly Box box; - - private Color4 colourActive; - private Color4 colourInactive; - - private float progress; - public float Progress - { - get { return progress; } - set - { - if (progress == value) return; - - progress = value; - box.ResizeTo(new Vector2(progress, 1), 100, Easing.OutQuad); - } - } - - private bool active; - - public bool Active - { - get { return active; } - set - { - active = value; - this.FadeColour(active ? colourActive : colourInactive, 100); - } - } - - public ProgressBar() - { - Children = new[] - { - box = new Box - { - RelativeSizeAxes = Axes.Both, - Width = 0, - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - colourActive = colours.Blue; - Colour = colourInactive = OsuColour.Gray(0.5f); - Height = 5; - } - } - } - - public enum ProgressNotificationState - { - Queued, - Active, - Completed, - Cancelled - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Notifications +{ + public class ProgressNotification : Notification, IHasCompletionTarget + { + public string Text + { + set + { + Schedule(() => textDrawable.Text = value); + } + } + + public string CompletionText { get; set; } = "Task has completed!"; + + public float Progress + { + get { return progressBar.Progress; } + set { Schedule(() => progressBar.Progress = value); } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + //we may have received changes before we were displayed. + State = state; + } + + public virtual ProgressNotificationState State + { + get { return state; } + set + { + Schedule(() => + { + bool stateChanged = state != value; + state = value; + + if (IsLoaded) + { + switch (state) + { + case ProgressNotificationState.Queued: + Light.Colour = colourQueued; + Light.Pulsate = false; + progressBar.Active = false; + break; + case ProgressNotificationState.Active: + Light.Colour = colourActive; + Light.Pulsate = true; + progressBar.Active = true; + break; + case ProgressNotificationState.Cancelled: + Light.Colour = colourCancelled; + Light.Pulsate = false; + progressBar.Active = false; + break; + } + } + + if (stateChanged) + { + switch (state) + { + case ProgressNotificationState.Completed: + NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); + this.FadeOut(200).Finally(d => Completed()); + break; + } + } + }); + } + } + + private ProgressNotificationState state; + + protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification + { + Activated = CompletionClickAction, + Text = CompletionText + }; + + protected virtual void Completed() + { + CompletionTarget?.Invoke(CreateCompletionNotification()); + base.Close(); + } + + public override bool DisplayOnTop => false; + + private readonly ProgressBar progressBar; + private Color4 colourQueued; + private Color4 colourActive; + private Color4 colourCancelled; + + private readonly TextFlowContainer textDrawable; + + public ProgressNotification() + { + IconContent.Add(new Box + { + RelativeSizeAxes = Axes.Both, + }); + + Content.Add(textDrawable = new OsuTextFlowContainer(t => + { + t.TextSize = 16; + }) + { + Colour = OsuColour.Gray(128), + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }); + + NotificationContent.Add(progressBar = new ProgressBar + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + }); + + State = ProgressNotificationState.Queued; + + // don't close on click by default. + Activated = () => false; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + colourQueued = colours.YellowDark; + colourActive = colours.Blue; + colourCancelled = colours.Red; + } + + public override void Close() + { + switch (State) + { + case ProgressNotificationState.Cancelled: + base.Close(); + break; + case ProgressNotificationState.Active: + case ProgressNotificationState.Queued: + if (CancelRequested?.Invoke() != false) + State = ProgressNotificationState.Cancelled; + break; + } + } + + public Func CancelRequested { get; set; } + + /// + /// The function to post completion notifications back to. + /// + public Action CompletionTarget { get; set; } + + /// + /// An action to complete when the completion notification is clicked. + /// + public Func CompletionClickAction; + + private class ProgressBar : Container + { + private readonly Box box; + + private Color4 colourActive; + private Color4 colourInactive; + + private float progress; + public float Progress + { + get { return progress; } + set + { + if (progress == value) return; + + progress = value; + box.ResizeTo(new Vector2(progress, 1), 100, Easing.OutQuad); + } + } + + private bool active; + + public bool Active + { + get { return active; } + set + { + active = value; + this.FadeColour(active ? colourActive : colourInactive, 100); + } + } + + public ProgressBar() + { + Children = new[] + { + box = new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + colourActive = colours.Blue; + Colour = colourInactive = OsuColour.Gray(0.5f); + Height = 5; + } + } + } + + public enum ProgressNotificationState + { + Queued, + Active, + Completed, + Cancelled + } +} diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index c3d5a52476..a78bc8da81 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -1,93 +1,93 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Overlays.Notifications -{ - public class SimpleNotification : Notification - { - private string text = string.Empty; - public string Text - { - get { return text; } - set - { - text = value; - textDrawable.Text = text; - } - } - - private FontAwesome icon = FontAwesome.fa_info_circle; - public FontAwesome Icon - { - get { return icon; } - set - { - icon = value; - iconDrawable.Icon = icon; - } - } - - private readonly TextFlowContainer textDrawable; - private readonly SpriteIcon iconDrawable; - - protected Box IconBackgound; - - public SimpleNotification() - { - IconContent.AddRange(new Drawable[] - { - IconBackgound = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.6f)) - }, - iconDrawable = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = icon, - Size = new Vector2(20), - } - }); - - Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 16) - { - Colour = OsuColour.Gray(128), - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Text = text - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Light.Colour = colours.Green; - } - - public override bool Read - { - get - { - return base.Read; - } - - set - { - if (value == base.Read) return; - - base.Read = value; - Light.FadeTo(value ? 0 : 1, 100); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Overlays.Notifications +{ + public class SimpleNotification : Notification + { + private string text = string.Empty; + public string Text + { + get { return text; } + set + { + text = value; + textDrawable.Text = text; + } + } + + private FontAwesome icon = FontAwesome.fa_info_circle; + public FontAwesome Icon + { + get { return icon; } + set + { + icon = value; + iconDrawable.Icon = icon; + } + } + + private readonly TextFlowContainer textDrawable; + private readonly SpriteIcon iconDrawable; + + protected Box IconBackgound; + + public SimpleNotification() + { + IconContent.AddRange(new Drawable[] + { + IconBackgound = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.6f)) + }, + iconDrawable = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = icon, + Size = new Vector2(20), + } + }); + + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 16) + { + Colour = OsuColour.Gray(128), + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Text = text + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Light.Colour = colours.Green; + } + + public override bool Read + { + get + { + return base.Read; + } + + set + { + if (value == base.Read) return; + + base.Read = value; + Light.FadeTo(value ? 0 : 1, 100); + } + } + } +} diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index bbb2c476f4..3dd088891d 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -1,291 +1,291 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Configuration.Tracking; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Overlays -{ - public class OnScreenDisplay : Container - { - private readonly Container box; - - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - - private readonly SpriteText textLine1; - private readonly SpriteText textLine2; - private readonly SpriteText textLine3; - - private const float height = 110; - private const float height_contracted = height * 0.9f; - - private readonly FillFlowContainer optionLights; - - public OnScreenDisplay() - { - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - box = new Container - { - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Both, - Position = new Vector2(0.5f, 0.75f), - Masking = true, - AutoSizeAxes = Axes.X, - Height = height_contracted, - Alpha = 0, - CornerRadius = 20, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.7f, - }, - new Container // purely to add a minimum width - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 240, - RelativeSizeAxes = Axes.Y, - }, - textLine1 = new OsuSpriteText - { - Padding = new MarginPadding(10), - Font = @"Exo2.0-Black", - Spacing = new Vector2(1, 0), - TextSize = 14, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - textLine2 = new OsuSpriteText - { - TextSize = 24, - Font = @"Exo2.0-Light", - Padding = new MarginPadding { Left = 10, Right = 10 }, - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - }, - new FillFlowContainer - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - optionLights = new FillFlowContainer - { - Padding = new MarginPadding { Top = 20, Bottom = 5 }, - Spacing = new Vector2(5, 0), - Direction = FillDirection.Horizontal, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both - }, - textLine3 = new OsuSpriteText - { - Padding = new MarginPadding { Bottom = 15 }, - Font = @"Exo2.0-Bold", - TextSize = 12, - Alpha = 0.3f, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - } - } - } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfig) - { - BeginTracking(this, frameworkConfig); - } - - private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>(); - - /// - /// Registers a to have its settings tracked by this . - /// - /// The object that is registering the to be tracked. - /// The to be tracked. - /// If is null. - /// If is already being tracked from the same . - public void BeginTracking(object source, ITrackableConfigManager configManager) - { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); - - if (trackedConfigManagers.ContainsKey((source, configManager))) - throw new InvalidOperationException($"{nameof(configManager)} is already registered."); - - var trackedSettings = configManager.CreateTrackedSettings(); - if (trackedSettings == null) - return; - - configManager.LoadInto(trackedSettings); - trackedSettings.SettingChanged += display; - - trackedConfigManagers.Add((source, configManager), trackedSettings); - } - - /// - /// Unregisters a from having its settings tracked by this . - /// - /// The object that registered the to be tracked. - /// The that is being tracked. - /// If is null. - /// If is not being tracked from the same . - public void StopTracking(object source, ITrackableConfigManager configManager) - { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); - - if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing)) - throw new InvalidOperationException($"{nameof(configManager)} is not registered."); - - existing.Unload(); - existing.SettingChanged -= display; - - trackedConfigManagers.Remove((source, configManager)); - } - - private void display(SettingDescription description) - { - Schedule(() => - { - textLine1.Text = description.Name.ToUpper(); - textLine2.Text = description.Value; - textLine3.Text = description.Shortcut.ToUpper(); - - box.Animate( - b => b.FadeIn(500, Easing.OutQuint), - b => b.ResizeHeightTo(height, 500, Easing.OutQuint) - ).Then( - b => b.FadeOutFromOne(1500, Easing.InQuint), - b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint) - ); - - int optionCount = 0; - int selectedOption = -1; - - if (description.RawValue is bool) - { - optionCount = 1; - if ((bool)description.RawValue) selectedOption = 0; - } - else if (description.RawValue is Enum) - { - var values = Enum.GetValues(description.RawValue.GetType()); - optionCount = values.Length; - selectedOption = Convert.ToInt32(description.RawValue); - } - - textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; - textLine2.Y = optionCount > 0 ? 0 : 5; - - if (optionLights.Children.Count != optionCount) - { - optionLights.Clear(); - for (int i = 0; i < optionCount; i++) - optionLights.Add(new OptionLight()); - } - - for (int i = 0; i < optionCount; i++) - optionLights.Children[i].Glowing = i == selectedOption; - }); - } - - private class OptionLight : Container - { - private Color4 glowingColour, idleColour; - - private const float transition_speed = 300; - - private const float glow_strength = 0.4f; - - private readonly Box fill; - - public OptionLight() - { - Children = new[] - { - fill = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - }, - }; - } - - private bool glowing; - - public bool Glowing - { - set - { - glowing = value; - if (!IsLoaded) return; - - updateGlow(); - } - } - - private void updateGlow() - { - if (glowing) - { - fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint); - FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint); - } - else - { - FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint); - fill.FadeColour(idleColour, transition_speed, Easing.OutQuint); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - fill.Colour = idleColour = Color4.White.Opacity(0.4f); - glowingColour = Color4.White; - - Size = new Vector2(25, 5); - - Masking = true; - CornerRadius = 3; - - EdgeEffect = new EdgeEffectParameters - { - Colour = colours.BlueDark.Opacity(glow_strength), - Type = EdgeEffectType.Glow, - Radius = 8, - }; - } - - protected override void LoadComplete() - { - updateGlow(); - FinishTransforms(true); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Configuration.Tracking; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays +{ + public class OnScreenDisplay : Container + { + private readonly Container box; + + public override bool HandleKeyboardInput => false; + public override bool HandleMouseInput => false; + + private readonly SpriteText textLine1; + private readonly SpriteText textLine2; + private readonly SpriteText textLine3; + + private const float height = 110; + private const float height_contracted = height * 0.9f; + + private readonly FillFlowContainer optionLights; + + public OnScreenDisplay() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + box = new Container + { + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + Position = new Vector2(0.5f, 0.75f), + Masking = true, + AutoSizeAxes = Axes.X, + Height = height_contracted, + Alpha = 0, + CornerRadius = 20, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.7f, + }, + new Container // purely to add a minimum width + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 240, + RelativeSizeAxes = Axes.Y, + }, + textLine1 = new OsuSpriteText + { + Padding = new MarginPadding(10), + Font = @"Exo2.0-Black", + Spacing = new Vector2(1, 0), + TextSize = 14, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + textLine2 = new OsuSpriteText + { + TextSize = 24, + Font = @"Exo2.0-Light", + Padding = new MarginPadding { Left = 10, Right = 10 }, + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + optionLights = new FillFlowContainer + { + Padding = new MarginPadding { Top = 20, Bottom = 5 }, + Spacing = new Vector2(5, 0), + Direction = FillDirection.Horizontal, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both + }, + textLine3 = new OsuSpriteText + { + Padding = new MarginPadding { Bottom = 15 }, + Font = @"Exo2.0-Bold", + TextSize = 12, + Alpha = 0.3f, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + } + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfig) + { + BeginTracking(this, frameworkConfig); + } + + private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>(); + + /// + /// Registers a to have its settings tracked by this . + /// + /// The object that is registering the to be tracked. + /// The to be tracked. + /// If is null. + /// If is already being tracked from the same . + public void BeginTracking(object source, ITrackableConfigManager configManager) + { + if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + + if (trackedConfigManagers.ContainsKey((source, configManager))) + throw new InvalidOperationException($"{nameof(configManager)} is already registered."); + + var trackedSettings = configManager.CreateTrackedSettings(); + if (trackedSettings == null) + return; + + configManager.LoadInto(trackedSettings); + trackedSettings.SettingChanged += display; + + trackedConfigManagers.Add((source, configManager), trackedSettings); + } + + /// + /// Unregisters a from having its settings tracked by this . + /// + /// The object that registered the to be tracked. + /// The that is being tracked. + /// If is null. + /// If is not being tracked from the same . + public void StopTracking(object source, ITrackableConfigManager configManager) + { + if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + + if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing)) + throw new InvalidOperationException($"{nameof(configManager)} is not registered."); + + existing.Unload(); + existing.SettingChanged -= display; + + trackedConfigManagers.Remove((source, configManager)); + } + + private void display(SettingDescription description) + { + Schedule(() => + { + textLine1.Text = description.Name.ToUpper(); + textLine2.Text = description.Value; + textLine3.Text = description.Shortcut.ToUpper(); + + box.Animate( + b => b.FadeIn(500, Easing.OutQuint), + b => b.ResizeHeightTo(height, 500, Easing.OutQuint) + ).Then( + b => b.FadeOutFromOne(1500, Easing.InQuint), + b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint) + ); + + int optionCount = 0; + int selectedOption = -1; + + if (description.RawValue is bool) + { + optionCount = 1; + if ((bool)description.RawValue) selectedOption = 0; + } + else if (description.RawValue is Enum) + { + var values = Enum.GetValues(description.RawValue.GetType()); + optionCount = values.Length; + selectedOption = Convert.ToInt32(description.RawValue); + } + + textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; + textLine2.Y = optionCount > 0 ? 0 : 5; + + if (optionLights.Children.Count != optionCount) + { + optionLights.Clear(); + for (int i = 0; i < optionCount; i++) + optionLights.Add(new OptionLight()); + } + + for (int i = 0; i < optionCount; i++) + optionLights.Children[i].Glowing = i == selectedOption; + }); + } + + private class OptionLight : Container + { + private Color4 glowingColour, idleColour; + + private const float transition_speed = 300; + + private const float glow_strength = 0.4f; + + private readonly Box fill; + + public OptionLight() + { + Children = new[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 1, + }, + }; + } + + private bool glowing; + + public bool Glowing + { + set + { + glowing = value; + if (!IsLoaded) return; + + updateGlow(); + } + } + + private void updateGlow() + { + if (glowing) + { + fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint); + FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint); + } + else + { + FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint); + fill.FadeColour(idleColour, transition_speed, Easing.OutQuint); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + fill.Colour = idleColour = Color4.White.Opacity(0.4f); + glowingColour = Color4.White; + + Size = new Vector2(25, 5); + + Masking = true; + CornerRadius = 3; + + EdgeEffect = new EdgeEffectParameters + { + Colour = colours.BlueDark.Opacity(glow_strength), + Type = EdgeEffectType.Glow, + Radius = 8, + }; + } + + protected override void LoadComplete() + { + updateGlow(); + FinishTransforms(true); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index f4b363cd91..4a88431cc4 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -1,499 +1,499 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Diagnostics; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; - -namespace osu.Game.Overlays.Profile -{ - public class ProfileHeader : Container - { - private readonly OsuTextFlowContainer infoTextLeft; - private readonly LinkFlowContainer infoTextRight; - private readonly FillFlowContainer scoreText, scoreNumberText; - private readonly RankGraph rankGraph; - - public readonly SupporterIcon SupporterTag; - private readonly Container coverContainer; - private readonly Sprite levelBadge; - private readonly SpriteText levelText; - private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; - private readonly Box colourBar; - private readonly DrawableFlag countryFlag; - - private const float cover_height = 350; - private const float info_height = 150; - private const float info_width = 220; - private const float avatar_size = 110; - private const float level_position = 30; - private const float level_height = 60; - - public ProfileHeader(User user) - { - RelativeSizeAxes = Axes.X; - Height = cover_height + info_height; - - Children = new Drawable[] - { - coverContainer = new Container - { - RelativeSizeAxes = Axes.X, - Height = cover_height, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = UserProfileOverlay.CONTENT_X_MARGIN, - Y = -20, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new UpdateableAvatar - { - User = user, - Size = new Vector2(avatar_size), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = avatar_size + 10, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - SupporterTag = new SupporterIcon - { - Alpha = 0, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -75, - Size = new Vector2(25, 25) - }, - new ProfileLink(user) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -48, - }, - countryFlag = new DrawableFlag(user.Country) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Width = 30, - Height = 20 - } - } - } - } - }, - colourBar = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = UserProfileOverlay.CONTENT_X_MARGIN, - Height = 5, - Width = info_width, - Alpha = 0 - } - } - }, - infoTextLeft = new OsuTextFlowContainer(t => t.TextSize = 14) - { - X = UserProfileOverlay.CONTENT_X_MARGIN, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - infoTextRight = new LinkFlowContainer(t => - { - t.TextSize = 14; - t.Font = @"Exo2.0-RegularItalic"; - }) - { - X = UserProfileOverlay.CONTENT_X_MARGIN + info_width + 20, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - new Container - { - X = -UserProfileOverlay.CONTENT_X_MARGIN, - RelativeSizeAxes = Axes.Y, - Width = 280, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Y = level_position, - Height = level_height, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - levelBadge = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Height = 50, - Width = 50, - Alpha = 0 - }, - levelText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 11, - TextSize = 20 - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Y = cover_height, - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Height = cover_height - level_height - level_position - 5, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - scoreText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - scoreNumberText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -64, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSSPlus = new GradeBadge("SSPlus") { Alpha = 0 }, - gradeSS = new GradeBadge("SS") { Alpha = 0 }, - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -18, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSPlus = new GradeBadge("SPlus") { Alpha = 0 }, - gradeS = new GradeBadge("S") { Alpha = 0 }, - gradeA = new GradeBadge("A") { Alpha = 0 }, - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = info_height - 15, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.25f), - RelativeSizeAxes = Axes.Both - }, - rankGraph = new RankGraph - { - RelativeSizeAxes = Axes.Both - } - } - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - levelBadge.Texture = textures.Get(@"Profile/levelbadge"); - } - - private User user; - - public User User - { - get { return user; } - set - { - user = value; - loadUser(); - } - } - - private void loadUser() - { - LoadComponentAsync(new UserCoverBackground(user) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), - Depth = float.MaxValue, - }, coverContainer.Add); - - if (user.IsSupporter) - SupporterTag.Show(); - - if (!string.IsNullOrEmpty(user.Colour)) - { - colourBar.Colour = OsuColour.FromHex(user.Colour); - colourBar.Show(); - } - - void boldItalic(SpriteText t) => t.Font = @"Exo2.0-BoldItalic"; - void lightText(SpriteText t) => t.Alpha = 0.8f; - - OsuSpriteText createScoreText(string text) => new OsuSpriteText - { - TextSize = 14, - Text = text - }; - - OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = text - }; - - if (user.Age != null) - { - infoTextLeft.AddText($"{user.Age} years old ", boldItalic); - } - - if (user.Country != null) - { - infoTextLeft.AddText("from ", lightText); - infoTextLeft.AddText(user.Country.FullName, boldItalic); - countryFlag.Country = user.Country; - } - - infoTextLeft.NewParagraph(); - - if (user.JoinDate.ToUniversalTime().Year < 2008) - { - infoTextLeft.AddText("Here since the beginning", boldItalic); - } - else - { - infoTextLeft.AddText("Joined ", lightText); - infoTextLeft.AddText(new DrawableDate(user.JoinDate), boldItalic); - } - - infoTextLeft.NewLine(); - infoTextLeft.AddText("Last seen ", lightText); - infoTextLeft.AddText(new DrawableDate(user.LastVisit), boldItalic); - infoTextLeft.NewParagraph(); - - if (user.PlayStyle?.Length > 0) - { - infoTextLeft.AddText("Plays with ", lightText); - infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); - } - - string websiteWithoutProtcol = user.Website; - if (!string.IsNullOrEmpty(websiteWithoutProtcol)) - { - int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); - if (protocolIndex >= 0) - websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); - } - - tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); - tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); - tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); - infoTextRight.NewParagraph(); - if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfoRightLine(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); - tryAddInfoRightLine(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - - if (user.Statistics != null) - { - levelBadge.Show(); - levelText.Text = user.Statistics.Level.Current.ToString(); - - scoreText.Add(createScoreText("Ranked Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); - scoreText.Add(createScoreText("Play Count")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Hits")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalHits.ToString(@"#,0"))); - scoreText.Add(createScoreText("Max Combo")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.MaxCombo.ToString(@"#,0"))); - scoreText.Add(createScoreText("Replays Watched by Others")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplaysWatched.ToString(@"#,0"))); - - gradeSSPlus.DisplayCount = user.Statistics.GradesCount.SSPlus; - gradeSSPlus.Show(); - gradeSS.DisplayCount = user.Statistics.GradesCount.SS; - gradeSS.Show(); - gradeSPlus.DisplayCount = user.Statistics.GradesCount.SPlus; - gradeSPlus.Show(); - gradeS.DisplayCount = user.Statistics.GradesCount.S; - gradeS.Show(); - gradeA.DisplayCount = user.Statistics.GradesCount.A; - gradeA.Show(); - - rankGraph.User.Value = user; - } - } - - private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) - { - if (string.IsNullOrEmpty(str)) return; - - infoTextRight.AddIcon(icon); - if (url != null) - { - infoTextRight.AddLink(" " + str, url); - } - else - { - infoTextRight.AddText(" " + str); - } - - infoTextRight.NewLine(); - } - - private class ProfileLink : OsuHoverContainer, IHasTooltip - { - public string TooltipText => "View Profile in Browser"; - - public override bool HandleMouseInput => true; - - public ProfileLink(User user) - { - Action = () => Process.Start($@"https://osu.ppy.sh/users/{user.Id}"); - - AutoSizeAxes = Axes.Both; - - Child = new OsuSpriteText - { - Text = user.Username, - Font = @"Exo2.0-RegularItalic", - TextSize = 30, - }; - } - } - - - private class GradeBadge : Container - { - private const float width = 50; - private readonly string grade; - private readonly Sprite badge; - private readonly SpriteText numberText; - - public int DisplayCount - { - set { numberText.Text = value.ToString(@"#,0"); } - } - - public GradeBadge(string grade) - { - this.grade = grade; - Width = width; - Height = 41; - Add(badge = new Sprite - { - Width = width, - Height = 26 - }); - Add(numberText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - TextSize = 14, - Font = @"Exo2.0-Bold" - }); - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - badge.Texture = textures.Get($"Grades/{grade}"); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Diagnostics; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile +{ + public class ProfileHeader : Container + { + private readonly OsuTextFlowContainer infoTextLeft; + private readonly LinkFlowContainer infoTextRight; + private readonly FillFlowContainer scoreText, scoreNumberText; + private readonly RankGraph rankGraph; + + public readonly SupporterIcon SupporterTag; + private readonly Container coverContainer; + private readonly Sprite levelBadge; + private readonly SpriteText levelText; + private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; + private readonly Box colourBar; + private readonly DrawableFlag countryFlag; + + private const float cover_height = 350; + private const float info_height = 150; + private const float info_width = 220; + private const float avatar_size = 110; + private const float level_position = 30; + private const float level_height = 60; + + public ProfileHeader(User user) + { + RelativeSizeAxes = Axes.X; + Height = cover_height + info_height; + + Children = new Drawable[] + { + coverContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = cover_height, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = UserProfileOverlay.CONTENT_X_MARGIN, + Y = -20, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new UpdateableAvatar + { + User = user, + Size = new Vector2(avatar_size), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = avatar_size + 10, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + SupporterTag = new SupporterIcon + { + Alpha = 0, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Y = -75, + Size = new Vector2(25, 25) + }, + new ProfileLink(user) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Y = -48, + }, + countryFlag = new DrawableFlag(user.Country) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Width = 30, + Height = 20 + } + } + } + } + }, + colourBar = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = UserProfileOverlay.CONTENT_X_MARGIN, + Height = 5, + Width = info_width, + Alpha = 0 + } + } + }, + infoTextLeft = new OsuTextFlowContainer(t => t.TextSize = 14) + { + X = UserProfileOverlay.CONTENT_X_MARGIN, + Y = cover_height + 20, + Width = info_width, + AutoSizeAxes = Axes.Y, + ParagraphSpacing = 0.8f, + LineSpacing = 0.2f + }, + infoTextRight = new LinkFlowContainer(t => + { + t.TextSize = 14; + t.Font = @"Exo2.0-RegularItalic"; + }) + { + X = UserProfileOverlay.CONTENT_X_MARGIN + info_width + 20, + Y = cover_height + 20, + Width = info_width, + AutoSizeAxes = Axes.Y, + ParagraphSpacing = 0.8f, + LineSpacing = 0.2f + }, + new Container + { + X = -UserProfileOverlay.CONTENT_X_MARGIN, + RelativeSizeAxes = Axes.Y, + Width = 280, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Y = level_position, + Height = level_height, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black.Opacity(0.5f), + RelativeSizeAxes = Axes.Both + }, + levelBadge = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 50, + Width = 50, + Alpha = 0 + }, + levelText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 11, + TextSize = 20 + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + Y = cover_height, + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + Height = cover_height - level_height - level_position - 5, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black.Opacity(0.5f), + RelativeSizeAxes = Axes.Both + }, + scoreText = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, + Spacing = new Vector2(0, 2) + }, + scoreNumberText = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, + Spacing = new Vector2(0, 2) + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -64, + Spacing = new Vector2(20, 0), + Children = new[] + { + gradeSSPlus = new GradeBadge("SSPlus") { Alpha = 0 }, + gradeSS = new GradeBadge("SS") { Alpha = 0 }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -18, + Spacing = new Vector2(20, 0), + Children = new[] + { + gradeSPlus = new GradeBadge("SPlus") { Alpha = 0 }, + gradeS = new GradeBadge("S") { Alpha = 0 }, + gradeA = new GradeBadge("A") { Alpha = 0 }, + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = info_height - 15, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black.Opacity(0.25f), + RelativeSizeAxes = Axes.Both + }, + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + levelBadge.Texture = textures.Get(@"Profile/levelbadge"); + } + + private User user; + + public User User + { + get { return user; } + set + { + user = value; + loadUser(); + } + } + + private void loadUser() + { + LoadComponentAsync(new UserCoverBackground(user) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + OnLoadComplete = d => d.FadeInFromZero(200), + Depth = float.MaxValue, + }, coverContainer.Add); + + if (user.IsSupporter) + SupporterTag.Show(); + + if (!string.IsNullOrEmpty(user.Colour)) + { + colourBar.Colour = OsuColour.FromHex(user.Colour); + colourBar.Show(); + } + + void boldItalic(SpriteText t) => t.Font = @"Exo2.0-BoldItalic"; + void lightText(SpriteText t) => t.Alpha = 0.8f; + + OsuSpriteText createScoreText(string text) => new OsuSpriteText + { + TextSize = 14, + Text = text + }; + + OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText + { + TextSize = 14, + Font = @"Exo2.0-Bold", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = text + }; + + if (user.Age != null) + { + infoTextLeft.AddText($"{user.Age} years old ", boldItalic); + } + + if (user.Country != null) + { + infoTextLeft.AddText("from ", lightText); + infoTextLeft.AddText(user.Country.FullName, boldItalic); + countryFlag.Country = user.Country; + } + + infoTextLeft.NewParagraph(); + + if (user.JoinDate.ToUniversalTime().Year < 2008) + { + infoTextLeft.AddText("Here since the beginning", boldItalic); + } + else + { + infoTextLeft.AddText("Joined ", lightText); + infoTextLeft.AddText(new DrawableDate(user.JoinDate), boldItalic); + } + + infoTextLeft.NewLine(); + infoTextLeft.AddText("Last seen ", lightText); + infoTextLeft.AddText(new DrawableDate(user.LastVisit), boldItalic); + infoTextLeft.NewParagraph(); + + if (user.PlayStyle?.Length > 0) + { + infoTextLeft.AddText("Plays with ", lightText); + infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); + } + + string websiteWithoutProtcol = user.Website; + if (!string.IsNullOrEmpty(websiteWithoutProtcol)) + { + int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); + if (protocolIndex >= 0) + websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); + } + + tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); + tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); + tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); + infoTextRight.NewParagraph(); + if (!string.IsNullOrEmpty(user.Twitter)) + tryAddInfoRightLine(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); + tryAddInfoRightLine(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + + if (user.Statistics != null) + { + levelBadge.Show(); + levelText.Text = user.Statistics.Level.Current.ToString(); + + scoreText.Add(createScoreText("Ranked Score")); + scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); + scoreText.Add(createScoreText("Accuracy")); + scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); + scoreText.Add(createScoreText("Play Count")); + scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); + scoreText.Add(createScoreText("Total Score")); + scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalScore.ToString(@"#,0"))); + scoreText.Add(createScoreText("Total Hits")); + scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalHits.ToString(@"#,0"))); + scoreText.Add(createScoreText("Max Combo")); + scoreNumberText.Add(createScoreNumberText(user.Statistics.MaxCombo.ToString(@"#,0"))); + scoreText.Add(createScoreText("Replays Watched by Others")); + scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplaysWatched.ToString(@"#,0"))); + + gradeSSPlus.DisplayCount = user.Statistics.GradesCount.SSPlus; + gradeSSPlus.Show(); + gradeSS.DisplayCount = user.Statistics.GradesCount.SS; + gradeSS.Show(); + gradeSPlus.DisplayCount = user.Statistics.GradesCount.SPlus; + gradeSPlus.Show(); + gradeS.DisplayCount = user.Statistics.GradesCount.S; + gradeS.Show(); + gradeA.DisplayCount = user.Statistics.GradesCount.A; + gradeA.Show(); + + rankGraph.User.Value = user; + } + } + + private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) + { + if (string.IsNullOrEmpty(str)) return; + + infoTextRight.AddIcon(icon); + if (url != null) + { + infoTextRight.AddLink(" " + str, url); + } + else + { + infoTextRight.AddText(" " + str); + } + + infoTextRight.NewLine(); + } + + private class ProfileLink : OsuHoverContainer, IHasTooltip + { + public string TooltipText => "View Profile in Browser"; + + public override bool HandleMouseInput => true; + + public ProfileLink(User user) + { + Action = () => Process.Start($@"https://osu.ppy.sh/users/{user.Id}"); + + AutoSizeAxes = Axes.Both; + + Child = new OsuSpriteText + { + Text = user.Username, + Font = @"Exo2.0-RegularItalic", + TextSize = 30, + }; + } + } + + + private class GradeBadge : Container + { + private const float width = 50; + private readonly string grade; + private readonly Sprite badge; + private readonly SpriteText numberText; + + public int DisplayCount + { + set { numberText.Text = value.ToString(@"#,0"); } + } + + public GradeBadge(string grade) + { + this.grade = grade; + Width = width; + Height = 41; + Add(badge = new Sprite + { + Width = width, + Height = 26 + }); + Add(numberText = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + TextSize = 14, + Font = @"Exo2.0-Bold" + }); + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + badge.Texture = textures.Get($"Grades/{grade}"); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 1c23fa575e..c408f69cd7 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -1,79 +1,79 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using OpenTK.Graphics; -using osu.Framework.Configuration; - -namespace osu.Game.Overlays.Profile -{ - public abstract class ProfileSection : FillFlowContainer - { - public abstract string Title { get; } - - public abstract string Identifier { get; } - - private readonly FillFlowContainer content; - - protected override Container Content => content; - - public readonly Bindable User = new Bindable(); - - protected ProfileSection() - { - Direction = FillDirection.Vertical; - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - InternalChildren = new Drawable[] - { - new OsuSpriteText - { - Text = Title, - TextSize = 20, - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding - { - Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10 - } - }, - content = new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding - { - Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, - Bottom = 20 - } - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(34), - EdgeSmoothness = new Vector2(1) - } - }; - - // placeholder - Add(new OsuSpriteText - { - Text = @"coming soon!", - TextSize = 16, - Font = @"Exo2.0-Medium", - Colour = Color4.Gray, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Top = 100, Bottom = 100 } - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using OpenTK.Graphics; +using osu.Framework.Configuration; + +namespace osu.Game.Overlays.Profile +{ + public abstract class ProfileSection : FillFlowContainer + { + public abstract string Title { get; } + + public abstract string Identifier { get; } + + private readonly FillFlowContainer content; + + protected override Container Content => content; + + public readonly Bindable User = new Bindable(); + + protected ProfileSection() + { + Direction = FillDirection.Vertical; + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Text = Title, + TextSize = 20, + Font = @"Exo2.0-RegularItalic", + Margin = new MarginPadding + { + Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, + Vertical = 10 + } + }, + content = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding + { + Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, + Bottom = 20 + } + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = OsuColour.Gray(34), + EdgeSmoothness = new Vector2(1) + } + }; + + // placeholder + Add(new OsuSpriteText + { + Text = @"coming soon!", + TextSize = 16, + Font = @"Exo2.0-Medium", + Colour = Color4.Gray, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Top = 100, Bottom = 100 } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/RankGraph.cs b/osu.Game/Overlays/Profile/RankGraph.cs index 369bdee65f..72dd4352f6 100644 --- a/osu.Game/Overlays/Profile/RankGraph.cs +++ b/osu.Game/Overlays/Profile/RankGraph.cs @@ -1,219 +1,219 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Users; -using System.Collections.Generic; -using osu.Framework.Configuration; - -namespace osu.Game.Overlays.Profile -{ - public class RankGraph : Container - { - private const float primary_textsize = 25; - private const float secondary_textsize = 13; - private const float padding = 10; - private const float fade_duration = 150; - private const int ranked_days = 88; - - private readonly SpriteText rankText, performanceText, relativeText; - private readonly RankChartLineGraph graph; - private readonly OsuSpriteText placeholder; - - private KeyValuePair[] ranks; - public Bindable User = new Bindable(); - - public RankGraph() - { - Padding = new MarginPadding { Vertical = padding }; - Children = new Drawable[] - { - placeholder = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "No recent plays", - TextSize = 14, - Font = @"Exo2.0-RegularItalic", - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = @"Exo2.0-RegularItalic", - TextSize = primary_textsize - }, - relativeText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = @"Exo2.0-RegularItalic", - Y = 25, - TextSize = secondary_textsize - }, - performanceText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = @"Exo2.0-RegularItalic", - TextSize = secondary_textsize - }, - graph = new RankChartLineGraph - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 75, - Y = -secondary_textsize, - Alpha = 0, - } - }; - - graph.OnBallMove += showHistoryRankTexts; - - User.ValueChanged += userChanged; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - graph.Colour = colours.Yellow; - } - - private void userChanged(User user) - { - placeholder.FadeIn(fade_duration, Easing.Out); - - if (user?.Statistics?.Ranks.Global == null) - { - rankText.Text = string.Empty; - performanceText.Text = string.Empty; - relativeText.Text = string.Empty; - graph.FadeOut(fade_duration, Easing.Out); - ranks = null; - return; - } - - int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Ranks.Global.Value }; - ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); - - if (ranks.Length > 1) - { - placeholder.FadeOut(fade_duration, Easing.Out); - - graph.DefaultValueCount = ranks.Length; - graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); - graph.SetStaticBallPosition(); - } - - graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); - - updateRankTexts(); - } - - private void updateRankTexts() - { - var user = User.Value; - - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - rankText.Text = user.Statistics.Ranks.Global > 0 ? $"#{user.Statistics.Ranks.Global:#,0}" : "no rank"; - relativeText.Text = user.Country != null && user.Statistics.Ranks.Country > 0 ? $"{user.Country.FullName} #{user.Statistics.Ranks.Country:#,0}" : "no rank"; - } - - private void showHistoryRankTexts(int dayIndex) - { - rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; - relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; - } - - protected override bool OnHover(InputState state) - { - if (ranks?.Length > 1) - { - graph.UpdateBallPosition(state.Mouse.Position.X); - graph.ShowBall(); - } - return base.OnHover(state); - } - - protected override bool OnMouseMove(InputState state) - { - if (ranks?.Length > 1) - graph.UpdateBallPosition(state.Mouse.Position.X); - - return base.OnMouseMove(state); - } - - protected override void OnHoverLost(InputState state) - { - if (ranks?.Length > 1) - { - graph.HideBall(); - updateRankTexts(); - } - - base.OnHoverLost(state); - } - - private class RankChartLineGraph : LineGraph - { - private readonly CircularContainer staticBall; - private readonly CircularContainer movingBall; - - public Action OnBallMove; - - public RankChartLineGraph() - { - Add(staticBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - Add(movingBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Alpha = 0, - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - } - - public void SetStaticBallPosition() => staticBall.Position = new Vector2(1, GetYPosition(Values.Last())); - - public void UpdateBallPosition(float mouseXPosition) - { - int index = calculateIndex(mouseXPosition); - movingBall.Position = calculateBallPosition(index); - OnBallMove.Invoke(index); - } - - public void ShowBall() => movingBall.FadeIn(fade_duration); - - public void HideBall() => movingBall.FadeOut(fade_duration); - - private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); - - private Vector2 calculateBallPosition(int index) - { - float y = GetYPosition(Values.ElementAt(index)); - return new Vector2(index / (float)(DefaultValueCount - 1), y); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using System.Collections.Generic; +using osu.Framework.Configuration; + +namespace osu.Game.Overlays.Profile +{ + public class RankGraph : Container + { + private const float primary_textsize = 25; + private const float secondary_textsize = 13; + private const float padding = 10; + private const float fade_duration = 150; + private const int ranked_days = 88; + + private readonly SpriteText rankText, performanceText, relativeText; + private readonly RankChartLineGraph graph; + private readonly OsuSpriteText placeholder; + + private KeyValuePair[] ranks; + public Bindable User = new Bindable(); + + public RankGraph() + { + Padding = new MarginPadding { Vertical = padding }; + Children = new Drawable[] + { + placeholder = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "No recent plays", + TextSize = 14, + Font = @"Exo2.0-RegularItalic", + }, + rankText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = @"Exo2.0-RegularItalic", + TextSize = primary_textsize + }, + relativeText = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = @"Exo2.0-RegularItalic", + Y = 25, + TextSize = secondary_textsize + }, + performanceText = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = @"Exo2.0-RegularItalic", + TextSize = secondary_textsize + }, + graph = new RankChartLineGraph + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 75, + Y = -secondary_textsize, + Alpha = 0, + } + }; + + graph.OnBallMove += showHistoryRankTexts; + + User.ValueChanged += userChanged; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + graph.Colour = colours.Yellow; + } + + private void userChanged(User user) + { + placeholder.FadeIn(fade_duration, Easing.Out); + + if (user?.Statistics?.Ranks.Global == null) + { + rankText.Text = string.Empty; + performanceText.Text = string.Empty; + relativeText.Text = string.Empty; + graph.FadeOut(fade_duration, Easing.Out); + ranks = null; + return; + } + + int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Ranks.Global.Value }; + ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + + if (ranks.Length > 1) + { + placeholder.FadeOut(fade_duration, Easing.Out); + + graph.DefaultValueCount = ranks.Length; + graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); + graph.SetStaticBallPosition(); + } + + graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + + updateRankTexts(); + } + + private void updateRankTexts() + { + var user = User.Value; + + performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; + rankText.Text = user.Statistics.Ranks.Global > 0 ? $"#{user.Statistics.Ranks.Global:#,0}" : "no rank"; + relativeText.Text = user.Country != null && user.Statistics.Ranks.Country > 0 ? $"{user.Country.FullName} #{user.Statistics.Ranks.Country:#,0}" : "no rank"; + } + + private void showHistoryRankTexts(int dayIndex) + { + rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; + relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; + } + + protected override bool OnHover(InputState state) + { + if (ranks?.Length > 1) + { + graph.UpdateBallPosition(state.Mouse.Position.X); + graph.ShowBall(); + } + return base.OnHover(state); + } + + protected override bool OnMouseMove(InputState state) + { + if (ranks?.Length > 1) + graph.UpdateBallPosition(state.Mouse.Position.X); + + return base.OnMouseMove(state); + } + + protected override void OnHoverLost(InputState state) + { + if (ranks?.Length > 1) + { + graph.HideBall(); + updateRankTexts(); + } + + base.OnHoverLost(state); + } + + private class RankChartLineGraph : LineGraph + { + private readonly CircularContainer staticBall; + private readonly CircularContainer movingBall; + + public Action OnBallMove; + + public RankChartLineGraph() + { + Add(staticBall = new CircularContainer + { + Origin = Anchor.Centre, + Size = new Vector2(8), + Masking = true, + RelativePositionAxes = Axes.Both, + Child = new Box { RelativeSizeAxes = Axes.Both } + }); + Add(movingBall = new CircularContainer + { + Origin = Anchor.Centre, + Size = new Vector2(8), + Alpha = 0, + Masking = true, + RelativePositionAxes = Axes.Both, + Child = new Box { RelativeSizeAxes = Axes.Both } + }); + } + + public void SetStaticBallPosition() => staticBall.Position = new Vector2(1, GetYPosition(Values.Last())); + + public void UpdateBallPosition(float mouseXPosition) + { + int index = calculateIndex(mouseXPosition); + movingBall.Position = calculateBallPosition(index); + OnBallMove.Invoke(index); + } + + public void ShowBall() => movingBall.FadeIn(fade_duration); + + public void HideBall() => movingBall.FadeOut(fade_duration); + + private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); + + private Vector2 calculateBallPosition(int index) + { + float y = GetYPosition(Values.ElementAt(index)); + return new Vector2(index / (float)(DefaultValueCount - 1), y); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/AboutSection.cs b/osu.Game/Overlays/Profile/Sections/AboutSection.cs index 5c56355d65..ba3ab80a0c 100644 --- a/osu.Game/Overlays/Profile/Sections/AboutSection.cs +++ b/osu.Game/Overlays/Profile/Sections/AboutSection.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Overlays.Profile.Sections -{ - public class AboutSection : ProfileSection - { - public override string Title => "me!"; - - public override string Identifier => "me"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Overlays.Profile.Sections +{ + public class AboutSection : ProfileSection + { + public override string Title => "me!"; + + public override string Identifier => "me"; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 8a40f0a489..da08c08179 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -1,63 +1,63 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Localisation; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -namespace osu.Game.Overlays.Profile.Sections -{ - /// - /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ). - /// - public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip - { - private readonly BeatmapInfo beatmap; - - public BeatmapMetadataContainer(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - AutoSizeAxes = Axes.Both; - TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; - } - - public string TooltipText { get; } - - [BackgroundDependencyLoader(true)] - private void load(LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) - { - Action = () => - { - if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value); - }; - - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Current = locale.GetUnicodePreference( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] " - ), - TextSize = 15, - Font = "Exo2.0-SemiBoldItalic", - }, - new OsuSpriteText - { - Current = locale.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), - TextSize = 12, - Padding = new MarginPadding { Top = 3 }, - Font = "Exo2.0-RegularItalic", - }, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +namespace osu.Game.Overlays.Profile.Sections +{ + /// + /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ). + /// + public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip + { + private readonly BeatmapInfo beatmap; + + public BeatmapMetadataContainer(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + AutoSizeAxes = Axes.Both; + TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; + } + + public string TooltipText { get; } + + [BackgroundDependencyLoader(true)] + private void load(LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) + { + Action = () => + { + if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value); + }; + + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Current = locale.GetUnicodePreference( + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] " + ), + TextSize = 15, + Font = "Exo2.0-SemiBoldItalic", + }, + new OsuSpriteText + { + Current = locale.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), + TextSize = 12, + Padding = new MarginPadding { Top = 3 }, + Font = "Exo2.0-RegularItalic", + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index da070d1cc2..842a13f8da 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,72 +1,72 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Direct; -using osu.Game.Users; -using System.Linq; - -namespace osu.Game.Overlays.Profile.Sections.Beatmaps -{ - public class PaginatedBeatmapContainer : PaginatedContainer - { - private const float panel_padding = 10f; - - private readonly BeatmapSetType type; - - private DirectPanel currentlyPlaying; - - public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.") - : base(user, header, missing) - { - this.type = type; - - ItemsPerPage = 6; - - ItemsContainer.Spacing = new Vector2(panel_padding); - } - - protected override void ShowMore() - { - base.ShowMore(); - - var req = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); - - req.Success += sets => - { - ShowMoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); - - if (!sets.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - foreach (var s in sets) - { - if (!s.OnlineBeatmapSetID.HasValue) - continue; - - var panel = new DirectGridPanel(s.ToBeatmapSet(Rulesets)); - ItemsContainer.Add(panel); - - panel.PreviewPlaying.ValueChanged += isPlaying => - { - if (!isPlaying) return; - - if (currentlyPlaying != null && currentlyPlaying != panel) - currentlyPlaying.PreviewPlaying.Value = false; - - currentlyPlaying = panel; - }; - } - }; - - Api.Queue(req); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Direct; +using osu.Game.Users; +using System.Linq; + +namespace osu.Game.Overlays.Profile.Sections.Beatmaps +{ + public class PaginatedBeatmapContainer : PaginatedContainer + { + private const float panel_padding = 10f; + + private readonly BeatmapSetType type; + + private DirectPanel currentlyPlaying; + + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.") + : base(user, header, missing) + { + this.type = type; + + ItemsPerPage = 6; + + ItemsContainer.Spacing = new Vector2(panel_padding); + } + + protected override void ShowMore() + { + base.ShowMore(); + + var req = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); + + req.Success += sets => + { + ShowMoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); + + if (!sets.Any() && VisiblePages == 1) + { + MissingText.Show(); + return; + } + + foreach (var s in sets) + { + if (!s.OnlineBeatmapSetID.HasValue) + continue; + + var panel = new DirectGridPanel(s.ToBeatmapSet(Rulesets)); + ItemsContainer.Add(panel); + + panel.PreviewPlaying.ValueChanged += isPlaying => + { + if (!isPlaying) return; + + if (currentlyPlaying != null && currentlyPlaying != panel) + currentlyPlaying.PreviewPlaying.Value = false; + + currentlyPlaying = panel; + }; + } + }; + + Api.Queue(req); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 760054716f..367d096c16 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Profile.Sections.Beatmaps; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class BeatmapsSection : ProfileSection - { - public override string Title => "Beatmaps"; - - public override string Identifier => "beatmaps"; - - public BeatmapsSection() - { - Children = new[] - { - new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, "Favourite Beatmaps"), - new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User, "Ranked & Approved Beatmaps"), - new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"), - new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile.Sections.Beatmaps; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class BeatmapsSection : ProfileSection + { + public override string Title => "Beatmaps"; + + public override string Identifier => "beatmaps"; + + public BeatmapsSection() + { + Children = new[] + { + new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, "Favourite Beatmaps"), + new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User, "Ranked & Approved Beatmaps"), + new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"), + new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"), + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index 4956015b3f..3c841cbf14 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -1,124 +1,124 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Profile.Sections -{ - public abstract class DrawableProfileRow : Container - { - private const int fade_duration = 200; - - private Box underscoreLine; - private readonly Box coloredBackground; - private readonly Container background; - - /// - /// A visual element displayed to the left of content. - /// - protected abstract Drawable CreateLeftVisual(); - - protected FillFlowContainer LeftFlowContainer { get; private set; } - protected FillFlowContainer RightFlowContainer { get; private set; } - - protected override Container Content { get; } - - protected DrawableProfileRow() - { - RelativeSizeAxes = Axes.X; - Height = 60; - InternalChildren = new Drawable[] - { - background = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 3, - Alpha = 0, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 1f, - Colour = Color4.Black.Opacity(0.2f), - }, - Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both } - }, - Content = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 0.97f, - }, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colour) - { - AddRange(new Drawable[] - { - underscoreLine = new Box - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 1, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - CreateLeftVisual(), - LeftFlowContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 10 }, - Direction = FillDirection.Vertical, - }, - } - }, - RightFlowContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Vertical, - }, - }); - - coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; - } - - protected override bool OnClick(InputState state) => true; - - protected override bool OnHover(InputState state) - { - background.FadeIn(fade_duration, Easing.OutQuint); - underscoreLine.FadeOut(fade_duration, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(InputState state) - { - background.FadeOut(fade_duration, Easing.OutQuint); - underscoreLine.FadeIn(fade_duration, Easing.OutQuint); - base.OnHoverLost(state); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class DrawableProfileRow : Container + { + private const int fade_duration = 200; + + private Box underscoreLine; + private readonly Box coloredBackground; + private readonly Container background; + + /// + /// A visual element displayed to the left of content. + /// + protected abstract Drawable CreateLeftVisual(); + + protected FillFlowContainer LeftFlowContainer { get; private set; } + protected FillFlowContainer RightFlowContainer { get; private set; } + + protected override Container Content { get; } + + protected DrawableProfileRow() + { + RelativeSizeAxes = Axes.X; + Height = 60; + InternalChildren = new Drawable[] + { + background = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 3, + Alpha = 0, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 1f, + Colour = Color4.Black.Opacity(0.2f), + }, + Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both } + }, + Content = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 0.97f, + }, + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colour) + { + AddRange(new Drawable[] + { + underscoreLine = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 1, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + CreateLeftVisual(), + LeftFlowContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 10 }, + Direction = FillDirection.Vertical, + }, + } + }, + RightFlowContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Vertical, + }, + }); + + coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; + } + + protected override bool OnClick(InputState state) => true; + + protected override bool OnHover(InputState state) + { + background.FadeIn(fade_duration, Easing.OutQuint); + underscoreLine.FadeOut(fade_duration, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(InputState state) + { + background.FadeOut(fade_duration, Easing.OutQuint); + underscoreLine.FadeIn(fade_duration, Easing.OutQuint); + base.OnHoverLost(state); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs index 2f8f8aa0ae..be8e09105b 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs @@ -1,105 +1,105 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using OpenTK; - -namespace osu.Game.Overlays.Profile.Sections.Historical -{ - public class DrawableMostPlayedRow : DrawableProfileRow - { - private readonly BeatmapInfo beatmap; - private readonly int playCount; - private OsuHoverContainer mapperContainer; - - public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount) - { - this.beatmap = beatmap; - this.playCount = playCount; - } - - protected override Drawable CreateLeftVisual() => new DelayedLoadWrapper(new BeatmapSetCover(beatmap.BeatmapSet, BeatmapSetCoverType.List) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(500, Easing.OutQuint) - }) - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - RelativeSizeAxes = Axes.None, - Size = new Vector2(80, 50), - }; - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profileOverlay) - { - LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap)); - LeftFlowContainer.Add(new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"mapped by ", - TextSize = 12, - }, - mapperContainer = new OsuHoverContainer - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = beatmap.Metadata.AuthorString, - TextSize = 12, - Font = @"Exo2.0-MediumItalic" - } - } - }, - } - }); - - RightFlowContainer.Add(new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Text = playCount.ToString(), - TextSize = 18, - Font = @"Exo2.0-SemiBoldItalic" - }, - new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Text = @"times played ", - TextSize = 12, - Font = @"Exo2.0-RegularItalic" - }, - } - }); - - if (profileOverlay != null) - mapperContainer.Action = () => profileOverlay.ShowUser(beatmap.BeatmapSet.Metadata.Author); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class DrawableMostPlayedRow : DrawableProfileRow + { + private readonly BeatmapInfo beatmap; + private readonly int playCount; + private OsuHoverContainer mapperContainer; + + public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount) + { + this.beatmap = beatmap; + this.playCount = playCount; + } + + protected override Drawable CreateLeftVisual() => new DelayedLoadWrapper(new BeatmapSetCover(beatmap.BeatmapSet, BeatmapSetCoverType.List) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + OnLoadComplete = d => d.FadeInFromZero(500, Easing.OutQuint) + }) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.None, + Size = new Vector2(80, 50), + }; + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profileOverlay) + { + LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap)); + LeftFlowContainer.Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"mapped by ", + TextSize = 12, + }, + mapperContainer = new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = beatmap.Metadata.AuthorString, + TextSize = 12, + Font = @"Exo2.0-MediumItalic" + } + } + }, + } + }); + + RightFlowContainer.Add(new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Text = playCount.ToString(), + TextSize = 18, + Font = @"Exo2.0-SemiBoldItalic" + }, + new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Text = @"times played ", + TextSize = 12, + Font = @"Exo2.0-RegularItalic" + }, + } + }); + + if (profileOverlay != null) + mapperContainer.Action = () => profileOverlay.ShowUser(beatmap.BeatmapSet.Metadata.Author); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 176b4bc30a..42784682be 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -1,51 +1,51 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.API.Requests; -using osu.Game.Users; - -namespace osu.Game.Overlays.Profile.Sections.Historical -{ - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer - { - public PaginatedMostPlayedBeatmapContainer(Bindable user) - :base(user, "Most Played Beatmaps", "No records. :(") - { - ItemsPerPage = 5; - - ItemsContainer.Direction = FillDirection.Vertical; - } - - protected override void ShowMore() - { - base.ShowMore(); - - var req = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); - - req.Success += beatmaps => - { - ShowMoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); - - if (!beatmaps.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (var beatmap in beatmaps) - { - ItemsContainer.Add(new DrawableMostPlayedRow(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); - } - }; - - Api.Queue(req); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + { + public PaginatedMostPlayedBeatmapContainer(Bindable user) + :base(user, "Most Played Beatmaps", "No records. :(") + { + ItemsPerPage = 5; + + ItemsContainer.Direction = FillDirection.Vertical; + } + + protected override void ShowMore() + { + base.ShowMore(); + + var req = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); + + req.Success += beatmaps => + { + ShowMoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); + + if (!beatmaps.Any() && VisiblePages == 1) + { + MissingText.Show(); + return; + } + + MissingText.Hide(); + + foreach (var beatmap in beatmaps) + { + ItemsContainer.Add(new DrawableMostPlayedRow(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); + } + }; + + Api.Queue(req); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 82b520cf10..924b27a86d 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Profile.Sections.Historical; -using osu.Game.Overlays.Profile.Sections.Ranks; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class HistoricalSection : ProfileSection - { - public override string Title => "Historical"; - - public override string Identifier => "historical"; - - public HistoricalSection() - { - Children = new Drawable[] - { - new PaginatedMostPlayedBeatmapContainer(User), - new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile.Sections.Historical; +using osu.Game.Overlays.Profile.Sections.Ranks; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class HistoricalSection : ProfileSection + { + public override string Title => "Historical"; + + public override string Identifier => "historical"; + + public HistoricalSection() + { + Children = new Drawable[] + { + new PaginatedMostPlayedBeatmapContainer(User), + new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("), + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 896ffc33f6..15b446efa5 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -1,135 +1,135 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; - -namespace osu.Game.Overlays.Profile.Sections.Kudosu -{ - public class KudosuInfo : Container - { - private readonly Bindable user = new Bindable(); - - public KudosuInfo(Bindable user) - { - this.user.BindTo(user); - - CountSection total; - CountSection avaliable; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Masking = true; - CornerRadius = 3; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 3f, - Colour = Color4.Black.Opacity(0.2f), - }; - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] - { - total = new CountSection( - "Total Kudosu Earned", - "Based on how much of a contribution the user has made to beatmap moderation. See this link for more information." - ), - avaliable = new CountSection( - "Kudosu Avaliable", - "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet." - ), - } - } - }; - - this.user.ValueChanged += newUser => - { - total.Count = newUser?.Kudosu.Total ?? 0; - avaliable.Count = newUser?.Kudosu.Available ?? 0; - }; - } - - protected override bool OnClick(InputState state) => true; - - private class CountSection : Container - { - private readonly OsuSpriteText valueText; - - public new int Count - { - set { valueText.Text = value.ToString(); } - } - - public CountSection(string header, string description) - { - RelativeSizeAxes = Axes.X; - Width = 0.5f; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Horizontal = 10, Top = 10, Bottom = 20 }; - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = header + ":", - TextSize = 20, - Font = @"Exo2.0-RegularItalic", - }, - valueText = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = "0", - TextSize = 40, - UseFullGlyphHeight = false, - Font = @"Exo2.0-RegularItalic" - } - } - }, - new OsuTextFlowContainer(t => { t.TextSize = 19; }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = description - } - } - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Sections.Kudosu +{ + public class KudosuInfo : Container + { + private readonly Bindable user = new Bindable(); + + public KudosuInfo(Bindable user) + { + this.user.BindTo(user); + + CountSection total; + CountSection avaliable; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 3; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 3f, + Colour = Color4.Black.Opacity(0.2f), + }; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + total = new CountSection( + "Total Kudosu Earned", + "Based on how much of a contribution the user has made to beatmap moderation. See this link for more information." + ), + avaliable = new CountSection( + "Kudosu Avaliable", + "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet." + ), + } + } + }; + + this.user.ValueChanged += newUser => + { + total.Count = newUser?.Kudosu.Total ?? 0; + avaliable.Count = newUser?.Kudosu.Available ?? 0; + }; + } + + protected override bool OnClick(InputState state) => true; + + private class CountSection : Container + { + private readonly OsuSpriteText valueText; + + public new int Count + { + set { valueText.Text = value.ToString(); } + } + + public CountSection(string header, string description) + { + RelativeSizeAxes = Axes.X; + Width = 0.5f; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Horizontal = 10, Top = 10, Bottom = 20 }; + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = header + ":", + TextSize = 20, + Font = @"Exo2.0-RegularItalic", + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "0", + TextSize = 40, + UseFullGlyphHeight = false, + Font = @"Exo2.0-RegularItalic" + } + } + }, + new OsuTextFlowContainer(t => { t.TextSize = 19; }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = description + } + } + }; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index 74291c587e..1490a4bda7 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Overlays.Profile.Sections.Kudosu; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class KudosuSection : ProfileSection - { - public override string Title => "Kudosu!"; - - public override string Identifier => "kudosu"; - - public KudosuSection() - { - Children = new[] - { - new KudosuInfo(User), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.Profile.Sections.Kudosu; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class KudosuSection : ProfileSection + { + public override string Title => "Kudosu!"; + + public override string Identifier => "kudosu"; + + public KudosuSection() + { + Children = new[] + { + new KudosuInfo(User), + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs index fe5543f264..45be08b292 100644 --- a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Overlays.Profile.Sections -{ - public class MedalsSection : ProfileSection - { - public override string Title => "Medals"; - - public override string Identifier => "medals"; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Overlays.Profile.Sections +{ + public class MedalsSection : ProfileSection + { + public override string Title => "Medals"; + + public override string Identifier => "medals"; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 3864002367..1e0406c125 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -1,110 +1,110 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Rulesets; -using osu.Game.Users; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class PaginatedContainer : FillFlowContainer - { - protected readonly FillFlowContainer ItemsContainer; - protected readonly OsuHoverContainer ShowMoreButton; - protected readonly LoadingAnimation ShowMoreLoading; - protected readonly OsuSpriteText MissingText; - - protected int VisiblePages; - protected int ItemsPerPage; - - protected readonly Bindable User = new Bindable(); - - protected APIAccess Api; - protected RulesetStore Rulesets; - - public PaginatedContainer(Bindable user, string header, string missing) - { - User.BindTo(user); - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - - Children = new Drawable[] - { - new OsuSpriteText - { - TextSize = 15, - Text = header, - Font = "Exo2.0-RegularItalic", - Margin = new MarginPadding { Top = 10, Bottom = 10 }, - }, - ItemsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Bottom = 10 } - }, - ShowMoreButton = new OsuHoverContainer - { - Alpha = 0, - Action = ShowMore, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Child = new OsuSpriteText - { - TextSize = 14, - Text = "show more", - } - }, - ShowMoreLoading = new LoadingAnimation - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(14), - }, - MissingText = new OsuSpriteText - { - TextSize = 14, - Text = missing, - Alpha = 0, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) - { - Api = api; - Rulesets = rulesets; - - User.ValueChanged += onUserChanged; - User.TriggerChange(); - } - - private void onUserChanged(User newUser) - { - VisiblePages = 0; - ItemsContainer.Clear(); - ShowMoreButton.Hide(); - - if (newUser != null) - ShowMore(); - } - - protected virtual void ShowMore() - { - ShowMoreLoading.Show(); - ShowMoreButton.Hide(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class PaginatedContainer : FillFlowContainer + { + protected readonly FillFlowContainer ItemsContainer; + protected readonly OsuHoverContainer ShowMoreButton; + protected readonly LoadingAnimation ShowMoreLoading; + protected readonly OsuSpriteText MissingText; + + protected int VisiblePages; + protected int ItemsPerPage; + + protected readonly Bindable User = new Bindable(); + + protected APIAccess Api; + protected RulesetStore Rulesets; + + public PaginatedContainer(Bindable user, string header, string missing) + { + User.BindTo(user); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 15, + Text = header, + Font = "Exo2.0-RegularItalic", + Margin = new MarginPadding { Top = 10, Bottom = 10 }, + }, + ItemsContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Bottom = 10 } + }, + ShowMoreButton = new OsuHoverContainer + { + Alpha = 0, + Action = ShowMore, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Child = new OsuSpriteText + { + TextSize = 14, + Text = "show more", + } + }, + ShowMoreLoading = new LoadingAnimation + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(14), + }, + MissingText = new OsuSpriteText + { + TextSize = 14, + Text = missing, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(APIAccess api, RulesetStore rulesets) + { + Api = api; + Rulesets = rulesets; + + User.ValueChanged += onUserChanged; + User.TriggerChange(); + } + + private void onUserChanged(User newUser) + { + VisiblePages = 0; + ItemsContainer.Clear(); + ShowMoreButton.Hide(); + + if (newUser != null) + ShowMore(); + } + + protected virtual void ShowMore() + { + ShowMoreLoading.Show(); + ShowMoreButton.Hide(); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs index 3b814b0542..ddbe10c05c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs @@ -1,49 +1,49 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public class DrawablePerformanceScore : DrawableProfileScore - { - private readonly double? weight; - - public DrawablePerformanceScore(Score score, double? weight = null) - : base(score) - { - this.weight = weight; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - double pp = Score.PP ?? 0; - RightFlowContainer.Add(new OsuSpriteText - { - Text = $"{pp:0}pp", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - TextSize = 18, - Font = "Exo2.0-BoldItalic", - }); - - if (weight.HasValue) - { - RightFlowContainer.Add(new OsuSpriteText - { - Text = $"weighted: {pp * weight:0}pp ({weight:P0})", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Colour = colour.GrayA, - TextSize = 11, - Font = "Exo2.0-RegularItalic", - }); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public class DrawablePerformanceScore : DrawableProfileScore + { + private readonly double? weight; + + public DrawablePerformanceScore(Score score, double? weight = null) + : base(score) + { + this.weight = weight; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + double pp = Score.PP ?? 0; + RightFlowContainer.Add(new OsuSpriteText + { + Text = $"{pp:0}pp", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + TextSize = 18, + Font = "Exo2.0-BoldItalic", + }); + + if (weight.HasValue) + { + RightFlowContainer.Add(new OsuSpriteText + { + Text = $"weighted: {pp * weight:0}pp ({weight:P0})", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colour.GrayA, + TextSize = 11, + Font = "Exo2.0-RegularItalic", + }); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 509356ae04..67e86265ed 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -1,70 +1,70 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public abstract class DrawableProfileScore : DrawableProfileRow - { - private readonly ScoreModsContainer modsContainer; - protected readonly Score Score; - - protected DrawableProfileScore(Score score) - { - Score = score; - - RelativeSizeAxes = Axes.X; - Height = 60; - Children = new Drawable[] - { - modsContainer = new ScoreModsContainer - { - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Width = 60, - Margin = new MarginPadding { Right = 160 } - } - }; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colour) - { - var text = new OsuSpriteText - { - Text = $"accuracy: {Score.Accuracy:P2}", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Colour = colour.GrayA, - TextSize = 11, - Font = "Exo2.0-RegularItalic" - }; - - RightFlowContainer.Add(text); - RightFlowContainer.SetLayoutPosition(text, 1); - - LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap)); - LeftFlowContainer.Add(new DrawableDate(Score.Date)); - - foreach (Mod mod in Score.Mods) - modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) }); - } - - protected override Drawable CreateLeftVisual() => new DrawableRank(Score.Rank) - { - RelativeSizeAxes = Axes.Y, - Width = 60, - FillMode = FillMode.Fit, - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public abstract class DrawableProfileScore : DrawableProfileRow + { + private readonly ScoreModsContainer modsContainer; + protected readonly Score Score; + + protected DrawableProfileScore(Score score) + { + Score = score; + + RelativeSizeAxes = Axes.X; + Height = 60; + Children = new Drawable[] + { + modsContainer = new ScoreModsContainer + { + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 60, + Margin = new MarginPadding { Right = 160 } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colour) + { + var text = new OsuSpriteText + { + Text = $"accuracy: {Score.Accuracy:P2}", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colour.GrayA, + TextSize = 11, + Font = "Exo2.0-RegularItalic" + }; + + RightFlowContainer.Add(text); + RightFlowContainer.SetLayoutPosition(text, 1); + + LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap)); + LeftFlowContainer.Add(new DrawableDate(Score.Date)); + + foreach (Mod mod in Score.Mods) + modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) }); + } + + protected override Drawable CreateLeftVisual() => new DrawableRank(Score.Rank) + { + RelativeSizeAxes = Axes.Y, + Width = 60, + FillMode = FillMode.Fit, + }; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs index 61154b2280..3a39ef5d61 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public class DrawableTotalScore : DrawableProfileScore - { - public DrawableTotalScore(Score score) - : base(score) - { - } - - [BackgroundDependencyLoader] - private void load() - { - RightFlowContainer.Add(new OsuSpriteText - { - Text = Score.TotalScore.ToString("#,###"), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - TextSize = 18, - Font = "Exo2.0-BoldItalic", - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public class DrawableTotalScore : DrawableProfileScore + { + public DrawableTotalScore(Score score) + : base(score) + { + } + + [BackgroundDependencyLoader] + private void load() + { + RightFlowContainer.Add(new OsuSpriteText + { + Text = Score.TotalScore.ToString("#,###"), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + TextSize = 18, + Font = "Exo2.0-BoldItalic", + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 8e20953457..87df84e2e5 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -1,73 +1,73 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.API.Requests; -using osu.Game.Users; -using System; -using System.Linq; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public class PaginatedScoreContainer : PaginatedContainer - { - private readonly bool includeWeight; - private readonly ScoreType type; - - public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing, bool includeWeight = false) - : base(user, header, missing) - { - this.type = type; - this.includeWeight = includeWeight; - - ItemsPerPage = 5; - - ItemsContainer.Direction = FillDirection.Vertical; - } - - protected override void ShowMore() - { - base.ShowMore(); - - var req = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); - - req.Success += scores => - { - foreach (var s in scores) - s.ApplyRuleset(Rulesets.GetRuleset(s.OnlineRulesetID)); - - ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); - - if (!scores.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (OnlineScore score in scores) - { - DrawableProfileScore drawableScore; - - switch (type) - { - default: - drawableScore = new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); - break; - case ScoreType.Recent: - drawableScore = new DrawableTotalScore(score); - break; - } - - ItemsContainer.Add(drawableScore); - } - }; - - Api.Queue(req); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests; +using osu.Game.Users; +using System; +using System.Linq; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public class PaginatedScoreContainer : PaginatedContainer + { + private readonly bool includeWeight; + private readonly ScoreType type; + + public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing, bool includeWeight = false) + : base(user, header, missing) + { + this.type = type; + this.includeWeight = includeWeight; + + ItemsPerPage = 5; + + ItemsContainer.Direction = FillDirection.Vertical; + } + + protected override void ShowMore() + { + base.ShowMore(); + + var req = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); + + req.Success += scores => + { + foreach (var s in scores) + s.ApplyRuleset(Rulesets.GetRuleset(s.OnlineRulesetID)); + + ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); + + if (!scores.Any() && VisiblePages == 1) + { + MissingText.Show(); + return; + } + + MissingText.Hide(); + + foreach (OnlineScore score in scores) + { + DrawableProfileScore drawableScore; + + switch (type) + { + default: + drawableScore = new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + break; + case ScoreType.Recent: + drawableScore = new DrawableTotalScore(score); + break; + } + + ItemsContainer.Add(drawableScore); + } + }; + + Api.Queue(req); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs index 59b16c0987..90f476fa73 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.UI; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public class ScoreModsContainer : FlowContainer - { - protected override IEnumerable ComputeLayoutPositions() - { - int count = FlowingChildren.Count(); - for (int i = 0; i < count; i++) - yield return new Vector2(DrawWidth * i * (count == 1 ? 0 : 1f / (count - 1)), 0); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public class ScoreModsContainer : FlowContainer + { + protected override IEnumerable ComputeLayoutPositions() + { + int count = FlowingChildren.Count(); + for (int i = 0; i < count; i++) + yield return new Vector2(DrawWidth * i * (count == 1 ? 0 : 1f / (count - 1)), 0); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 64687284cb..965f668a68 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Online.API.Requests; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class RanksSection : ProfileSection - { - public override string Title => "Ranks"; - - public override string Identifier => "top_ranks"; - - public RanksSection() - { - Children = new[] - { - new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", "No performance records. :(", true), - new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks", "No awesome performance records yet. :("), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Online.API.Requests; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class RanksSection : ProfileSection + { + public override string Title => "Ranks"; + + public override string Identifier => "top_ranks"; + + public RanksSection() + { + Children = new[] + { + new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", "No performance records. :(", true), + new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks", "No awesome performance records yet. :("), + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index e8be8d1e44..721cc30b61 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -1,162 +1,162 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Online.Chat; -using osu.Game.Screens.Select.Leaderboards; - -namespace osu.Game.Overlays.Profile.Sections.Recent -{ - public class DrawableRecentActivity : DrawableProfileRow - { - private APIAccess api; - - private readonly RecentActivity activity; - - private LinkFlowContainer content; - - public DrawableRecentActivity(RecentActivity activity) - { - this.activity = activity; - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - - LeftFlowContainer.Padding = new MarginPadding { Left = 10, Right = 160 }; - - LeftFlowContainer.Add(content = new LinkFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }); - - RightFlowContainer.Add(new DrawableDate(activity.CreatedAt) - { - TextSize = 13, - Colour = OsuColour.Gray(0xAA), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }); - - var formatted = createMessage(); - - content.AddLinks(formatted.Text, formatted.Links); - } - - protected override Drawable CreateLeftVisual() - { - switch (activity.Type) - { - case RecentActivityType.Rank: - return new DrawableRank(activity.ScoreRank) - { - RelativeSizeAxes = Axes.Y, - Width = 60, - FillMode = FillMode.Fit, - }; - - case RecentActivityType.Achievement: - return new MedalIcon(activity.Achievement.Slug) - { - RelativeSizeAxes = Axes.Y, - Width = 60, - FillMode = FillMode.Fit, - }; - - default: - return new Container - { - RelativeSizeAxes = Axes.Y, - Width = 60, - FillMode = FillMode.Fit, - }; - } - } - - private string toAbsoluteUrl(string url) => $"{api.Endpoint}{url}"; - - private MessageFormatter.MessageFormatterResult createMessage() - { - string userLinkTemplate() => $"[{toAbsoluteUrl(activity.User?.Url)} {activity.User?.Username}]"; - string beatmapLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmap?.Url)} {activity.Beatmap?.Title}]"; - string beatmapsetLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmapset?.Url)} {activity.Beatmapset?.Title}]"; - - string message; - - switch (activity.Type) - { - case RecentActivityType.Achievement: - message = $"{userLinkTemplate()} unlocked the {activity.Achievement.Name} medal!"; - break; - - case RecentActivityType.BeatmapPlaycount: - message = $"{beatmapLinkTemplate()} has been played {activity.Count} times!"; - break; - - case RecentActivityType.BeatmapsetApprove: - message = $"{beatmapsetLinkTemplate()} has been {activity.Approval.ToString().ToLowerInvariant()}!"; - break; - - case RecentActivityType.BeatmapsetDelete: - message = $"{beatmapsetLinkTemplate()} has been deleted."; - break; - - case RecentActivityType.BeatmapsetRevive: - message = $"{beatmapsetLinkTemplate()} has been revived from eternal slumber by {userLinkTemplate()}."; - break; - - case RecentActivityType.BeatmapsetUpdate: - message = $"{userLinkTemplate()} has updated the beatmap {beatmapsetLinkTemplate()}!"; - break; - - case RecentActivityType.BeatmapsetUpload: - message = $"{userLinkTemplate()} has submitted a new beatmap {beatmapsetLinkTemplate()}!"; - break; - - case RecentActivityType.Medal: - // apparently this shouldn't exist look at achievement instead (https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/profile-page/recent-activity.coffee#L111) - message = string.Empty; - break; - - case RecentActivityType.Rank: - message = $"{userLinkTemplate()} achieved rank #{activity.Rank} on {beatmapLinkTemplate()} ({activity.Mode}!)"; - break; - - case RecentActivityType.RankLost: - message = $"{userLinkTemplate()} has lost first place on {beatmapLinkTemplate()} ({activity.Mode}!)"; - break; - - case RecentActivityType.UserSupportAgain: - message = $"{userLinkTemplate()} has once again chosen to support osu! - thanks for your generosity!"; - break; - - case RecentActivityType.UserSupportFirst: - message = $"{userLinkTemplate()} has become an osu! supporter - thanks for your generosity!"; - break; - - case RecentActivityType.UserSupportGift: - message = $"{userLinkTemplate()} has received the gift of osu! supporter!"; - break; - - case RecentActivityType.UsernameChange: - message = $"{activity.User?.PreviousUsername} has changed their username to {userLinkTemplate()}!"; - break; - - default: - message = string.Empty; - break; - } - - return MessageFormatter.FormatText(message); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using osu.Game.Screens.Select.Leaderboards; + +namespace osu.Game.Overlays.Profile.Sections.Recent +{ + public class DrawableRecentActivity : DrawableProfileRow + { + private APIAccess api; + + private readonly RecentActivity activity; + + private LinkFlowContainer content; + + public DrawableRecentActivity(RecentActivity activity) + { + this.activity = activity; + } + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + this.api = api; + + LeftFlowContainer.Padding = new MarginPadding { Left = 10, Right = 160 }; + + LeftFlowContainer.Add(content = new LinkFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }); + + RightFlowContainer.Add(new DrawableDate(activity.CreatedAt) + { + TextSize = 13, + Colour = OsuColour.Gray(0xAA), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }); + + var formatted = createMessage(); + + content.AddLinks(formatted.Text, formatted.Links); + } + + protected override Drawable CreateLeftVisual() + { + switch (activity.Type) + { + case RecentActivityType.Rank: + return new DrawableRank(activity.ScoreRank) + { + RelativeSizeAxes = Axes.Y, + Width = 60, + FillMode = FillMode.Fit, + }; + + case RecentActivityType.Achievement: + return new MedalIcon(activity.Achievement.Slug) + { + RelativeSizeAxes = Axes.Y, + Width = 60, + FillMode = FillMode.Fit, + }; + + default: + return new Container + { + RelativeSizeAxes = Axes.Y, + Width = 60, + FillMode = FillMode.Fit, + }; + } + } + + private string toAbsoluteUrl(string url) => $"{api.Endpoint}{url}"; + + private MessageFormatter.MessageFormatterResult createMessage() + { + string userLinkTemplate() => $"[{toAbsoluteUrl(activity.User?.Url)} {activity.User?.Username}]"; + string beatmapLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmap?.Url)} {activity.Beatmap?.Title}]"; + string beatmapsetLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmapset?.Url)} {activity.Beatmapset?.Title}]"; + + string message; + + switch (activity.Type) + { + case RecentActivityType.Achievement: + message = $"{userLinkTemplate()} unlocked the {activity.Achievement.Name} medal!"; + break; + + case RecentActivityType.BeatmapPlaycount: + message = $"{beatmapLinkTemplate()} has been played {activity.Count} times!"; + break; + + case RecentActivityType.BeatmapsetApprove: + message = $"{beatmapsetLinkTemplate()} has been {activity.Approval.ToString().ToLowerInvariant()}!"; + break; + + case RecentActivityType.BeatmapsetDelete: + message = $"{beatmapsetLinkTemplate()} has been deleted."; + break; + + case RecentActivityType.BeatmapsetRevive: + message = $"{beatmapsetLinkTemplate()} has been revived from eternal slumber by {userLinkTemplate()}."; + break; + + case RecentActivityType.BeatmapsetUpdate: + message = $"{userLinkTemplate()} has updated the beatmap {beatmapsetLinkTemplate()}!"; + break; + + case RecentActivityType.BeatmapsetUpload: + message = $"{userLinkTemplate()} has submitted a new beatmap {beatmapsetLinkTemplate()}!"; + break; + + case RecentActivityType.Medal: + // apparently this shouldn't exist look at achievement instead (https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/profile-page/recent-activity.coffee#L111) + message = string.Empty; + break; + + case RecentActivityType.Rank: + message = $"{userLinkTemplate()} achieved rank #{activity.Rank} on {beatmapLinkTemplate()} ({activity.Mode}!)"; + break; + + case RecentActivityType.RankLost: + message = $"{userLinkTemplate()} has lost first place on {beatmapLinkTemplate()} ({activity.Mode}!)"; + break; + + case RecentActivityType.UserSupportAgain: + message = $"{userLinkTemplate()} has once again chosen to support osu! - thanks for your generosity!"; + break; + + case RecentActivityType.UserSupportFirst: + message = $"{userLinkTemplate()} has become an osu! supporter - thanks for your generosity!"; + break; + + case RecentActivityType.UserSupportGift: + message = $"{userLinkTemplate()} has received the gift of osu! supporter!"; + break; + + case RecentActivityType.UsernameChange: + message = $"{activity.User?.PreviousUsername} has changed their username to {userLinkTemplate()}!"; + break; + + default: + message = string.Empty; + break; + } + + return MessageFormatter.FormatText(message); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs index 6ffbe7193f..0d354c728f 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Overlays.Profile.Sections.Recent -{ - public class MedalIcon : Container - { - private readonly string slug; - private readonly Sprite sprite; - - private string url => $@"https://s.ppy.sh/images/medals-client/{slug}@2x.png"; - - public MedalIcon(string slug) - { - this.slug = slug; - - Child = sprite = new Sprite - { - Height = 40, - Width = 40, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - sprite.Texture = textures.Get(url); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Overlays.Profile.Sections.Recent +{ + public class MedalIcon : Container + { + private readonly string slug; + private readonly Sprite sprite; + + private string url => $@"https://s.ppy.sh/images/medals-client/{slug}@2x.png"; + + public MedalIcon(string slug) + { + this.slug = slug; + + Child = sprite = new Sprite + { + Height = 40, + Width = 40, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + sprite.Texture = textures.Get(url); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index d479627cde..8c1108b115 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -1,48 +1,48 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Game.Online.API.Requests; -using osu.Game.Users; -using System.Linq; - -namespace osu.Game.Overlays.Profile.Sections.Recent -{ - public class PaginatedRecentActivityContainer : PaginatedContainer - { - public PaginatedRecentActivityContainer(Bindable user, string header, string missing) - : base(user, header, missing) - { - ItemsPerPage = 5; - } - - protected override void ShowMore() - { - base.ShowMore(); - - var req = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); - - req.Success += activities => - { - ShowMoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); - - if (!activities.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (RecentActivity activity in activities) - { - ItemsContainer.Add(new DrawableRecentActivity(activity)); - } - }; - - Api.Queue(req); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Users; +using System.Linq; + +namespace osu.Game.Overlays.Profile.Sections.Recent +{ + public class PaginatedRecentActivityContainer : PaginatedContainer + { + public PaginatedRecentActivityContainer(Bindable user, string header, string missing) + : base(user, header, missing) + { + ItemsPerPage = 5; + } + + protected override void ShowMore() + { + base.ShowMore(); + + var req = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); + + req.Success += activities => + { + ShowMoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); + + if (!activities.Any() && VisiblePages == 1) + { + MissingText.Show(); + return; + } + + MissingText.Hide(); + + foreach (RecentActivity activity in activities) + { + ItemsContainer.Add(new DrawableRecentActivity(activity)); + } + }; + + Api.Queue(req); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index 84a941aa1a..fdccd00bac 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Overlays.Profile.Sections.Recent; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class RecentSection : ProfileSection - { - public override string Title => "Recent"; - - public override string Identifier => "recent_activity"; - - public RecentSection() - { - Children = new[] - { - new PaginatedRecentActivityContainer(User, null, @"This user hasn't done anything notable recently!"), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.Profile.Sections.Recent; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class RecentSection : ProfileSection + { + public override string Title => "Recent"; + + public override string Identifier => "recent_activity"; + + public RecentSection() + { + Children = new[] + { + new PaginatedRecentActivityContainer(User, null, @"This user hasn't done anything notable recently!"), + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/SupporterIcon.cs b/osu.Game/Overlays/Profile/SupporterIcon.cs index 8f89dfe2ea..e8d52bf50e 100644 --- a/osu.Game/Overlays/Profile/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/SupporterIcon.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; - -namespace osu.Game.Overlays.Profile -{ - public class SupporterIcon : CircularContainer, IHasTooltip - { - private readonly Box background; - - public string TooltipText => "osu!supporter"; - - public SupporterIcon() - { - Masking = true; - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both }, - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), - Masking = true, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - new Triangles - { - TriangleScale = 0.2f, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - Velocity = 0.3f, - }, - } - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, - Scale = new Vector2(0.45f), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Pink; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Overlays.Profile +{ + public class SupporterIcon : CircularContainer, IHasTooltip + { + private readonly Box background; + + public string TooltipText => "osu!supporter"; + + public SupporterIcon() + { + Masking = true; + Children = new Drawable[] + { + new Box { RelativeSizeAxes = Axes.Both }, + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.8f), + Masking = true, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + new Triangles + { + TriangleScale = 0.2f, + ColourLight = OsuColour.FromHex(@"ff7db7"), + ColourDark = OsuColour.FromHex(@"de5b95"), + RelativeSizeAxes = Axes.Both, + Velocity = 0.3f, + }, + } + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_heart, + Scale = new Vector2(0.45f), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Pink; + } + } +} diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index 84d28e95ed..26a87230de 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -1,102 +1,102 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.SearchableList -{ - public class DisplayStyleControl : Container - { - public readonly SlimEnumDropdown Dropdown; - public readonly Bindable DisplayStyle = new Bindable(); - - public DisplayStyleControl() - { - AutoSizeAxes = Axes.Both; - - Children = new[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(10f, 0f), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5f, 0f), - Direction = FillDirection.Horizontal, - Children = new[] - { - new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle), - new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle), - }, - }, - Dropdown = new SlimEnumDropdown - { - RelativeSizeAxes = Axes.None, - Width = 160f, - }, - }, - }, - }; - - DisplayStyle.Value = PanelDisplayStyle.Grid; - } - - private class DisplayStyleToggleButton : OsuClickableContainer - { - private readonly SpriteIcon icon; - private readonly PanelDisplayStyle style; - private readonly Bindable bindable; - - public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable bindable) - { - this.bindable = bindable; - this.style = style; - Size = new Vector2(25f); - - Children = new Drawable[] - { - this.icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = icon, - Size = new Vector2(18), - Alpha = 0.5f, - }, - }; - - bindable.ValueChanged += Bindable_ValueChanged; - Bindable_ValueChanged(bindable.Value); - Action = () => bindable.Value = this.style; - } - - private void Bindable_ValueChanged(PanelDisplayStyle style) - { - icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100); - } - - protected override void Dispose(bool isDisposing) - { - bindable.ValueChanged -= Bindable_ValueChanged; - } - } - } - - public enum PanelDisplayStyle - { - Grid, - List, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.SearchableList +{ + public class DisplayStyleControl : Container + { + public readonly SlimEnumDropdown Dropdown; + public readonly Bindable DisplayStyle = new Bindable(); + + public DisplayStyleControl() + { + AutoSizeAxes = Axes.Both; + + Children = new[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Spacing = new Vector2(10f, 0f), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5f, 0f), + Direction = FillDirection.Horizontal, + Children = new[] + { + new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle), + }, + }, + Dropdown = new SlimEnumDropdown + { + RelativeSizeAxes = Axes.None, + Width = 160f, + }, + }, + }, + }; + + DisplayStyle.Value = PanelDisplayStyle.Grid; + } + + private class DisplayStyleToggleButton : OsuClickableContainer + { + private readonly SpriteIcon icon; + private readonly PanelDisplayStyle style; + private readonly Bindable bindable; + + public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable bindable) + { + this.bindable = bindable; + this.style = style; + Size = new Vector2(25f); + + Children = new Drawable[] + { + this.icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = icon, + Size = new Vector2(18), + Alpha = 0.5f, + }, + }; + + bindable.ValueChanged += Bindable_ValueChanged; + Bindable_ValueChanged(bindable.Value); + Action = () => bindable.Value = this.style; + } + + private void Bindable_ValueChanged(PanelDisplayStyle style) + { + icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100); + } + + protected override void Dispose(bool isDisposing) + { + bindable.ValueChanged -= Bindable_ValueChanged; + } + } + } + + public enum PanelDisplayStyle + { + Grid, + List, + } +} diff --git a/osu.Game/Overlays/SearchableList/HeaderTabControl.cs b/osu.Game/Overlays/SearchableList/HeaderTabControl.cs index 2dd9d7de29..62685eb323 100644 --- a/osu.Game/Overlays/SearchableList/HeaderTabControl.cs +++ b/osu.Game/Overlays/SearchableList/HeaderTabControl.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.SearchableList -{ - public class HeaderTabControl : OsuTabControl - { - protected override TabItem CreateTabItem(T value) => new HeaderTabItem(value); - - public HeaderTabControl() - { - Height = 26; - AccentColour = Color4.White; - } - - private class HeaderTabItem : OsuTabItem - { - public HeaderTabItem(T value) : base(value) - { - Text.TextSize = 16; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.SearchableList +{ + public class HeaderTabControl : OsuTabControl + { + protected override TabItem CreateTabItem(T value) => new HeaderTabItem(value); + + public HeaderTabControl() + { + Height = 26; + AccentColour = Color4.White; + } + + private class HeaderTabItem : OsuTabItem + { + public HeaderTabItem(T value) : base(value) + { + Text.TextSize = 16; + } + } + } +} diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index ffc6a370ec..82a60a09e1 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -1,134 +1,134 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Overlays.SearchableList -{ - public abstract class SearchableListFilterControl : Container - { - private const float padding = 10; - - private readonly Container filterContainer; - private readonly Box tabStrip; - - public readonly SearchTextBox Search; - public readonly PageTabControl Tabs; - public readonly DisplayStyleControl DisplayStyleControl; - - protected abstract Color4 BackgroundColour { get; } - protected abstract T DefaultTab { get; } - protected virtual Drawable CreateSupplementaryControls() => null; - - protected SearchableListFilterControl() - { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("SearchableListFilterControl's sort tabs only support enums as the generic type argument"); - - RelativeSizeAxes = Axes.X; - - var controls = CreateSupplementaryControls(); - Container controlsContainer; - Children = new Drawable[] - { - filterContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour, - Alpha = 0.9f, - }, - tabStrip = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 1, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Top = padding, Horizontal = SearchableListOverlay.WIDTH_PADDING }, - Children = new Drawable[] - { - Search = new FilterSearchTextBox - { - RelativeSizeAxes = Axes.X, - }, - controlsContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = controls != null ? padding : 0 }, - }, - Tabs = new PageTabControl - { - RelativeSizeAxes = Axes.X, - }, - new Box //keep the tab strip part of autosize, but don't put it in the flow container - { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = Color4.White.Opacity(0), - }, - }, - }, - }, - }, - DisplayStyleControl = new DisplayStyleControl - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - }; - - if (controls != null) controlsContainer.Children = new[] { controls }; - - Tabs.Current.Value = DefaultTab; - Tabs.Current.TriggerChange(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - tabStrip.Colour = colours.Yellow; - } - - protected override void Update() - { - base.Update(); - - Height = filterContainer.Height; - DisplayStyleControl.Margin = new MarginPadding { Top = filterContainer.Height - 35, Right = SearchableListOverlay.WIDTH_PADDING }; - } - - private class FilterSearchTextBox : SearchTextBox - { - protected override Color4 BackgroundUnfocused => backgroundColour; - protected override Color4 BackgroundFocused => backgroundColour; - protected override bool AllowCommit => true; - - private Color4 backgroundColour; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - backgroundColour = colours.Gray2.Opacity(0.9f); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Overlays.SearchableList +{ + public abstract class SearchableListFilterControl : Container + { + private const float padding = 10; + + private readonly Container filterContainer; + private readonly Box tabStrip; + + public readonly SearchTextBox Search; + public readonly PageTabControl Tabs; + public readonly DisplayStyleControl DisplayStyleControl; + + protected abstract Color4 BackgroundColour { get; } + protected abstract T DefaultTab { get; } + protected virtual Drawable CreateSupplementaryControls() => null; + + protected SearchableListFilterControl() + { + if (!typeof(T).IsEnum) + throw new InvalidOperationException("SearchableListFilterControl's sort tabs only support enums as the generic type argument"); + + RelativeSizeAxes = Axes.X; + + var controls = CreateSupplementaryControls(); + Container controlsContainer; + Children = new Drawable[] + { + filterContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = BackgroundColour, + Alpha = 0.9f, + }, + tabStrip = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = padding, Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Children = new Drawable[] + { + Search = new FilterSearchTextBox + { + RelativeSizeAxes = Axes.X, + }, + controlsContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = controls != null ? padding : 0 }, + }, + Tabs = new PageTabControl + { + RelativeSizeAxes = Axes.X, + }, + new Box //keep the tab strip part of autosize, but don't put it in the flow container + { + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = Color4.White.Opacity(0), + }, + }, + }, + }, + }, + DisplayStyleControl = new DisplayStyleControl + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }; + + if (controls != null) controlsContainer.Children = new[] { controls }; + + Tabs.Current.Value = DefaultTab; + Tabs.Current.TriggerChange(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + tabStrip.Colour = colours.Yellow; + } + + protected override void Update() + { + base.Update(); + + Height = filterContainer.Height; + DisplayStyleControl.Margin = new MarginPadding { Top = filterContainer.Height - 35, Right = SearchableListOverlay.WIDTH_PADDING }; + } + + private class FilterSearchTextBox : SearchTextBox + { + protected override Color4 BackgroundUnfocused => backgroundColour; + protected override Color4 BackgroundFocused => backgroundColour; + protected override bool AllowCommit => true; + + private Color4 backgroundColour; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + backgroundColour = colours.Gray2.Opacity(0.9f); + } + } + } +} diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index e053f2f773..9c4fe1c398 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -1,83 +1,83 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Overlays.SearchableList -{ - public abstract class SearchableListHeader : Container - { - public readonly HeaderTabControl Tabs; - - protected abstract Color4 BackgroundColour { get; } - protected abstract T DefaultTab { get; } - protected abstract Drawable CreateHeaderText(); - protected abstract FontAwesome Icon { get; } - - protected SearchableListHeader() - { - if (!typeof(T).IsEnum) - throw new InvalidOperationException("BrowseHeader only supports enums as the generic type argument"); - - RelativeSizeAxes = Axes.X; - Height = 90; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING, Right = SearchableListOverlay.WIDTH_PADDING }, - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft, - Position = new Vector2(-35f, 5f), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), - Children = new[] - { - new SpriteIcon - { - Size = new Vector2(25), - Icon = Icon, - }, - CreateHeaderText(), - }, - }, - Tabs = new HeaderTabControl - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }, - }, - }, - }; - - Tabs.Current.Value = DefaultTab; - Tabs.Current.TriggerChange(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Tabs.StripColour = colours.Green; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Overlays.SearchableList +{ + public abstract class SearchableListHeader : Container + { + public readonly HeaderTabControl Tabs; + + protected abstract Color4 BackgroundColour { get; } + protected abstract T DefaultTab { get; } + protected abstract Drawable CreateHeaderText(); + protected abstract FontAwesome Icon { get; } + + protected SearchableListHeader() + { + if (!typeof(T).IsEnum) + throw new InvalidOperationException("BrowseHeader only supports enums as the generic type argument"); + + RelativeSizeAxes = Axes.X; + Height = 90; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = BackgroundColour, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING, Right = SearchableListOverlay.WIDTH_PADDING }, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomLeft, + Position = new Vector2(-35f, 5f), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10f, 0f), + Children = new[] + { + new SpriteIcon + { + Size = new Vector2(25), + Icon = Icon, + }, + CreateHeaderText(), + }, + }, + Tabs = new HeaderTabControl + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + }, + }, + }, + }; + + Tabs.Current.Value = DefaultTab; + Tabs.Current.TriggerChange(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Tabs.StripColour = colours.Green; + } + } +} diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 3998af05b2..47cdb4a765 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -1,123 +1,123 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.SearchableList -{ - public abstract class SearchableListOverlay : WaveOverlayContainer - { - public static readonly float WIDTH_PADDING = 80; - } - - public abstract class SearchableListOverlay : SearchableListOverlay - { - private readonly Container scrollContainer; - - protected readonly SearchableListHeader Header; - protected readonly SearchableListFilterControl Filter; - protected readonly FillFlowContainer ScrollFlow; - - protected abstract Color4 BackgroundColour { get; } - protected abstract Color4 TrianglesColourLight { get; } - protected abstract Color4 TrianglesColourDark { get; } - protected abstract SearchableListHeader CreateHeader(); - protected abstract SearchableListFilterControl CreateFilterControl(); - - protected SearchableListOverlay() - { - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new[] - { - new Triangles - { - RelativeSizeAxes = Axes.Both, - TriangleScale = 5, - ColourLight = TrianglesColourLight, - ColourDark = TrianglesColourDark, - }, - }, - }, - scrollContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new[] - { - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Children = new[] - { - ScrollFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = WIDTH_PADDING, Bottom = 50 }, - Direction = FillDirection.Vertical, - }, - }, - }, - }, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header = CreateHeader(), - Filter = CreateFilterControl(), - }, - }, - }; - - Filter.Search.Exit = Hide; - } - - protected override void Update() - { - base.Update(); - - scrollContainer.Padding = new MarginPadding { Top = Header.Height + Filter.Height }; - } - - protected override void OnFocus(InputState state) - { - GetContainingInputManager().ChangeFocus(Filter.Search); - } - - protected override void PopIn() - { - base.PopIn(); - - Filter.Search.HoldFocus = true; - } - - protected override void PopOut() - { - base.PopOut(); - - Filter.Search.HoldFocus = false; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.SearchableList +{ + public abstract class SearchableListOverlay : WaveOverlayContainer + { + public static readonly float WIDTH_PADDING = 80; + } + + public abstract class SearchableListOverlay : SearchableListOverlay + { + private readonly Container scrollContainer; + + protected readonly SearchableListHeader Header; + protected readonly SearchableListFilterControl Filter; + protected readonly FillFlowContainer ScrollFlow; + + protected abstract Color4 BackgroundColour { get; } + protected abstract Color4 TrianglesColourLight { get; } + protected abstract Color4 TrianglesColourDark { get; } + protected abstract SearchableListHeader CreateHeader(); + protected abstract SearchableListFilterControl CreateFilterControl(); + + protected SearchableListOverlay() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = BackgroundColour, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new[] + { + new Triangles + { + RelativeSizeAxes = Axes.Both, + TriangleScale = 5, + ColourLight = TrianglesColourLight, + ColourDark = TrianglesColourDark, + }, + }, + }, + scrollContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Children = new[] + { + ScrollFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = WIDTH_PADDING, Bottom = 50 }, + Direction = FillDirection.Vertical, + }, + }, + }, + }, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + Header = CreateHeader(), + Filter = CreateFilterControl(), + }, + }, + }; + + Filter.Search.Exit = Hide; + } + + protected override void Update() + { + base.Update(); + + scrollContainer.Padding = new MarginPadding { Top = Header.Height + Filter.Height }; + } + + protected override void OnFocus(InputState state) + { + GetContainingInputManager().ChangeFocus(Filter.Search); + } + + protected override void PopIn() + { + base.PopIn(); + + Filter.Search.HoldFocus = true; + } + + protected override void PopOut() + { + base.PopOut(); + + Filter.Search.HoldFocus = false; + } + } +} diff --git a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs index a528a3c35d..33ddb8f53f 100644 --- a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs +++ b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; -using OpenTK; - -namespace osu.Game.Overlays.SearchableList -{ - public class SlimEnumDropdown : OsuEnumDropdown - { - protected override DropdownHeader CreateHeader() => new SlimDropdownHeader(); - - protected override DropdownMenu CreateMenu() => new SlimMenu(); - - private class SlimDropdownHeader : OsuDropdownHeader - { - public SlimDropdownHeader() - { - Height = 25; - Icon.Size = new Vector2(16); - Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - BackgroundColour = Color4.Black.Opacity(0.25f); - } - } - - private class SlimMenu : OsuDropdownMenu - { - public SlimMenu() - { - BackgroundColour = Color4.Black.Opacity(0.7f); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using OpenTK; + +namespace osu.Game.Overlays.SearchableList +{ + public class SlimEnumDropdown : OsuEnumDropdown + { + protected override DropdownHeader CreateHeader() => new SlimDropdownHeader(); + + protected override DropdownMenu CreateMenu() => new SlimMenu(); + + private class SlimDropdownHeader : OsuDropdownHeader + { + public SlimDropdownHeader() + { + Height = 25; + Icon.Size = new Vector2(16); + Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + BackgroundColour = Color4.Black.Opacity(0.25f); + } + } + + private class SlimMenu : OsuDropdownMenu + { + public SlimMenu() + { + BackgroundColour = Color4.Black.Opacity(0.7f); + } + } + } +} diff --git a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs index 69c0c03552..9eb68b416a 100644 --- a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs +++ b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Settings -{ - /// - /// A with pink colours to mark dangerous/destructive actions. - /// - public class DangerousSettingsButton : SettingsButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Pink; - - Triangles.ColourDark = colours.PinkDark; - Triangles.ColourLight = colours.PinkLight; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Settings +{ + /// + /// A with pink colours to mark dangerous/destructive actions. + /// + public class DangerousSettingsButton : SettingsButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Pink; + + Triangles.ColourDark = colours.PinkDark; + Triangles.ColourLight = colours.PinkLight; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index 5260fb89d4..d637eb96f6 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -1,73 +1,73 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Graphics; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Overlays.Settings.Sections.Audio -{ - public class AudioDevicesSettings : SettingsSubsection - { - protected override string Header => "Devices"; - - private AudioManager audio; - private SettingsDropdown dropdown; - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - this.audio = audio; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (audio != null) - { - audio.OnNewDevice -= onDeviceChanged; - audio.OnLostDevice -= onDeviceChanged; - } - } - - private void updateItems() - { - var deviceItems = new List> { new KeyValuePair("Default", string.Empty) }; - deviceItems.AddRange(audio.AudioDeviceNames.Select(d => new KeyValuePair(d, d))); - - var preferredDeviceName = audio.AudioDevice.Value; - if (deviceItems.All(kv => kv.Value != preferredDeviceName)) - deviceItems.Add(new KeyValuePair(preferredDeviceName, preferredDeviceName)); - - // The option dropdown for audio device selection lists all audio - // device names. Dropdowns, however, may not have multiple identical - // keys. Thus, we remove duplicate audio device names from - // the dropdown. BASS does not give us a simple mechanism to select - // specific audio devices in such a case anyways. Such - // functionality would require involved OS-specific code. - dropdown.Items = deviceItems.Distinct().ToList(); - } - - private void onDeviceChanged(string name) => updateItems(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - Children = new Drawable[] - { - dropdown = new SettingsDropdown() - }; - - updateItems(); - - dropdown.Bindable = audio.AudioDevice; - - audio.OnNewDevice += onDeviceChanged; - audio.OnLostDevice += onDeviceChanged; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + public class AudioDevicesSettings : SettingsSubsection + { + protected override string Header => "Devices"; + + private AudioManager audio; + private SettingsDropdown dropdown; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + this.audio = audio; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (audio != null) + { + audio.OnNewDevice -= onDeviceChanged; + audio.OnLostDevice -= onDeviceChanged; + } + } + + private void updateItems() + { + var deviceItems = new List> { new KeyValuePair("Default", string.Empty) }; + deviceItems.AddRange(audio.AudioDeviceNames.Select(d => new KeyValuePair(d, d))); + + var preferredDeviceName = audio.AudioDevice.Value; + if (deviceItems.All(kv => kv.Value != preferredDeviceName)) + deviceItems.Add(new KeyValuePair(preferredDeviceName, preferredDeviceName)); + + // The option dropdown for audio device selection lists all audio + // device names. Dropdowns, however, may not have multiple identical + // keys. Thus, we remove duplicate audio device names from + // the dropdown. BASS does not give us a simple mechanism to select + // specific audio devices in such a case anyways. Such + // functionality would require involved OS-specific code. + dropdown.Items = deviceItems.Distinct().ToList(); + } + + private void onDeviceChanged(string name) => updateItems(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + Children = new Drawable[] + { + dropdown = new SettingsDropdown() + }; + + updateItems(); + + dropdown.Bindable = audio.AudioDevice; + + audio.OnNewDevice += onDeviceChanged; + audio.OnLostDevice += onDeviceChanged; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index ad1eec3c68..35f0a19796 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Audio -{ - public class MainMenuSettings : SettingsSubsection - { - protected override string Header => "Main Menu"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new[] - { - new SettingsCheckbox - { - LabelText = "Interface voices", - Bindable = config.GetBindable(OsuSetting.MenuVoice) - }, - new SettingsCheckbox - { - LabelText = "osu! music theme", - Bindable = config.GetBindable(OsuSetting.MenuMusic) - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + public class MainMenuSettings : SettingsSubsection + { + protected override string Header => "Main Menu"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new[] + { + new SettingsCheckbox + { + LabelText = "Interface voices", + Bindable = config.GetBindable(OsuSetting.MenuVoice) + }, + new SettingsCheckbox + { + LabelText = "osu! music theme", + Bindable = config.GetBindable(OsuSetting.MenuMusic) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 598195333c..8850f716e0 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings.Sections.Audio -{ - public class OffsetSettings : SettingsSubsection - { - protected override string Header => "Offset Adjustment"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsSlider - { - LabelText = "Audio Offset", - Bindable = config.GetBindable(OsuSetting.AudioOffset), - KeyboardStep = 100f - }, - new SettingsButton - { - Text = "Offset wizard" - } - }; - } - - private class OffsetSlider : OsuSliderBar - { - public override string TooltipText => Current.Value.ToString(@"0ms"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + public class OffsetSettings : SettingsSubsection + { + protected override string Header => "Offset Adjustment"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "Audio Offset", + Bindable = config.GetBindable(OsuSetting.AudioOffset), + KeyboardStep = 100f + }, + new SettingsButton + { + Text = "Offset wizard" + } + }; + } + + private class OffsetSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0ms"); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index 92ee01dd7a..80896c163f 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Graphics; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Audio -{ - public class VolumeSettings : SettingsSubsection - { - protected override string Header => "Volume"; - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsSlider { LabelText = "Master", Bindable = audio.Volume, KeyboardStep = 0.1f }, - new SettingsSlider { LabelText = "Master (Window Inactive)", Bindable = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.1f }, - new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.1f }, - new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.1f }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + public class VolumeSettings : SettingsSubsection + { + protected override string Header => "Volume"; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsSlider { LabelText = "Master", Bindable = audio.Volume, KeyboardStep = 0.1f }, + new SettingsSlider { LabelText = "Master (Window Inactive)", Bindable = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.1f }, + new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.1f }, + new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.1f }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index ad074f6328..d9326b686f 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.Audio; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class AudioSection : SettingsSection - { - public override string Header => "Audio"; - public override FontAwesome Icon => FontAwesome.fa_volume_up; - - public AudioSection() - { - Children = new Drawable[] - { - new AudioDevicesSettings(), - new VolumeSettings(), - new OffsetSettings(), - new MainMenuSettings(), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Audio; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class AudioSection : SettingsSection + { + public override string Header => "Audio"; + public override FontAwesome Icon => FontAwesome.fa_volume_up; + + public AudioSection() + { + Children = new Drawable[] + { + new AudioDevicesSettings(), + new VolumeSettings(), + new OffsetSettings(), + new MainMenuSettings(), + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs index 9a5946bf4e..9f550413f3 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Runtime; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; - -namespace osu.Game.Overlays.Settings.Sections.Debug -{ - public class GCSettings : SettingsSubsection - { - protected override string Header => "Garbage Collector"; - - [BackgroundDependencyLoader] - private void load(FrameworkDebugConfigManager config) - { - Children = new Drawable[] - { - new SettingsEnumDropdown - { - LabelText = "Active mode", - Bindable = config.GetBindable(DebugSetting.ActiveGCMode) - }, - new SettingsButton - { - Text = "Force garbage collection", - Action = GC.Collect - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Runtime; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; + +namespace osu.Game.Overlays.Settings.Sections.Debug +{ + public class GCSettings : SettingsSubsection + { + protected override string Header => "Garbage Collector"; + + [BackgroundDependencyLoader] + private void load(FrameworkDebugConfigManager config) + { + Children = new Drawable[] + { + new SettingsEnumDropdown + { + LabelText = "Active mode", + Bindable = config.GetBindable(DebugSetting.ActiveGCMode) + }, + new SettingsButton + { + Text = "Force garbage collection", + Action = GC.Collect + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index ba591b9456..8b09a2a26a 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -1,37 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; - -namespace osu.Game.Overlays.Settings.Sections.Debug -{ - public class GeneralSettings : SettingsSubsection - { - protected override string Header => "General"; - - [BackgroundDependencyLoader] - private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Show log overlay", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) - }, - new SettingsCheckbox - { - LabelText = "Performance logging", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.PerformanceLogging) - }, - new SettingsCheckbox - { - LabelText = "Bypass caching (slow)", - Bindable = config.GetBindable(DebugSetting.BypassCaching) - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; + +namespace osu.Game.Overlays.Settings.Sections.Debug +{ + public class GeneralSettings : SettingsSubsection + { + protected override string Header => "General"; + + [BackgroundDependencyLoader] + private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Show log overlay", + Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) + }, + new SettingsCheckbox + { + LabelText = "Performance logging", + Bindable = frameworkConfig.GetBindable(FrameworkSetting.PerformanceLogging) + }, + new SettingsCheckbox + { + LabelText = "Bypass caching (slow)", + Bindable = config.GetBindable(DebugSetting.BypassCaching) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index fb4d4f62a8..0502e98192 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.Debug; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class DebugSection : SettingsSection - { - public override string Header => "Debug"; - public override FontAwesome Icon => FontAwesome.fa_bug; - - public DebugSection() - { - Children = new Drawable[] - { - new GeneralSettings(), - new GCSettings(), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Debug; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class DebugSection : SettingsSection + { + public override string Header => "Debug"; + public override FontAwesome Icon => FontAwesome.fa_bug; + + public DebugSection() + { + Children = new Drawable[] + { + new GeneralSettings(), + new GCSettings(), + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index b60b0d9531..647395cf69 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -1,44 +1,44 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Gameplay -{ - public class GeneralSettings : SettingsSubsection - { - protected override string Header => "General"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsSlider - { - LabelText = "Background dim", - Bindable = config.GetBindable(OsuSetting.DimLevel), - KeyboardStep = 0.1f - }, - new SettingsSlider - { - LabelText = "Background blur", - Bindable = config.GetBindable(OsuSetting.BlurLevel), - KeyboardStep = 0.1f - }, - new SettingsCheckbox - { - LabelText = "Show score overlay", - Bindable = config.GetBindable(OsuSetting.ShowInterface) - }, - new SettingsCheckbox - { - LabelText = "Always show key overlay", - Bindable = config.GetBindable(OsuSetting.KeyOverlay) - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Gameplay +{ + public class GeneralSettings : SettingsSubsection + { + protected override string Header => "General"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "Background dim", + Bindable = config.GetBindable(OsuSetting.DimLevel), + KeyboardStep = 0.1f + }, + new SettingsSlider + { + LabelText = "Background blur", + Bindable = config.GetBindable(OsuSetting.BlurLevel), + KeyboardStep = 0.1f + }, + new SettingsCheckbox + { + LabelText = "Show score overlay", + Bindable = config.GetBindable(OsuSetting.ShowInterface) + }, + new SettingsCheckbox + { + LabelText = "Always show key overlay", + Bindable = config.GetBindable(OsuSetting.KeyOverlay) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs index 4e8706137c..0e661aeba6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Gameplay -{ - public class ScrollingSettings : SettingsSubsection - { - protected override string Header => "Scrolling"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new[] - { - new SettingsEnumDropdown - { - LabelText = "Visualise speed changes as", - Bindable = config.GetBindable(OsuSetting.SpeedChangeVisualisation), - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Gameplay +{ + public class ScrollingSettings : SettingsSubsection + { + protected override string Header => "Scrolling"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new[] + { + new SettingsEnumDropdown + { + LabelText = "Visualise speed changes as", + Bindable = config.GetBindable(OsuSetting.SpeedChangeVisualisation), + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 4bbd87c7e6..7575105ef7 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -1,50 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings.Sections.Gameplay -{ - public class SongSelectSettings : SettingsSubsection - { - protected override string Header => "Song Select"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Show converted beatmaps", - Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - }, - new SettingsSlider - { - LabelText = "Display beatmaps from", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), - KeyboardStep = 1f - }, - new SettingsSlider - { - LabelText = "up to", - Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), - KeyboardStep = 1f - }, - new SettingsEnumDropdown - { - LabelText = "Random selection algorithm", - Bindable = config.GetBindable(OsuSetting.RandomSelectAlgorithm), - } - }; - } - - private class StarSlider : OsuSliderBar - { - public override string TooltipText => Current.Value.ToString(@"0.## stars"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings.Sections.Gameplay +{ + public class SongSelectSettings : SettingsSubsection + { + protected override string Header => "Song Select"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Show converted beatmaps", + Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + }, + new SettingsSlider + { + LabelText = "Display beatmaps from", + Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), + KeyboardStep = 1f + }, + new SettingsSlider + { + LabelText = "up to", + Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), + KeyboardStep = 1f + }, + new SettingsEnumDropdown + { + LabelText = "Random selection algorithm", + Bindable = config.GetBindable(OsuSetting.RandomSelectAlgorithm), + } + }; + } + + private class StarSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0.## stars"); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index 8a2131fb1c..3851a73901 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.Gameplay; -using osu.Game.Rulesets; -using System.Linq; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class GameplaySection : SettingsSection - { - public override string Header => "Gameplay"; - public override FontAwesome Icon => FontAwesome.fa_circle_o; - - public GameplaySection() - { - Children = new Drawable[] - { - new GeneralSettings(), - new SongSelectSettings(), - new ScrollingSettings() - }; - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - foreach(Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) - { - SettingsSubsection section = ruleset.CreateSettings(); - if (section != null) - Add(section); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Gameplay; +using osu.Game.Rulesets; +using System.Linq; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class GameplaySection : SettingsSection + { + public override string Header => "Gameplay"; + public override FontAwesome Icon => FontAwesome.fa_circle_o; + + public GameplaySection() + { + Children = new Drawable[] + { + new GeneralSettings(), + new SongSelectSettings(), + new ScrollingSettings() + }; + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + foreach(Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) + { + SettingsSubsection section = ruleset.CreateSettings(); + if (section != null) + Add(section); + } + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index 113ef10bad..977d75079b 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; - -namespace osu.Game.Overlays.Settings.Sections.General -{ - public class LanguageSettings : SettingsSubsection - { - protected override string Header => "Language"; - - [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfig) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Prefer metadata in original language", - Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode) - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; + +namespace osu.Game.Overlays.Settings.Sections.General +{ + public class LanguageSettings : SettingsSubsection + { + protected override string Header => "Language"; + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfig) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Prefer metadata in original language", + Bindable = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 2d07704853..9a42bdd2aa 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -1,379 +1,379 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Configuration; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using OpenTK; -using osu.Framework.Input; -using osu.Game.Users; -using System.ComponentModel; -using osu.Game.Graphics; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; -using Container = osu.Framework.Graphics.Containers.Container; - -namespace osu.Game.Overlays.Settings.Sections.General -{ - public class LoginSettings : FillFlowContainer, IOnlineComponent - { - private bool bounding = true; - private LoginForm form; - private OsuColour colours; - - private UserPanel panel; - private UserDropdown dropdown; - - /// - /// Called to request a hide of a parent displaying this container. - /// - public Action RequestHide; - - public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty; - - public bool Bounding - { - get { return bounding; } - set - { - bounding = value; - Invalidate(Invalidation.MiscGeometry); - } - } - - public LoginSettings() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0f, 5f); - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, APIAccess api) - { - this.colours = colours; - - api?.Register(this); - } - - public void APIStateChanged(APIAccess api, APIState state) - { - form = null; - - switch (state) - { - case APIState.Offline: - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "ACCOUNT", - Margin = new MarginPadding { Bottom = 5 }, - Font = @"Exo2.0-Black", - }, - form = new LoginForm() - }; - break; - case APIState.Failing: - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Connection failing :(", - }, - }; - break; - case APIState.Connecting: - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Connecting...", - Margin = new MarginPadding { Top = 10, Bottom = 10 }, - }, - }; - break; - case APIState.Online: - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 20, Right = 20 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Signed in", - TextSize = 18, - Font = @"Exo2.0-Bold", - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - }, - }, - }, - panel = new UserPanel(api.LocalUser.Value) - { - RelativeSizeAxes = Axes.X, - Action = RequestHide - }, - dropdown = new UserDropdown { RelativeSizeAxes = Axes.X }, - }, - }, - }; - - panel.Status.BindTo(api.LocalUser.Value.Status); - - dropdown.Current.ValueChanged += newValue => - { - switch (newValue) - { - case UserAction.Online: - api.LocalUser.Value.Status.Value = new UserStatusOnline(); - dropdown.StatusColour = colours.Green; - break; - case UserAction.DoNotDisturb: - api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); - dropdown.StatusColour = colours.Red; - break; - case UserAction.AppearOffline: - api.LocalUser.Value.Status.Value = new UserStatusOffline(); - dropdown.StatusColour = colours.Gray7; - break; - case UserAction.SignOut: - api.Logout(); - break; - } - }; - dropdown.Current.TriggerChange(); - - break; - } - - if (form != null) GetContainingInputManager()?.ChangeFocus(form); - } - - public override bool AcceptsFocus => true; - - protected override bool OnClick(InputState state) => true; - - protected override void OnFocus(InputState state) - { - if (form != null) GetContainingInputManager().ChangeFocus(form); - base.OnFocus(state); - } - - private class LoginForm : FillFlowContainer - { - private TextBox username; - private TextBox password; - private APIAccess api; - - private void performLogin() - { - if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) - api.Login(username.Text, password.Text); - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuConfigManager config) - { - this.api = api; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 5); - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - Children = new Drawable[] - { - username = new OsuTextBox - { - PlaceholderText = "Email address", - RelativeSizeAxes = Axes.X, - Text = api?.ProvidedUsername ?? string.Empty, - TabbableContentContainer = this - }, - password = new OsuPasswordTextBox - { - PlaceholderText = "Password", - RelativeSizeAxes = Axes.X, - TabbableContentContainer = this, - OnCommit = (sender, newText) => performLogin() - }, - new SettingsCheckbox - { - LabelText = "Remember email address", - Bindable = config.GetBindable(OsuSetting.SaveUsername), - }, - new SettingsCheckbox - { - LabelText = "Stay signed in", - Bindable = config.GetBindable(OsuSetting.SavePassword), - }, - new SettingsButton - { - Text = "Sign in", - Action = performLogin - }, - new SettingsButton - { - Text = "Register", - //Action = registerLink - } - }; - } - - public override bool AcceptsFocus => true; - - protected override bool OnClick(InputState state) => true; - - protected override void OnFocus(InputState state) - { - Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); - } - } - - private class UserDropdown : OsuEnumDropdown - { - protected override DropdownHeader CreateHeader() => new UserDropdownHeader(); - - protected override DropdownMenu CreateMenu() => new UserDropdownMenu(); - - public Color4 StatusColour - { - set - { - var h = Header as UserDropdownHeader; - if (h == null) return; - h.StatusColour = value; - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = colours.Gray5; - } - - private class UserDropdownMenu : OsuDropdownMenu - { - public UserDropdownMenu() - { - Masking = true; - CornerRadius = 5; - - Margin = new MarginPadding { Bottom = 5 }; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }; - - ItemsContainer.Padding = new MarginPadding(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Gray3; - } - - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item); - - private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem - { - public DrawableUserDropdownMenuItem(MenuItem item) - : base(item) - { - Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 }; - CornerRadius = 5; - } - - protected override Drawable CreateContent() => new Content - { - Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } } - }; - } - } - - private class UserDropdownHeader : OsuDropdownHeader - { - public const float LABEL_LEFT_MARGIN = 20; - - private readonly SpriteIcon statusIcon; - public Color4 StatusColour - { - set - { - statusIcon.FadeColour(value, 500, Easing.OutQuint); - } - } - - public UserDropdownHeader() - { - Foreground.Padding = new MarginPadding { Left = 10, Right = 10 }; - Margin = new MarginPadding { Bottom = 5 }; - Masking = true; - CornerRadius = 5; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }; - - Icon.Size = new Vector2(14); - Icon.Margin = new MarginPadding(0); - - Foreground.Add(statusIcon = new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, - Size = new Vector2(14), - }); - - Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Gray3; - } - } - } - - private enum UserAction - { - Online, - [Description(@"Do not disturb")] - DoNotDisturb, - [Description(@"Appear offline")] - AppearOffline, - [Description(@"Sign out")] - SignOut, - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using OpenTK; +using osu.Framework.Input; +using osu.Game.Users; +using System.ComponentModel; +using osu.Game.Graphics; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; +using Container = osu.Framework.Graphics.Containers.Container; + +namespace osu.Game.Overlays.Settings.Sections.General +{ + public class LoginSettings : FillFlowContainer, IOnlineComponent + { + private bool bounding = true; + private LoginForm form; + private OsuColour colours; + + private UserPanel panel; + private UserDropdown dropdown; + + /// + /// Called to request a hide of a parent displaying this container. + /// + public Action RequestHide; + + public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty; + + public bool Bounding + { + get { return bounding; } + set + { + bounding = value; + Invalidate(Invalidation.MiscGeometry); + } + } + + public LoginSettings() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(0f, 5f); + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuColour colours, APIAccess api) + { + this.colours = colours; + + api?.Register(this); + } + + public void APIStateChanged(APIAccess api, APIState state) + { + form = null; + + switch (state) + { + case APIState.Offline: + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "ACCOUNT", + Margin = new MarginPadding { Bottom = 5 }, + Font = @"Exo2.0-Black", + }, + form = new LoginForm() + }; + break; + case APIState.Failing: + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Connection failing :(", + }, + }; + break; + case APIState.Connecting: + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Connecting...", + Margin = new MarginPadding { Top = 10, Bottom = 10 }, + }, + }; + break; + case APIState.Online: + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 20, Right = 20 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Signed in", + TextSize = 18, + Font = @"Exo2.0-Bold", + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + }, + }, + }, + panel = new UserPanel(api.LocalUser.Value) + { + RelativeSizeAxes = Axes.X, + Action = RequestHide + }, + dropdown = new UserDropdown { RelativeSizeAxes = Axes.X }, + }, + }, + }; + + panel.Status.BindTo(api.LocalUser.Value.Status); + + dropdown.Current.ValueChanged += newValue => + { + switch (newValue) + { + case UserAction.Online: + api.LocalUser.Value.Status.Value = new UserStatusOnline(); + dropdown.StatusColour = colours.Green; + break; + case UserAction.DoNotDisturb: + api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); + dropdown.StatusColour = colours.Red; + break; + case UserAction.AppearOffline: + api.LocalUser.Value.Status.Value = new UserStatusOffline(); + dropdown.StatusColour = colours.Gray7; + break; + case UserAction.SignOut: + api.Logout(); + break; + } + }; + dropdown.Current.TriggerChange(); + + break; + } + + if (form != null) GetContainingInputManager()?.ChangeFocus(form); + } + + public override bool AcceptsFocus => true; + + protected override bool OnClick(InputState state) => true; + + protected override void OnFocus(InputState state) + { + if (form != null) GetContainingInputManager().ChangeFocus(form); + base.OnFocus(state); + } + + private class LoginForm : FillFlowContainer + { + private TextBox username; + private TextBox password; + private APIAccess api; + + private void performLogin() + { + if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) + api.Login(username.Text, password.Text); + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(APIAccess api, OsuConfigManager config) + { + this.api = api; + Direction = FillDirection.Vertical; + Spacing = new Vector2(0, 5); + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + Children = new Drawable[] + { + username = new OsuTextBox + { + PlaceholderText = "Email address", + RelativeSizeAxes = Axes.X, + Text = api?.ProvidedUsername ?? string.Empty, + TabbableContentContainer = this + }, + password = new OsuPasswordTextBox + { + PlaceholderText = "Password", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, newText) => performLogin() + }, + new SettingsCheckbox + { + LabelText = "Remember email address", + Bindable = config.GetBindable(OsuSetting.SaveUsername), + }, + new SettingsCheckbox + { + LabelText = "Stay signed in", + Bindable = config.GetBindable(OsuSetting.SavePassword), + }, + new SettingsButton + { + Text = "Sign in", + Action = performLogin + }, + new SettingsButton + { + Text = "Register", + //Action = registerLink + } + }; + } + + public override bool AcceptsFocus => true; + + protected override bool OnClick(InputState state) => true; + + protected override void OnFocus(InputState state) + { + Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); + } + } + + private class UserDropdown : OsuEnumDropdown + { + protected override DropdownHeader CreateHeader() => new UserDropdownHeader(); + + protected override DropdownMenu CreateMenu() => new UserDropdownMenu(); + + public Color4 StatusColour + { + set + { + var h = Header as UserDropdownHeader; + if (h == null) return; + h.StatusColour = value; + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Gray5; + } + + private class UserDropdownMenu : OsuDropdownMenu + { + public UserDropdownMenu() + { + Masking = true; + CornerRadius = 5; + + Margin = new MarginPadding { Bottom = 5 }; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }; + + ItemsContainer.Padding = new MarginPadding(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray3; + } + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item); + + private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem + { + public DrawableUserDropdownMenuItem(MenuItem item) + : base(item) + { + Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 }; + CornerRadius = 5; + } + + protected override Drawable CreateContent() => new Content + { + Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } } + }; + } + } + + private class UserDropdownHeader : OsuDropdownHeader + { + public const float LABEL_LEFT_MARGIN = 20; + + private readonly SpriteIcon statusIcon; + public Color4 StatusColour + { + set + { + statusIcon.FadeColour(value, 500, Easing.OutQuint); + } + } + + public UserDropdownHeader() + { + Foreground.Padding = new MarginPadding { Left = 10, Right = 10 }; + Margin = new MarginPadding { Bottom = 5 }; + Masking = true; + CornerRadius = 5; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }; + + Icon.Size = new Vector2(14); + Icon.Margin = new MarginPadding(0); + + Foreground.Add(statusIcon = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_circle_o, + Size = new Vector2(14), + }); + + Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray3; + } + } + } + + private enum UserAction + { + Online, + [Description(@"Do not disturb")] + DoNotDisturb, + [Description(@"Appear offline")] + AppearOffline, + [Description(@"Sign out")] + SignOut, + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index caee7639e4..34d13b1462 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Platform; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.General -{ - public class UpdateSettings : SettingsSubsection - { - protected override string Header => "Updates"; - - [BackgroundDependencyLoader] - private void load(Storage storage, OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsEnumDropdown - { - LabelText = "Release stream", - Bindable = config.GetBindable(OsuSetting.ReleaseStream), - }, - new SettingsButton - { - Text = "Open osu! folder", - Action = storage.OpenInNativeExplorer, - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.General +{ + public class UpdateSettings : SettingsSubsection + { + protected override string Header => "Updates"; + + [BackgroundDependencyLoader] + private void load(Storage storage, OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsEnumDropdown + { + LabelText = "Release stream", + Bindable = config.GetBindable(OsuSetting.ReleaseStream), + }, + new SettingsButton + { + Text = "Open osu! folder", + Action = storage.OpenInNativeExplorer, + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index f7dfd9471b..f75f01c034 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.General; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class GeneralSection : SettingsSection - { - public override string Header => "General"; - public override FontAwesome Icon => FontAwesome.fa_gear; - - public GeneralSection() - { - Children = new Drawable[] - { - new LanguageSettings(), - new UpdateSettings(), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.General; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class GeneralSection : SettingsSection + { + public override string Header => "General"; + public override FontAwesome Icon => FontAwesome.fa_gear; + + public GeneralSection() + { + Children = new Drawable[] + { + new LanguageSettings(), + new UpdateSettings(), + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index fa57a85454..a78cb29468 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -1,37 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Graphics -{ - public class DetailSettings : SettingsSubsection - { - protected override string Header => "Detail Settings"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Storyboards", - Bindable = config.GetBindable(OsuSetting.ShowStoryboard) - }, - new SettingsCheckbox - { - LabelText = "Rotate cursor when dragging", - Bindable = config.GetBindable(OsuSetting.CursorRotation) - }, - new SettingsEnumDropdown - { - LabelText = "Screenshot format", - Bindable = config.GetBindable(OsuSetting.ScreenshotFormat) - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Graphics +{ + public class DetailSettings : SettingsSubsection + { + protected override string Header => "Detail Settings"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Storyboards", + Bindable = config.GetBindable(OsuSetting.ShowStoryboard) + }, + new SettingsCheckbox + { + LabelText = "Rotate cursor when dragging", + Bindable = config.GetBindable(OsuSetting.CursorRotation) + }, + new SettingsEnumDropdown + { + LabelText = "Screenshot format", + Bindable = config.GetBindable(OsuSetting.ScreenshotFormat) + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 8f775e686a..1f87a635de 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -1,76 +1,76 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Overlays.Settings.Sections.Graphics -{ - public class LayoutSettings : SettingsSubsection - { - protected override string Header => "Layout"; - - private FillFlowContainer letterboxSettings; - - private Bindable letterboxing; - - private const int transition_duration = 400; - - [BackgroundDependencyLoader] - private void load(FrameworkConfigManager config) - { - letterboxing = config.GetBindable(FrameworkSetting.Letterboxing); - - Children = new Drawable[] - { - new SettingsEnumDropdown - { - LabelText = "Screen mode", - Bindable = config.GetBindable(FrameworkSetting.WindowMode), - }, - new SettingsCheckbox - { - LabelText = "Letterboxing", - Bindable = letterboxing, - }, - letterboxSettings = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - AutoSizeDuration = transition_duration, - AutoSizeEasing = Easing.OutQuint, - Masking = true, - - Children = new Drawable[] - { - new SettingsSlider - { - LabelText = "Horizontal position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX), - KeyboardStep = 0.1f - }, - new SettingsSlider - { - LabelText = "Vertical position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY), - KeyboardStep = 0.1f - }, - } - }, - }; - - letterboxing.ValueChanged += isVisible => - { - letterboxSettings.ClearTransforms(); - letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; - - if (!isVisible) - letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); - }; - letterboxing.TriggerChange(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Overlays.Settings.Sections.Graphics +{ + public class LayoutSettings : SettingsSubsection + { + protected override string Header => "Layout"; + + private FillFlowContainer letterboxSettings; + + private Bindable letterboxing; + + private const int transition_duration = 400; + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager config) + { + letterboxing = config.GetBindable(FrameworkSetting.Letterboxing); + + Children = new Drawable[] + { + new SettingsEnumDropdown + { + LabelText = "Screen mode", + Bindable = config.GetBindable(FrameworkSetting.WindowMode), + }, + new SettingsCheckbox + { + LabelText = "Letterboxing", + Bindable = letterboxing, + }, + letterboxSettings = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = transition_duration, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "Horizontal position", + Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX), + KeyboardStep = 0.1f + }, + new SettingsSlider + { + LabelText = "Vertical position", + Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY), + KeyboardStep = 0.1f + }, + } + }, + }; + + letterboxing.ValueChanged += isVisible => + { + letterboxSettings.ClearTransforms(); + letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + + if (!isVisible) + letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + }; + letterboxing.TriggerChange(); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs index e779538fc8..71d2b31946 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Graphics -{ - public class MainMenuSettings : SettingsSubsection - { - protected override string Header => "User Interface"; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new[] - { - new SettingsCheckbox - { - LabelText = "Parallax", - Bindable = config.GetBindable(OsuSetting.MenuParallax) - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Graphics +{ + public class MainMenuSettings : SettingsSubsection + { + protected override string Header => "User Interface"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new[] + { + new SettingsCheckbox + { + LabelText = "Parallax", + Bindable = config.GetBindable(OsuSetting.MenuParallax) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 7e9ff018c4..5f3c7aa7e9 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Game.Configuration; - -namespace osu.Game.Overlays.Settings.Sections.Graphics -{ - public class RendererSettings : SettingsSubsection - { - protected override string Header => "Renderer"; - - [BackgroundDependencyLoader] - private void load(FrameworkConfigManager config, OsuConfigManager osuConfig) - { - // NOTE: Compatability mode omitted - Children = new Drawable[] - { - // TODO: this needs to be a custom dropdown at some point - new SettingsEnumDropdown - { - LabelText = "Frame limiter", - Bindable = config.GetBindable(FrameworkSetting.FrameSync) - }, - new SettingsCheckbox - { - LabelText = "Show FPS", - Bindable = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Graphics +{ + public class RendererSettings : SettingsSubsection + { + protected override string Header => "Renderer"; + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager config, OsuConfigManager osuConfig) + { + // NOTE: Compatability mode omitted + Children = new Drawable[] + { + // TODO: this needs to be a custom dropdown at some point + new SettingsEnumDropdown + { + LabelText = "Frame limiter", + Bindable = config.GetBindable(FrameworkSetting.FrameSync) + }, + new SettingsCheckbox + { + LabelText = "Show FPS", + Bindable = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index a34a76ff40..1ce25bf517 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.Graphics; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class GraphicsSection : SettingsSection - { - public override string Header => "Graphics"; - public override FontAwesome Icon => FontAwesome.fa_laptop; - - public GraphicsSection() - { - Children = new Drawable[] - { - new RendererSettings(), - new LayoutSettings(), - new DetailSettings(), - new MainMenuSettings(), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Graphics; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class GraphicsSection : SettingsSection + { + public override string Header => "Graphics"; + public override FontAwesome Icon => FontAwesome.fa_laptop; + + public GraphicsSection() + { + Children = new Drawable[] + { + new RendererSettings(), + new LayoutSettings(), + new DetailSettings(), + new MainMenuSettings(), + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs index 49ac650f92..456d1c9a2f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; - -namespace osu.Game.Overlays.Settings.Sections.Input -{ - public class KeyboardSettings : SettingsSubsection - { - protected override string Header => "Keyboard"; - - public KeyboardSettings(KeyBindingOverlay keyConfig) - { - Children = new Drawable[] - { - new SettingsButton - { - Text = "Key Configuration", - Action = keyConfig.ToggleVisibility - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; + +namespace osu.Game.Overlays.Settings.Sections.Input +{ + public class KeyboardSettings : SettingsSubsection + { + protected override string Header => "Keyboard"; + + public KeyboardSettings(KeyBindingOverlay keyConfig) + { + Children = new Drawable[] + { + new SettingsButton + { + Text = "Key Configuration", + Action = keyConfig.ToggleVisibility + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index c368b8fea7..51a624330b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -1,145 +1,143 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Input; -using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings.Sections.Input -{ - public class MouseSettings : SettingsSubsection - { - protected override string Header => "Mouse"; - - private readonly BindableBool rawInputToggle = new BindableBool(); - private Bindable activeInputHandlers; - private SensitivitySetting sensitivity; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Raw Input", - Bindable = rawInputToggle - }, - sensitivity = new SensitivitySetting - { - LabelText = "Cursor Sensitivity", - Bindable = config.GetBindable(FrameworkSetting.CursorSensitivity) - }, - new SettingsCheckbox - { - LabelText = "Map absolute input to window", - Bindable = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) - }, - new SettingsEnumDropdown - { - LabelText = "Confine mouse cursor to window", - Bindable = config.GetBindable(FrameworkSetting.ConfineMouseMode), - }, - new SettingsCheckbox - { - LabelText = "Disable mouse wheel during gameplay", - Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) - }, - new SettingsCheckbox - { - LabelText = "Disable mouse buttons during gameplay", - Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) - }, - }; - - rawInputToggle.ValueChanged += enabled => - { - // this is temporary until we support per-handler settings. - const string raw_mouse_handler = @"OpenTKRawMouseHandler"; - const string standard_mouse_handler = @"OpenTKMouseHandler"; - - activeInputHandlers.Value = enabled ? - activeInputHandlers.Value.Replace(standard_mouse_handler, raw_mouse_handler) : - activeInputHandlers.Value.Replace(raw_mouse_handler, standard_mouse_handler); - }; - - activeInputHandlers = config.GetBindable(FrameworkSetting.ActiveInputHandlers); - activeInputHandlers.ValueChanged += handlers => - { - bool raw = handlers.Contains("Raw"); - rawInputToggle.Value = raw; - sensitivity.Bindable.Disabled = !raw; - }; - - activeInputHandlers.TriggerChange(); - } - - private class SensitivitySetting : SettingsSlider - { - public override Bindable Bindable - { - get { return ((SensitivitySlider)Control).Sensitivity; } - - set - { - BindableDouble doubleValue = (BindableDouble)value; - - // create a second layer of bindable so we can only handle state changes when not being dragged. - ((SensitivitySlider)Control).Sensitivity = doubleValue; - - // this bindable will still act as the "interactive" bindable displayed during a drag. - base.Bindable = new BindableDouble(doubleValue.Value) - { - Default = doubleValue.Default, - MinValue = doubleValue.MinValue, - MaxValue = doubleValue.MaxValue - }; - - // one-way binding to update the sliderbar with changes from external actions. - doubleValue.DisabledChanged += disabled => base.Bindable.Disabled = disabled; - doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue; - } - } - - public SensitivitySetting() - { - KeyboardStep = 0.01f; - } - } - - private class SensitivitySlider : OsuSliderBar - { - public Bindable Sensitivity; - - public SensitivitySlider() - { - Current.ValueChanged += newValue => - { - if (!isDragging && Sensitivity != null) - Sensitivity.Value = newValue; - }; - } - - private bool isDragging; - - protected override bool OnDragStart(InputState state) - { - isDragging = true; - return base.OnDragStart(state); - } - - protected override bool OnDragEnd(InputState state) - { - isDragging = false; - Current.TriggerChange(); - - return base.OnDragEnd(state); - } - - public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings.Sections.Input +{ + public class MouseSettings : SettingsSubsection + { + protected override string Header => "Mouse"; + + private readonly BindableBool rawInputToggle = new BindableBool(); + private Bindable ignoredInputHandler; + private SensitivitySetting sensitivity; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Raw Input", + Bindable = rawInputToggle + }, + sensitivity = new SensitivitySetting + { + LabelText = "Cursor Sensitivity", + Bindable = config.GetBindable(FrameworkSetting.CursorSensitivity) + }, + new SettingsCheckbox + { + LabelText = "Map absolute input to window", + Bindable = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) + }, + new SettingsEnumDropdown + { + LabelText = "Confine mouse cursor to window", + Bindable = config.GetBindable(FrameworkSetting.ConfineMouseMode), + }, + new SettingsCheckbox + { + LabelText = "Disable mouse wheel during gameplay", + Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableWheel) + }, + new SettingsCheckbox + { + LabelText = "Disable mouse buttons during gameplay", + Bindable = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) + }, + }; + + rawInputToggle.ValueChanged += enabled => + { + // this is temporary until we support per-handler settings. + const string raw_mouse_handler = @"OpenTKRawMouseHandler"; + const string standard_mouse_handler = @"OpenTKMouseHandler"; + + ignoredInputHandler.Value = enabled ? standard_mouse_handler : raw_mouse_handler; + }; + + ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); + ignoredInputHandler.ValueChanged += handler => + { + bool raw = !handler.Contains("Raw"); + rawInputToggle.Value = raw; + sensitivity.Bindable.Disabled = !raw; + }; + + ignoredInputHandler.TriggerChange(); + } + + private class SensitivitySetting : SettingsSlider + { + public override Bindable Bindable + { + get { return ((SensitivitySlider)Control).Sensitivity; } + + set + { + BindableDouble doubleValue = (BindableDouble)value; + + // create a second layer of bindable so we can only handle state changes when not being dragged. + ((SensitivitySlider)Control).Sensitivity = doubleValue; + + // this bindable will still act as the "interactive" bindable displayed during a drag. + base.Bindable = new BindableDouble(doubleValue.Value) + { + Default = doubleValue.Default, + MinValue = doubleValue.MinValue, + MaxValue = doubleValue.MaxValue + }; + + // one-way binding to update the sliderbar with changes from external actions. + doubleValue.DisabledChanged += disabled => base.Bindable.Disabled = disabled; + doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue; + } + } + + public SensitivitySetting() + { + KeyboardStep = 0.01f; + } + } + + private class SensitivitySlider : OsuSliderBar + { + public Bindable Sensitivity; + + public SensitivitySlider() + { + Current.ValueChanged += newValue => + { + if (!isDragging && Sensitivity != null) + Sensitivity.Value = newValue; + }; + } + + private bool isDragging; + + protected override bool OnDragStart(InputState state) + { + isDragging = true; + return base.OnDragStart(state); + } + + protected override bool OnDragEnd(InputState state) + { + isDragging = false; + Current.TriggerChange(); + + return base.OnDragEnd(state); + } + + public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x"); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index ae4167fe52..df94c1ad08 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.Input; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class InputSection : SettingsSection - { - public override string Header => "Input"; - public override FontAwesome Icon => FontAwesome.fa_keyboard_o; - - public InputSection(KeyBindingOverlay keyConfig) - { - Children = new Drawable[] - { - new MouseSettings(), - new KeyboardSettings(keyConfig), - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Input; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class InputSection : SettingsSection + { + public override string Header => "Input"; + public override FontAwesome Icon => FontAwesome.fa_keyboard_o; + + public InputSection(KeyBindingOverlay keyConfig) + { + Children = new Drawable[] + { + new MouseSettings(), + new KeyboardSettings(keyConfig), + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs index 027b5b5aeb..ebabdd7a5d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -1,32 +1,32 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; -using osu.Game.Overlays.Dialog; - -namespace osu.Game.Overlays.Settings.Sections.Maintenance -{ - public class DeleteAllBeatmapsDialog : PopupDialog - { - public DeleteAllBeatmapsDialog(Action deleteAction) - { - BodyText = "Everything?"; - - Icon = FontAwesome.fa_trash_o; - HeaderText = @"Confirm deletion of"; - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Yes. Go for it.", - Action = deleteAction - }, - new PopupDialogCancelButton - { - Text = @"No! Abort mission!", - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class DeleteAllBeatmapsDialog : PopupDialog + { + public DeleteAllBeatmapsDialog(Action deleteAction) + { + BodyText = "Everything?"; + + Icon = FontAwesome.fa_trash_o; + HeaderText = @"Confirm deletion of"; + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = deleteAction + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!", + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index d9fedd0225..93d1986e3a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -1,73 +1,73 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using System.Threading.Tasks; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings.Sections.Maintenance -{ - public class GeneralSettings : SettingsSubsection - { - private TriangleButton importButton; - private TriangleButton deleteButton; - private TriangleButton restoreButton; - private TriangleButton undeleteButton; - - protected override string Header => "General"; - - [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, DialogOverlay dialogOverlay) - { - Children = new Drawable[] - { - importButton = new SettingsButton - { - Text = "Import beatmaps from stable", - Action = () => - { - importButton.Enabled.Value = false; - beatmaps.ImportFromStable().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true)); - } - }, - deleteButton = new DangerousSettingsButton - { - Text = "Delete ALL beatmaps", - Action = () => - { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => - { - deleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); - })); - } - }, - restoreButton = new SettingsButton - { - Text = "Restore all hidden difficulties", - Action = () => - { - restoreButton.Enabled.Value = false; - Task.Run(() => - { - foreach (var b in beatmaps.QueryBeatmaps(b => b.Hidden).ToList()) - beatmaps.Restore(b); - }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); - } - }, - undeleteButton = new SettingsButton - { - Text = "Restore all recently deleted beatmaps", - Action = () => - { - undeleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); - } - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class GeneralSettings : SettingsSubsection + { + private TriangleButton importButton; + private TriangleButton deleteButton; + private TriangleButton restoreButton; + private TriangleButton undeleteButton; + + protected override string Header => "General"; + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps, DialogOverlay dialogOverlay) + { + Children = new Drawable[] + { + importButton = new SettingsButton + { + Text = "Import beatmaps from stable", + Action = () => + { + importButton.Enabled.Value = false; + beatmaps.ImportFromStable().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true)); + } + }, + deleteButton = new DangerousSettingsButton + { + Text = "Delete ALL beatmaps", + Action = () => + { + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => + { + deleteButton.Enabled.Value = false; + Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + })); + } + }, + restoreButton = new SettingsButton + { + Text = "Restore all hidden difficulties", + Action = () => + { + restoreButton.Enabled.Value = false; + Task.Run(() => + { + foreach (var b in beatmaps.QueryBeatmaps(b => b.Hidden).ToList()) + beatmaps.Restore(b); + }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + } + }, + undeleteButton = new SettingsButton + { + Text = "Restore all recently deleted beatmaps", + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 808b32f881..aa933ca188 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings.Sections.Maintenance; -using OpenTK; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class MaintenanceSection : SettingsSection - { - public override string Header => "Maintenance"; - public override FontAwesome Icon => FontAwesome.fa_wrench; - - public MaintenanceSection() - { - FlowContent.Spacing = new Vector2(0, 5); - Children = new Drawable[] - { - new GeneralSettings() - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Maintenance; +using OpenTK; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class MaintenanceSection : SettingsSection + { + public override string Header => "Maintenance"; + public override FontAwesome Icon => FontAwesome.fa_wrench; + + public MaintenanceSection() + { + FlowContent.Spacing = new Vector2(0, 5); + Children = new Drawable[] + { + new GeneralSettings() + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index a002b8516c..28cb503288 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class OnlineSection : SettingsSection - { - public override string Header => "Online"; - public override FontAwesome Icon => FontAwesome.fa_globe; - - public OnlineSection() - { - Children = new Drawable[] - { - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class OnlineSection : SettingsSection + { + public override string Header => "Online"; + public override FontAwesome Icon => FontAwesome.fa_globe; + + public OnlineSection() + { + Children = new Drawable[] + { + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index a2215035dd..930b3c1eaa 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -1,82 +1,82 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Skinning; -using OpenTK; - -namespace osu.Game.Overlays.Settings.Sections -{ - public class SkinSection : SettingsSection - { - private SettingsDropdown skinDropdown; - - public override string Header => "Skin"; - - public override FontAwesome Icon => FontAwesome.fa_paint_brush; - - private SkinManager skins; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, SkinManager skins) - { - this.skins = skins; - - FlowContent.Spacing = new Vector2(0, 5); - Children = new Drawable[] - { - skinDropdown = new SettingsDropdown(), - new SettingsSlider - { - LabelText = "Menu cursor size", - Bindable = config.GetBindable(OsuSetting.MenuCursorSize), - KeyboardStep = 0.1f - }, - new SettingsSlider - { - LabelText = "Gameplay cursor size", - Bindable = config.GetBindable(OsuSetting.GameplayCursorSize), - KeyboardStep = 0.1f - }, - new SettingsCheckbox - { - LabelText = "Adjust gameplay cursor size based on current beatmap", - Bindable = config.GetBindable(OsuSetting.AutoCursorSize) - }, - }; - - skins.ItemAdded += onItemsChanged; - skins.ItemRemoved += onItemsChanged; - - reloadSkins(); - - skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin); - } - - private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID)); - - private void onItemsChanged(SkinInfo _) => Schedule(reloadSkins); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (skins != null) - { - skins.ItemAdded -= onItemsChanged; - skins.ItemRemoved -= onItemsChanged; - } - } - - private class SizeSlider : OsuSliderBar - { - public override string TooltipText => Current.Value.ToString(@"0.##x"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; +using OpenTK; + +namespace osu.Game.Overlays.Settings.Sections +{ + public class SkinSection : SettingsSection + { + private SettingsDropdown skinDropdown; + + public override string Header => "Skin"; + + public override FontAwesome Icon => FontAwesome.fa_paint_brush; + + private SkinManager skins; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, SkinManager skins) + { + this.skins = skins; + + FlowContent.Spacing = new Vector2(0, 5); + Children = new Drawable[] + { + skinDropdown = new SettingsDropdown(), + new SettingsSlider + { + LabelText = "Menu cursor size", + Bindable = config.GetBindable(OsuSetting.MenuCursorSize), + KeyboardStep = 0.1f + }, + new SettingsSlider + { + LabelText = "Gameplay cursor size", + Bindable = config.GetBindable(OsuSetting.GameplayCursorSize), + KeyboardStep = 0.1f + }, + new SettingsCheckbox + { + LabelText = "Adjust gameplay cursor size based on current beatmap", + Bindable = config.GetBindable(OsuSetting.AutoCursorSize) + }, + }; + + skins.ItemAdded += onItemsChanged; + skins.ItemRemoved += onItemsChanged; + + reloadSkins(); + + skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin); + } + + private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID)); + + private void onItemsChanged(SkinInfo _) => Schedule(reloadSkins); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (skins != null) + { + skins.ItemAdded -= onItemsChanged; + skins.ItemRemoved -= onItemsChanged; + } + } + + private class SizeSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0.##x"); + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 9fccc6476f..6689664988 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsButton : TriangleButton - { - public SettingsButton() - { - RelativeSizeAxes = Axes.X; - Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsButton : TriangleButton + { + public SettingsButton() + { + RelativeSizeAxes = Axes.X; + Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsCheckbox.cs b/osu.Game/Overlays/Settings/SettingsCheckbox.cs index 773cb04313..2c0f510cb8 100644 --- a/osu.Game/Overlays/Settings/SettingsCheckbox.cs +++ b/osu.Game/Overlays/Settings/SettingsCheckbox.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsCheckbox : SettingsItem - { - private OsuCheckbox checkbox; - - protected override Drawable CreateControl() => checkbox = new OsuCheckbox(); - - public override string LabelText - { - get { return checkbox.LabelText; } - set { checkbox.LabelText = value; } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsCheckbox : SettingsItem + { + private OsuCheckbox checkbox; + + protected override Drawable CreateControl() => checkbox = new OsuCheckbox(); + + public override string LabelText + { + get { return checkbox.LabelText; } + set { checkbox.LabelText = value; } + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsDropdown.cs b/osu.Game/Overlays/Settings/SettingsDropdown.cs index 4356d8a599..33a8af7d91 100644 --- a/osu.Game/Overlays/Settings/SettingsDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsDropdown.cs @@ -1,37 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsDropdown : SettingsItem - { - private Dropdown dropdown; - - private IEnumerable> items = new KeyValuePair[] { }; - public IEnumerable> Items - { - get - { - return items; - } - set - { - items = value; - if (dropdown != null) - dropdown.Items = value; - } - } - - protected override Drawable CreateControl() => dropdown = new OsuDropdown - { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - Items = Items, - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsDropdown : SettingsItem + { + private Dropdown dropdown; + + private IEnumerable> items = new KeyValuePair[] { }; + public IEnumerable> Items + { + get + { + return items; + } + set + { + items = value; + if (dropdown != null) + dropdown.Items = value; + } + } + + protected override Drawable CreateControl() => dropdown = new OsuDropdown + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + Items = Items, + }; + } +} diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index d4a5a1a861..64811137a6 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsEnumDropdown : SettingsDropdown - { - protected override Drawable CreateControl() => new OsuEnumDropdown - { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsEnumDropdown : SettingsDropdown + { + protected override Drawable CreateControl() => new OsuEnumDropdown + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + }; + } +} diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index b9581bac8a..900f03fe7b 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -1,73 +1,73 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Development; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsFooter : FillFlowContainer - { - [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours, RulesetStore rulesets) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - Padding = new MarginPadding { Top = 20, Bottom = 30 }; - - var modes = new List(); - - foreach (var ruleset in rulesets.AvailableRulesets) - { - var icon = new ConstrainedIconContainer - { - Icon = ruleset.CreateInstance().CreateIcon(), - Colour = Color4.Gray, - Size = new Vector2(20), - }; - - modes.Add(icon); - } - - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Full, - AutoSizeAxes = Axes.Both, - Children = modes, - Spacing = new Vector2(5), - Padding = new MarginPadding { Bottom = 10 }, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = game.Name, - TextSize = 18, - Font = @"Exo2.0-Bold", - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextSize = 14, - Text = game.Version, - Colour = DebugUtils.IsDebug ? colours.Red : Color4.White, - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Development; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsFooter : FillFlowContainer + { + [BackgroundDependencyLoader] + private void load(OsuGameBase game, OsuColour colours, RulesetStore rulesets) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Padding = new MarginPadding { Top = 20, Bottom = 30 }; + + var modes = new List(); + + foreach (var ruleset in rulesets.AvailableRulesets) + { + var icon = new ConstrainedIconContainer + { + Icon = ruleset.CreateInstance().CreateIcon(), + Colour = Color4.Gray, + Size = new Vector2(20), + }; + + modes.Add(icon); + } + + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Full, + AutoSizeAxes = Axes.Both, + Children = modes, + Spacing = new Vector2(5), + Padding = new MarginPadding { Bottom = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = game.Name, + TextSize = 18, + Font = @"Exo2.0-Bold", + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextSize = 14, + Text = game.Version, + Colour = DebugUtils.IsDebug ? colours.Red : Color4.White, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index 7cdd24b48d..fc86a38689 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsHeader : Container - { - private readonly string heading; - private readonly string subheading; - - public SettingsHeader(string heading, string subheading) - { - this.heading = heading; - this.subheading = subheading; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = heading, - TextSize = 40, - Margin = new MarginPadding - { - Left = SettingsOverlay.CONTENT_MARGINS, - Top = Toolbar.Toolbar.TOOLTIP_HEIGHT - }, - }, - new OsuSpriteText - { - Colour = colours.Pink, - Text = subheading, - TextSize = 18, - Margin = new MarginPadding - { - Left = SettingsOverlay.CONTENT_MARGINS, - Bottom = 30 - }, - }, - } - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsHeader : Container + { + private readonly string heading; + private readonly string subheading; + + public SettingsHeader(string heading, string subheading) + { + this.heading = heading; + this.subheading = subheading; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = heading, + TextSize = 40, + Margin = new MarginPadding + { + Left = SettingsOverlay.CONTENT_MARGINS, + Top = Toolbar.Toolbar.TOOLTIP_HEIGHT + }, + }, + new OsuSpriteText + { + Colour = colours.Pink, + Text = subheading, + TextSize = 18, + Margin = new MarginPadding + { + Left = SettingsOverlay.CONTENT_MARGINS, + Bottom = 30 + }, + }, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index cc290fe1bb..4c1ea1f32e 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -1,211 +1,211 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Allocation; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK; - -namespace osu.Game.Overlays.Settings -{ - public abstract class SettingsItem : Container, IFilterable - { - protected abstract Drawable CreateControl(); - - protected Drawable Control { get; } - - private IHasCurrentValue controlWithCurrent => Control as IHasCurrentValue; - - protected override Container Content => FlowContent; - - protected readonly FillFlowContainer FlowContent; - - private SpriteText text; - - private readonly RestoreDefaultValueButton restoreDefaultButton; - - public bool ShowsDefaultIndicator = true; - - public virtual string LabelText - { - get { return text?.Text ?? string.Empty; } - set - { - if (text == null) - { - // construct lazily for cases where the label is not needed (may be provided by the Control). - Add(text = new OsuSpriteText()); - FlowContent.SetLayoutPosition(text, -1); - } - - text.Text = value; - } - } - - // hold a reference to the provided bindable so we don't have to in every settings section. - private Bindable bindable; - - public virtual Bindable Bindable - { - get { return bindable; } - - set - { - bindable = value; - controlWithCurrent?.Current.BindTo(bindable); - if (ShowsDefaultIndicator) - { - restoreDefaultButton.Bindable = bindable.GetBoundCopy(); - restoreDefaultButton.Bindable.TriggerChange(); - } - } - } - - public IEnumerable FilterTerms => new[] { LabelText }; - - public bool MatchingFilter - { - set - { - // probably needs a better transition. - this.FadeTo(value ? 1 : 0); - } - } - - protected SettingsItem() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = SettingsOverlay.CONTENT_MARGINS }; - - InternalChildren = new Drawable[] - { - restoreDefaultButton = new RestoreDefaultValueButton(), - FlowContent = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS }, - Child = Control = CreateControl() - }, - }; - } - - [BackgroundDependencyLoader] - private void load() - { - if (controlWithCurrent != null) - controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; }; - } - - private class RestoreDefaultValueButton : Container, IHasTooltip - { - private Bindable bindable; - - public Bindable Bindable - { - get { return bindable; } - set - { - bindable = value; - bindable.ValueChanged += newValue => UpdateState(); - bindable.DisabledChanged += disabled => UpdateState(); - } - } - - private Color4 buttonColour; - - private bool hovering; - - public RestoreDefaultValueButton() - { - RelativeSizeAxes = Axes.Y; - Width = SettingsOverlay.CONTENT_MARGINS; - Alpha = 0f; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - buttonColour = colour.Yellow; - - Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - CornerRadius = 3, - Masking = true, - Colour = buttonColour, - EdgeEffect = new EdgeEffectParameters - { - Colour = buttonColour.Opacity(0.1f), - Type = EdgeEffectType.Glow, - Radius = 2, - }, - Size = new Vector2(0.33f, 0.8f), - Child = new Box { RelativeSizeAxes = Axes.Both }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - UpdateState(); - } - - public string TooltipText => "Revert to default"; - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; - - protected override bool OnClick(InputState state) - { - if (bindable != null && !bindable.Disabled) - bindable.SetDefault(); - return true; - } - - protected override bool OnHover(InputState state) - { - hovering = true; - UpdateState(); - return false; - } - - protected override void OnHoverLost(InputState state) - { - hovering = false; - UpdateState(); - } - - public void SetButtonColour(Color4 buttonColour) - { - this.buttonColour = buttonColour; - UpdateState(); - } - - public void UpdateState() - { - if (bindable == null) - return; - - this.FadeTo(bindable.IsDefault ? 0f : - hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); - this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Overlays.Settings +{ + public abstract class SettingsItem : Container, IFilterable + { + protected abstract Drawable CreateControl(); + + protected Drawable Control { get; } + + private IHasCurrentValue controlWithCurrent => Control as IHasCurrentValue; + + protected override Container Content => FlowContent; + + protected readonly FillFlowContainer FlowContent; + + private SpriteText text; + + private readonly RestoreDefaultValueButton restoreDefaultButton; + + public bool ShowsDefaultIndicator = true; + + public virtual string LabelText + { + get { return text?.Text ?? string.Empty; } + set + { + if (text == null) + { + // construct lazily for cases where the label is not needed (may be provided by the Control). + Add(text = new OsuSpriteText()); + FlowContent.SetLayoutPosition(text, -1); + } + + text.Text = value; + } + } + + // hold a reference to the provided bindable so we don't have to in every settings section. + private Bindable bindable; + + public virtual Bindable Bindable + { + get { return bindable; } + + set + { + bindable = value; + controlWithCurrent?.Current.BindTo(bindable); + if (ShowsDefaultIndicator) + { + restoreDefaultButton.Bindable = bindable.GetBoundCopy(); + restoreDefaultButton.Bindable.TriggerChange(); + } + } + } + + public IEnumerable FilterTerms => new[] { LabelText }; + + public bool MatchingFilter + { + set + { + // probably needs a better transition. + this.FadeTo(value ? 1 : 0); + } + } + + protected SettingsItem() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Right = SettingsOverlay.CONTENT_MARGINS }; + + InternalChildren = new Drawable[] + { + restoreDefaultButton = new RestoreDefaultValueButton(), + FlowContent = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS }, + Child = Control = CreateControl() + }, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + if (controlWithCurrent != null) + controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; }; + } + + private class RestoreDefaultValueButton : Container, IHasTooltip + { + private Bindable bindable; + + public Bindable Bindable + { + get { return bindable; } + set + { + bindable = value; + bindable.ValueChanged += newValue => UpdateState(); + bindable.DisabledChanged += disabled => UpdateState(); + } + } + + private Color4 buttonColour; + + private bool hovering; + + public RestoreDefaultValueButton() + { + RelativeSizeAxes = Axes.Y; + Width = SettingsOverlay.CONTENT_MARGINS; + Alpha = 0f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + buttonColour = colour.Yellow; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + CornerRadius = 3, + Masking = true, + Colour = buttonColour, + EdgeEffect = new EdgeEffectParameters + { + Colour = buttonColour.Opacity(0.1f), + Type = EdgeEffectType.Glow, + Radius = 2, + }, + Size = new Vector2(0.33f, 0.8f), + Child = new Box { RelativeSizeAxes = Axes.Both }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateState(); + } + + public string TooltipText => "Revert to default"; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + + protected override bool OnClick(InputState state) + { + if (bindable != null && !bindable.Disabled) + bindable.SetDefault(); + return true; + } + + protected override bool OnHover(InputState state) + { + hovering = true; + UpdateState(); + return false; + } + + protected override void OnHoverLost(InputState state) + { + hovering = false; + UpdateState(); + } + + public void SetButtonColour(Color4 buttonColour) + { + this.buttonColour = buttonColour; + UpdateState(); + } + + public void UpdateState() + { + if (bindable == null) + return; + + this.FadeTo(bindable.IsDefault ? 0f : + hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); + this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsLabel.cs b/osu.Game/Overlays/Settings/SettingsLabel.cs index db7712c873..2df4073191 100644 --- a/osu.Game/Overlays/Settings/SettingsLabel.cs +++ b/osu.Game/Overlays/Settings/SettingsLabel.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsLabel : SettingsItem - { - protected override Drawable CreateControl() => null; - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - Colour = colour.Gray6; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsLabel : SettingsItem + { + protected override Drawable CreateControl() => null; + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + Colour = colour.Gray6; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 546b40a1f2..9555f0fbc5 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -1,91 +1,91 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Overlays.Settings -{ - public abstract class SettingsSection : Container, IHasFilterableChildren - { - protected FillFlowContainer FlowContent; - protected override Container Content => FlowContent; - - public abstract FontAwesome Icon { get; } - public abstract string Header { get; } - - public IEnumerable FilterableChildren => Children.OfType(); - public IEnumerable FilterTerms => new[] { Header }; - - private const int header_size = 26; - private const int header_margin = 25; - private const int border_size = 2; - - public bool MatchingFilter - { - set { this.FadeTo(value ? 1 : 0); } - } - - protected SettingsSection() - { - Margin = new MarginPadding { Top = 20 }; - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; - - FlowContent = new FillFlowContainer - { - Margin = new MarginPadding - { - Top = header_size + header_margin - }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 30), - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AddRangeInternal(new Drawable[] - { - new Box - { - Colour = new Color4(0, 0, 0, 255), - RelativeSizeAxes = Axes.X, - Height = border_size, - }, - new Container - { - Padding = new MarginPadding - { - Top = 20 + border_size, - Bottom = 10, - }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] - { - new OsuSpriteText - { - TextSize = header_size, - Text = Header, - Colour = colours.Yellow, - Margin = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS } - }, - FlowContent - } - }, - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Overlays.Settings +{ + public abstract class SettingsSection : Container, IHasFilterableChildren + { + protected FillFlowContainer FlowContent; + protected override Container Content => FlowContent; + + public abstract FontAwesome Icon { get; } + public abstract string Header { get; } + + public IEnumerable FilterableChildren => Children.OfType(); + public IEnumerable FilterTerms => new[] { Header }; + + private const int header_size = 26; + private const int header_margin = 25; + private const int border_size = 2; + + public bool MatchingFilter + { + set { this.FadeTo(value ? 1 : 0); } + } + + protected SettingsSection() + { + Margin = new MarginPadding { Top = 20 }; + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + FlowContent = new FillFlowContainer + { + Margin = new MarginPadding + { + Top = header_size + header_margin + }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 30), + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddRangeInternal(new Drawable[] + { + new Box + { + Colour = new Color4(0, 0, 0, 255), + RelativeSizeAxes = Axes.X, + Height = border_size, + }, + new Container + { + Padding = new MarginPadding + { + Top = 20 + border_size, + Bottom = 10, + }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + new OsuSpriteText + { + TextSize = header_size, + Text = Header, + Colour = colours.Yellow, + Margin = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS } + }, + FlowContent + } + }, + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index 708d9437a5..a3698c36e6 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -1,36 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsSlider : SettingsSlider> - where T : struct, IEquatable, IComparable, IConvertible - { - } - - public class SettingsSlider : SettingsItem - where T : struct, IEquatable, IComparable, IConvertible - where U : OsuSliderBar, new() - { - protected override Drawable CreateControl() => new U - { - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - RelativeSizeAxes = Axes.X - }; - - public float KeyboardStep; - - [BackgroundDependencyLoader] - private void load() - { - var slider = Control as U; - if (slider != null) - slider.KeyboardStep = KeyboardStep; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsSlider : SettingsSlider> + where T : struct, IEquatable, IComparable, IConvertible + { + } + + public class SettingsSlider : SettingsItem + where T : struct, IEquatable, IComparable, IConvertible + where U : OsuSliderBar, new() + { + protected override Drawable CreateControl() => new U + { + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + RelativeSizeAxes = Axes.X + }; + + public float KeyboardStep; + + [BackgroundDependencyLoader] + private void load() + { + var slider = Control as U; + if (slider != null) + slider.KeyboardStep = KeyboardStep; + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index f0191f3af1..82589a99bd 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -1,62 +1,62 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; - -namespace osu.Game.Overlays.Settings -{ - public abstract class SettingsSubsection : FillFlowContainer, IHasFilterableChildren - { - protected override Container Content => FlowContent; - - protected readonly FillFlowContainer FlowContent; - - protected abstract string Header { get; } - - public IEnumerable FilterableChildren => Children.OfType(); - public IEnumerable FilterTerms => new[] { Header }; - public bool MatchingFilter - { - set - { - this.FadeTo(value ? 1 : 0); - } - } - - protected SettingsSubsection() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - - FlowContent = new FillFlowContainer - { - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }; - } - - [BackgroundDependencyLoader] - private void load() - { - AddRangeInternal(new Drawable[] - { - new OsuSpriteText - { - Text = Header.ToUpper(), - Margin = new MarginPadding { Bottom = 10, Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }, - Font = @"Exo2.0-Black", - }, - FlowContent - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays.Settings +{ + public abstract class SettingsSubsection : FillFlowContainer, IHasFilterableChildren + { + protected override Container Content => FlowContent; + + protected readonly FillFlowContainer FlowContent; + + protected abstract string Header { get; } + + public IEnumerable FilterableChildren => Children.OfType(); + public IEnumerable FilterTerms => new[] { Header }; + public bool MatchingFilter + { + set + { + this.FadeTo(value ? 1 : 0); + } + } + + protected SettingsSubsection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + FlowContent = new FillFlowContainer + { + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + new OsuSpriteText + { + Text = Header.ToUpper(), + Margin = new MarginPadding { Bottom = 10, Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }, + Font = @"Exo2.0-Black", + }, + FlowContent + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index 8f901ef252..ce9218bbe7 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsTextBox : SettingsItem - { - protected override Drawable CreateControl() => new OsuTextBox(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsTextBox : SettingsItem + { + protected override Drawable CreateControl() => new OsuTextBox(); + } +} diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 8792eafdbe..50452a7110 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -1,140 +1,140 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Threading; -using osu.Game.Overlays.Toolbar; - -namespace osu.Game.Overlays.Settings -{ - public class Sidebar : Container, IStateful - { - private readonly FillFlowContainer content; - public const float DEFAULT_WIDTH = ToolbarButton.WIDTH; - public const int EXPANDED_WIDTH = 200; - - public event Action StateChanged; - - protected override Container Content => content; - - public Sidebar() - { - RelativeSizeAxes = Axes.Y; - InternalChildren = new Drawable[] - { - new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }, - new SidebarScrollContainer - { - Children = new[] - { - content = new FillFlowContainer - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - } - } - }, - }; - } - - private ScheduledDelegate expandEvent; - private ExpandedState state; - - protected override bool OnHover(InputState state) - { - queueExpandIfHovering(); - return true; - } - - protected override void OnHoverLost(InputState state) - { - expandEvent?.Cancel(); - lastHoveredButton = null; - State = ExpandedState.Contracted; - - base.OnHoverLost(state); - } - - protected override bool OnMouseMove(InputState state) - { - queueExpandIfHovering(); - return base.OnMouseMove(state); - } - - private class SidebarScrollContainer : ScrollContainer - { - public SidebarScrollContainer() - { - Content.Anchor = Anchor.CentreLeft; - Content.Origin = Anchor.CentreLeft; - RelativeSizeAxes = Axes.Both; - } - } - - public ExpandedState State - { - get { return state; } - set - { - expandEvent?.Cancel(); - - if (state == value) return; - - state = value; - - switch (state) - { - default: - this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); - break; - case ExpandedState.Expanded: - this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); - break; - } - - StateChanged?.Invoke(State); - } - } - - private Drawable lastHoveredButton; - - private Drawable hoveredButton => content.Children.FirstOrDefault(c => c.IsHovered); - - private void queueExpandIfHovering() - { - // only expand when we hover a different button. - if (lastHoveredButton == hoveredButton) return; - - if (!IsHovered) return; - - if (State != ExpandedState.Expanded) - { - expandEvent?.Cancel(); - expandEvent = Scheduler.AddDelayed(() => State = ExpandedState.Expanded, 750); - } - - lastHoveredButton = hoveredButton; - } - } - - public enum ExpandedState - { - Contracted, - Expanded, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Threading; +using osu.Game.Overlays.Toolbar; + +namespace osu.Game.Overlays.Settings +{ + public class Sidebar : Container, IStateful + { + private readonly FillFlowContainer content; + public const float DEFAULT_WIDTH = ToolbarButton.WIDTH; + public const int EXPANDED_WIDTH = 200; + + public event Action StateChanged; + + protected override Container Content => content; + + public Sidebar() + { + RelativeSizeAxes = Axes.Y; + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new SidebarScrollContainer + { + Children = new[] + { + content = new FillFlowContainer + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + } + } + }, + }; + } + + private ScheduledDelegate expandEvent; + private ExpandedState state; + + protected override bool OnHover(InputState state) + { + queueExpandIfHovering(); + return true; + } + + protected override void OnHoverLost(InputState state) + { + expandEvent?.Cancel(); + lastHoveredButton = null; + State = ExpandedState.Contracted; + + base.OnHoverLost(state); + } + + protected override bool OnMouseMove(InputState state) + { + queueExpandIfHovering(); + return base.OnMouseMove(state); + } + + private class SidebarScrollContainer : ScrollContainer + { + public SidebarScrollContainer() + { + Content.Anchor = Anchor.CentreLeft; + Content.Origin = Anchor.CentreLeft; + RelativeSizeAxes = Axes.Both; + } + } + + public ExpandedState State + { + get { return state; } + set + { + expandEvent?.Cancel(); + + if (state == value) return; + + state = value; + + switch (state) + { + default: + this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); + break; + case ExpandedState.Expanded: + this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); + break; + } + + StateChanged?.Invoke(State); + } + } + + private Drawable lastHoveredButton; + + private Drawable hoveredButton => content.Children.FirstOrDefault(c => c.IsHovered); + + private void queueExpandIfHovering() + { + // only expand when we hover a different button. + if (lastHoveredButton == hoveredButton) return; + + if (!IsHovered) return; + + if (State != ExpandedState.Expanded) + { + expandEvent?.Cancel(); + expandEvent = Scheduler.AddDelayed(() => State = ExpandedState.Expanded, 750); + } + + lastHoveredButton = hoveredButton; + } + } + + public enum ExpandedState + { + Contracted, + Expanded, + } +} diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index a4c35204f4..0a3a30480b 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -1,128 +1,128 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Settings -{ - public class SidebarButton : OsuButton - { - private readonly SpriteIcon drawableIcon; - private readonly SpriteText headerText; - private readonly Box selectionIndicator; - private readonly Container text; - public new Action Action; - - private SettingsSection section; - public SettingsSection Section - { - get - { - return section; - } - set - { - section = value; - headerText.Text = value.Header; - drawableIcon.Icon = value.Icon; - } - } - - private bool selected; - public bool Selected - { - get { return selected; } - set - { - selected = value; - if (selected) - { - selectionIndicator.FadeIn(50); - text.FadeColour(Color4.White, 50); - } - else - { - selectionIndicator.FadeOut(50); - text.FadeColour(OsuColour.Gray(0.6f), 50); - } - } - } - - public SidebarButton() - { - BackgroundColour = OsuColour.Gray(60); - Background.Alpha = 0; - - Height = Sidebar.DEFAULT_WIDTH; - RelativeSizeAxes = Axes.X; - - AddRange(new Drawable[] - { - text = new Container - { - Width = Sidebar.DEFAULT_WIDTH, - RelativeSizeAxes = Axes.Y, - Colour = OsuColour.Gray(0.6f), - Children = new Drawable[] - { - headerText = new OsuSpriteText - { - Position = new Vector2(Sidebar.DEFAULT_WIDTH + 10, 0), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - drawableIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(20), - }, - } - }, - selectionIndicator = new Box - { - Alpha = 0, - RelativeSizeAxes = Axes.Y, - Width = 5, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - selectionIndicator.Colour = colours.Yellow; - } - - protected override bool OnClick(InputState state) - { - Action?.Invoke(section); - return base.OnClick(state); - } - - protected override bool OnHover(InputState state) - { - Background.FadeTo(0.4f, 200); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - Background.FadeTo(0, 200); - base.OnHoverLost(state); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SidebarButton : OsuButton + { + private readonly SpriteIcon drawableIcon; + private readonly SpriteText headerText; + private readonly Box selectionIndicator; + private readonly Container text; + public new Action Action; + + private SettingsSection section; + public SettingsSection Section + { + get + { + return section; + } + set + { + section = value; + headerText.Text = value.Header; + drawableIcon.Icon = value.Icon; + } + } + + private bool selected; + public bool Selected + { + get { return selected; } + set + { + selected = value; + if (selected) + { + selectionIndicator.FadeIn(50); + text.FadeColour(Color4.White, 50); + } + else + { + selectionIndicator.FadeOut(50); + text.FadeColour(OsuColour.Gray(0.6f), 50); + } + } + } + + public SidebarButton() + { + BackgroundColour = OsuColour.Gray(60); + Background.Alpha = 0; + + Height = Sidebar.DEFAULT_WIDTH; + RelativeSizeAxes = Axes.X; + + AddRange(new Drawable[] + { + text = new Container + { + Width = Sidebar.DEFAULT_WIDTH, + RelativeSizeAxes = Axes.Y, + Colour = OsuColour.Gray(0.6f), + Children = new Drawable[] + { + headerText = new OsuSpriteText + { + Position = new Vector2(Sidebar.DEFAULT_WIDTH + 10, 0), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + drawableIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20), + }, + } + }, + selectionIndicator = new Box + { + Alpha = 0, + RelativeSizeAxes = Axes.Y, + Width = 5, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selectionIndicator.Colour = colours.Yellow; + } + + protected override bool OnClick(InputState state) + { + Action?.Invoke(section); + return base.OnClick(state); + } + + protected override bool OnHover(InputState state) + { + Background.FadeTo(0.4f, 200); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Background.FadeTo(0, 200); + base.OnHoverLost(state); + } + } +} diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index c003046242..55326b53ed 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -1,224 +1,224 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Overlays -{ - public abstract class SettingsOverlay : OsuFocusedOverlayContainer - { - public const float CONTENT_MARGINS = 15; - - public const float TRANSITION_LENGTH = 600; - - private const float sidebar_width = Sidebar.DEFAULT_WIDTH; - - protected const float WIDTH = 400; - - private const float sidebar_padding = 10; - - protected Container ContentContainer; - - protected override Container Content => ContentContainer; - - protected Sidebar Sidebar; - private SidebarButton selectedSidebarButton; - - protected SettingsSectionsContainer SectionsContainer; - - private SearchTextBox searchTextBox; - - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - - private readonly bool showSidebar; - - protected Box Background; - - protected SettingsOverlay(bool showSidebar) - { - this.showSidebar = showSidebar; - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - } - - protected virtual IEnumerable CreateSections() => null; - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = ContentContainer = new Container - { - Width = WIDTH, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - Background = new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Scale = new Vector2(2, 1), // over-extend to the left for transitions - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - SectionsContainer = new SettingsSectionsContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - ExpandableHeader = CreateHeader(), - FixedHeader = searchTextBox = new SearchTextBox - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Width = 0.95f, - Margin = new MarginPadding - { - Top = 20, - Bottom = 20 - }, - Exit = Hide, - }, - Footer = CreateFooter() - }, - } - }; - - if (showSidebar) - { - AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); - - SectionsContainer.SelectedSection.ValueChanged += section => - { - selectedSidebarButton.Selected = false; - selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section); - selectedSidebarButton.Selected = true; - }; - } - - searchTextBox.Current.ValueChanged += newValue => SectionsContainer.SearchContainer.SearchTerm = newValue; - - CreateSections()?.ForEach(AddSection); - } - - protected void AddSection(SettingsSection section) - { - SectionsContainer.Add(section); - - if (Sidebar != null) - { - var button = new SidebarButton - { - Section = section, - Action = s => - { - SectionsContainer.ScrollTo(s); - Sidebar.State = ExpandedState.Contracted; - }, - }; - - Sidebar.Add(button); - - if (selectedSidebarButton == null) - { - selectedSidebarButton = Sidebar.Children.First(); - selectedSidebarButton.Selected = true; - } - } - } - - protected virtual Drawable CreateHeader() => new Container(); - - protected virtual Drawable CreateFooter() => new Container(); - - protected override void PopIn() - { - base.PopIn(); - - ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); - - Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); - - searchTextBox.HoldFocus = true; - } - - protected virtual float ExpandedPosition => 0; - - protected override void PopOut() - { - base.PopOut(); - - ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - - Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); - - searchTextBox.HoldFocus = false; - if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(null); - } - - public override bool AcceptsFocus => true; - - protected override void OnFocus(InputState state) - { - GetContainingInputManager().ChangeFocus(searchTextBox); - base.OnFocus(state); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; - ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; - } - - protected class SettingsSectionsContainer : SectionsContainer - { - public SearchContainer SearchContainer; - - protected override FlowContainer CreateScrollContentContainer() - => SearchContainer = new SearchContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - }; - - public SettingsSectionsContainer() - { - HeaderBackground = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both - }; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // no null check because the usage of this class is strict - HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Overlays +{ + public abstract class SettingsOverlay : OsuFocusedOverlayContainer + { + public const float CONTENT_MARGINS = 15; + + public const float TRANSITION_LENGTH = 600; + + private const float sidebar_width = Sidebar.DEFAULT_WIDTH; + + protected const float WIDTH = 400; + + private const float sidebar_padding = 10; + + protected Container ContentContainer; + + protected override Container Content => ContentContainer; + + protected Sidebar Sidebar; + private SidebarButton selectedSidebarButton; + + protected SettingsSectionsContainer SectionsContainer; + + private SearchTextBox searchTextBox; + + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + + private readonly bool showSidebar; + + protected Box Background; + + protected SettingsOverlay(bool showSidebar) + { + this.showSidebar = showSidebar; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + } + + protected virtual IEnumerable CreateSections() => null; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = ContentContainer = new Container + { + Width = WIDTH, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + Background = new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Scale = new Vector2(2, 1), // over-extend to the left for transitions + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + SectionsContainer = new SettingsSectionsContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + ExpandableHeader = CreateHeader(), + FixedHeader = searchTextBox = new SearchTextBox + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Width = 0.95f, + Margin = new MarginPadding + { + Top = 20, + Bottom = 20 + }, + Exit = Hide, + }, + Footer = CreateFooter() + }, + } + }; + + if (showSidebar) + { + AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); + + SectionsContainer.SelectedSection.ValueChanged += section => + { + selectedSidebarButton.Selected = false; + selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section); + selectedSidebarButton.Selected = true; + }; + } + + searchTextBox.Current.ValueChanged += newValue => SectionsContainer.SearchContainer.SearchTerm = newValue; + + CreateSections()?.ForEach(AddSection); + } + + protected void AddSection(SettingsSection section) + { + SectionsContainer.Add(section); + + if (Sidebar != null) + { + var button = new SidebarButton + { + Section = section, + Action = s => + { + SectionsContainer.ScrollTo(s); + Sidebar.State = ExpandedState.Contracted; + }, + }; + + Sidebar.Add(button); + + if (selectedSidebarButton == null) + { + selectedSidebarButton = Sidebar.Children.First(); + selectedSidebarButton.Selected = true; + } + } + } + + protected virtual Drawable CreateHeader() => new Container(); + + protected virtual Drawable CreateFooter() => new Container(); + + protected override void PopIn() + { + base.PopIn(); + + ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + + searchTextBox.HoldFocus = true; + } + + protected virtual float ExpandedPosition => 0; + + protected override void PopOut() + { + base.PopOut(); + + ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + + searchTextBox.HoldFocus = false; + if (searchTextBox.HasFocus) + GetContainingInputManager().ChangeFocus(null); + } + + public override bool AcceptsFocus => true; + + protected override void OnFocus(InputState state) + { + GetContainingInputManager().ChangeFocus(searchTextBox); + base.OnFocus(state); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; + ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } + + protected class SettingsSectionsContainer : SectionsContainer + { + public SearchContainer SearchContainer; + + protected override FlowContainer CreateScrollContentContainer() + => SearchContainer = new SearchContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }; + + public SettingsSectionsContainer() + { + HeaderBackground = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both + }; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // no null check because the usage of this class is strict + HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f; + } + } + } +} diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs index 382b3fd0e7..1b9ce1033f 100644 --- a/osu.Game/Overlays/Social/FilterControl.cs +++ b/osu.Game/Overlays/Social/FilterControl.cs @@ -1,32 +1,32 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Overlays.SearchableList; - -namespace osu.Game.Overlays.Social -{ - public class FilterControl : SearchableListFilterControl - { - protected override Color4 BackgroundColour => OsuColour.FromHex(@"47253a"); - protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank; - - public FilterControl() - { - Tabs.Margin = new MarginPadding { Top = 10 }; - } - } - - public enum SocialSortCriteria - { - Rank, - Name, - Location, - //[Description("Time Zone")] - //TimeZone, - //[Description("World Map")] - //WorldMap, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Overlays.SearchableList; + +namespace osu.Game.Overlays.Social +{ + public class FilterControl : SearchableListFilterControl + { + protected override Color4 BackgroundColour => OsuColour.FromHex(@"47253a"); + protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank; + + public FilterControl() + { + Tabs.Margin = new MarginPadding { Top = 10 }; + } + } + + public enum SocialSortCriteria + { + Rank, + Name, + Location, + //[Description("Time Zone")] + //TimeZone, + //[Description("World Map")] + //WorldMap, + } +} diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index 89224e1315..df97bdf448 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -1,65 +1,65 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Overlays.SearchableList; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Framework.Allocation; -using System.ComponentModel; - -namespace osu.Game.Overlays.Social -{ - public class Header : SearchableListHeader - { - private OsuSpriteText browser; - - protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); - - protected override SocialTab DefaultTab => SocialTab.AllPlayers; - protected override FontAwesome Icon => FontAwesome.fa_users; - - protected override Drawable CreateHeaderText() - { - return new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "social ", - TextSize = 25, - }, - browser = new OsuSpriteText - { - Text = "browser", - TextSize = 25, - Font = @"Exo2.0-Light", - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - browser.Colour = colours.Pink; - } - } - - public enum SocialTab - { - [Description("All Players")] - AllPlayers, - [Description("Friends")] - Friends, - //[Description("Team Members")] - //TeamMembers, - //[Description("Chat Channels")] - //ChatChannels, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.SearchableList; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using System.ComponentModel; + +namespace osu.Game.Overlays.Social +{ + public class Header : SearchableListHeader + { + private OsuSpriteText browser; + + protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); + + protected override SocialTab DefaultTab => SocialTab.AllPlayers; + protected override FontAwesome Icon => FontAwesome.fa_users; + + protected override Drawable CreateHeaderText() + { + return new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = "social ", + TextSize = 25, + }, + browser = new OsuSpriteText + { + Text = "browser", + TextSize = 25, + Font = @"Exo2.0-Light", + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + browser.Colour = colours.Pink; + } + } + + public enum SocialTab + { + [Description("All Players")] + AllPlayers, + [Description("Friends")] + Friends, + //[Description("Team Members")] + //TeamMembers, + //[Description("Chat Channels")] + //ChatChannels, + } +} diff --git a/osu.Game/Overlays/Social/SocialGridPanel.cs b/osu.Game/Overlays/Social/SocialGridPanel.cs index f9fbce123d..0051e1e0e5 100644 --- a/osu.Game/Overlays/Social/SocialGridPanel.cs +++ b/osu.Game/Overlays/Social/SocialGridPanel.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Users; - -namespace osu.Game.Overlays.Social -{ - public class SocialGridPanel : SocialPanel - { - public SocialGridPanel(User user) : base(user) - { - Width = 300; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialGridPanel : SocialPanel + { + public SocialGridPanel(User user) : base(user) + { + Width = 300; + } + } +} diff --git a/osu.Game/Overlays/Social/SocialListPanel.cs b/osu.Game/Overlays/Social/SocialListPanel.cs index 0f102005d6..ff39e3b236 100644 --- a/osu.Game/Overlays/Social/SocialListPanel.cs +++ b/osu.Game/Overlays/Social/SocialListPanel.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Users; - -namespace osu.Game.Overlays.Social -{ - public class SocialListPanel : SocialPanel - { - public SocialListPanel(User user) : base(user) - { - RelativeSizeAxes = Axes.X; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialListPanel : SocialPanel + { + public SocialListPanel(User user) : base(user) + { + RelativeSizeAxes = Axes.X; + } + } +} diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs index 54fb88f929..2411db7535 100644 --- a/osu.Game/Overlays/Social/SocialPanel.cs +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -1,60 +1,60 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Users; - -namespace osu.Game.Overlays.Social -{ - public class SocialPanel : UserPanel - { - private const double hover_transition_time = 400; - - public SocialPanel(User user) : base(user) - { - } - - private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 2f, - Colour = Color4.Black.Opacity(0.25f), - }; - - private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 5f), - Radius = 10f, - Colour = Color4.Black.Opacity(0.3f), - }; - - protected override bool OnHover(InputState state) - { - Content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); - Content.MoveToY(-4, hover_transition_time, Easing.OutQuint); - - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - Content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); - Content.MoveToY(0, hover_transition_time, Easing.OutQuint); - - base.OnHoverLost(state); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - this.FadeInFromZero(200, Easing.Out); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialPanel : UserPanel + { + private const double hover_transition_time = 400; + + public SocialPanel(User user) : base(user) + { + } + + private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 2f, + Colour = Color4.Black.Opacity(0.25f), + }; + + private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 5f), + Radius = 10f, + Colour = Color4.Black.Opacity(0.3f), + }; + + protected override bool OnHover(InputState state) + { + Content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); + Content.MoveToY(-4, hover_transition_time, Easing.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); + Content.MoveToY(0, hover_transition_time, Easing.OutQuint); + + base.OnHoverLost(state); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(200, Easing.Out); + } + } +} diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index ddcb933e5d..d224d4c92d 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -1,213 +1,213 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.SearchableList; -using osu.Game.Overlays.Social; -using osu.Game.Users; -using osu.Framework.Configuration; -using osu.Framework.Threading; - -namespace osu.Game.Overlays -{ - public class SocialOverlay : SearchableListOverlay, IOnlineComponent - { - private APIAccess api; - private readonly LoadingAnimation loading; - private FillFlowContainer panels; - - protected override Color4 BackgroundColour => OsuColour.FromHex(@"60284b"); - protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"672b51"); - protected override Color4 TrianglesColourDark => OsuColour.FromHex(@"5c2648"); - - protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); - - private IEnumerable users; - public IEnumerable Users - { - get { return users; } - set - { - if (users?.Equals(value) ?? false) - return; - - users = value?.ToList(); - } - } - - public SocialOverlay() - { - FirstWaveColour = OsuColour.FromHex(@"cb5fa0"); - SecondWaveColour = OsuColour.FromHex(@"b04384"); - ThirdWaveColour = OsuColour.FromHex(@"9b2b6e"); - FourthWaveColour = OsuColour.FromHex(@"6d214d"); - - Add(loading = new LoadingAnimation()); - - Filter.Search.Current.ValueChanged += text => - { - if (!string.IsNullOrEmpty(text)) - { - // force searching in players until searching for friends is supported - Header.Tabs.Current.Value = SocialTab.AllPlayers; - - if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank) - Filter.Tabs.Current.Value = SocialSortCriteria.Rank; - } - }; - - Header.Tabs.Current.ValueChanged += tab => Scheduler.AddOnce(updateSearch); - - Filter.Tabs.Current.ValueChanged += sortCriteria => Scheduler.AddOnce(updateSearch); - - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += sortOrder => Scheduler.AddOnce(updateSearch); - - currentQuery.ValueChanged += query => - { - queryChangedDebounce?.Cancel(); - - if (string.IsNullOrEmpty(query)) - Scheduler.AddOnce(updateSearch); - else - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); - }; - - currentQuery.BindTo(Filter.Search.Current); - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - api.Register(this); - } - - private void recreatePanels(PanelDisplayStyle displayStyle) - { - clearPanels(); - - if (Users == null) - return; - - var newPanels = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10f), - Margin = new MarginPadding { Top = 10 }, - ChildrenEnumerable = Users.Select(u => - { - SocialPanel panel; - switch (displayStyle) - { - case PanelDisplayStyle.Grid: - panel = new SocialGridPanel(u) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }; - break; - default: - panel = new SocialListPanel(u); - break; - } - panel.Status.BindTo(u.Status); - return panel; - }) - }; - - LoadComponentAsync(newPanels, f => - { - if(panels != null) - ScrollFlow.Remove(panels); - - ScrollFlow.Add(panels = newPanels); - }); - } - - private APIRequest getUsersRequest; - - private readonly Bindable currentQuery = new Bindable(); - - private ScheduledDelegate queryChangedDebounce; - - private void updateSearch() - { - queryChangedDebounce?.Cancel(); - - if (!IsLoaded) - return; - - Users = null; - clearPanels(); - loading.Hide(); - getUsersRequest?.Cancel(); - - if (api?.IsLoggedIn != true) - return; - - switch (Header.Tabs.Current.Value) - { - case SocialTab.Friends: - var friendRequest = new GetFriendsRequest(); // TODO filter arguments? - friendRequest.Success += updateUsers; - api.Queue(getUsersRequest = friendRequest); - break; - default: - var userRequest = new GetUsersRequest(); // TODO filter arguments! - userRequest.Success += response => updateUsers(response.Select(r => r.User)); - api.Queue(getUsersRequest = userRequest); - break; - } - loading.Show(); - } - - private void updateUsers(IEnumerable newUsers) - { - Users = newUsers; - loading.Hide(); - recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); - } - - private void clearPanels() - { - if (panels != null) - { - panels.Expire(); - panels = null; - } - } - - public void APIStateChanged(APIAccess api, APIState state) - { - switch (state) - { - case APIState.Online: - Scheduler.AddOnce(updateSearch); - break; - default: - Users = null; - clearPanels(); - break; - } - } - } - - public enum SortDirection - { - Ascending, - Descending - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.SearchableList; +using osu.Game.Overlays.Social; +using osu.Game.Users; +using osu.Framework.Configuration; +using osu.Framework.Threading; + +namespace osu.Game.Overlays +{ + public class SocialOverlay : SearchableListOverlay, IOnlineComponent + { + private APIAccess api; + private readonly LoadingAnimation loading; + private FillFlowContainer panels; + + protected override Color4 BackgroundColour => OsuColour.FromHex(@"60284b"); + protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"672b51"); + protected override Color4 TrianglesColourDark => OsuColour.FromHex(@"5c2648"); + + protected override SearchableListHeader CreateHeader() => new Header(); + protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); + + private IEnumerable users; + public IEnumerable Users + { + get { return users; } + set + { + if (users?.Equals(value) ?? false) + return; + + users = value?.ToList(); + } + } + + public SocialOverlay() + { + FirstWaveColour = OsuColour.FromHex(@"cb5fa0"); + SecondWaveColour = OsuColour.FromHex(@"b04384"); + ThirdWaveColour = OsuColour.FromHex(@"9b2b6e"); + FourthWaveColour = OsuColour.FromHex(@"6d214d"); + + Add(loading = new LoadingAnimation()); + + Filter.Search.Current.ValueChanged += text => + { + if (!string.IsNullOrEmpty(text)) + { + // force searching in players until searching for friends is supported + Header.Tabs.Current.Value = SocialTab.AllPlayers; + + if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank) + Filter.Tabs.Current.Value = SocialSortCriteria.Rank; + } + }; + + Header.Tabs.Current.ValueChanged += tab => Scheduler.AddOnce(updateSearch); + + Filter.Tabs.Current.ValueChanged += sortCriteria => Scheduler.AddOnce(updateSearch); + + Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += sortOrder => Scheduler.AddOnce(updateSearch); + + currentQuery.ValueChanged += query => + { + queryChangedDebounce?.Cancel(); + + if (string.IsNullOrEmpty(query)) + Scheduler.AddOnce(updateSearch); + else + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); + }; + + currentQuery.BindTo(Filter.Search.Current); + } + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + this.api = api; + api.Register(this); + } + + private void recreatePanels(PanelDisplayStyle displayStyle) + { + clearPanels(); + + if (Users == null) + return; + + var newPanels = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10f), + Margin = new MarginPadding { Top = 10 }, + ChildrenEnumerable = Users.Select(u => + { + SocialPanel panel; + switch (displayStyle) + { + case PanelDisplayStyle.Grid: + panel = new SocialGridPanel(u) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }; + break; + default: + panel = new SocialListPanel(u); + break; + } + panel.Status.BindTo(u.Status); + return panel; + }) + }; + + LoadComponentAsync(newPanels, f => + { + if(panels != null) + ScrollFlow.Remove(panels); + + ScrollFlow.Add(panels = newPanels); + }); + } + + private APIRequest getUsersRequest; + + private readonly Bindable currentQuery = new Bindable(); + + private ScheduledDelegate queryChangedDebounce; + + private void updateSearch() + { + queryChangedDebounce?.Cancel(); + + if (!IsLoaded) + return; + + Users = null; + clearPanels(); + loading.Hide(); + getUsersRequest?.Cancel(); + + if (api?.IsLoggedIn != true) + return; + + switch (Header.Tabs.Current.Value) + { + case SocialTab.Friends: + var friendRequest = new GetFriendsRequest(); // TODO filter arguments? + friendRequest.Success += updateUsers; + api.Queue(getUsersRequest = friendRequest); + break; + default: + var userRequest = new GetUsersRequest(); // TODO filter arguments! + userRequest.Success += response => updateUsers(response.Select(r => r.User)); + api.Queue(getUsersRequest = userRequest); + break; + } + loading.Show(); + } + + private void updateUsers(IEnumerable newUsers) + { + Users = newUsers; + loading.Hide(); + recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); + } + + private void clearPanels() + { + if (panels != null) + { + panels.Expire(); + panels = null; + } + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + case APIState.Online: + Scheduler.AddOnce(updateSearch); + break; + default: + Users = null; + clearPanels(); + break; + } + } + } + + public enum SortDirection + { + Ascending, + Descending + } +} diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 8ac4e44a84..424a457110 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -1,135 +1,135 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Overlays.Toolbar -{ - public class Toolbar : OverlayContainer - { - public const float HEIGHT = 40; - public const float TOOLTIP_HEIGHT = 30; - - public Action OnHome; - - private readonly ToolbarUserArea userArea; - - protected override bool BlockPassThroughMouse => false; - - private const double transition_time = 500; - - private const float alpha_hovering = 0.8f; - private const float alpha_normal = 0.6f; - - public Toolbar() - { - Children = new Drawable[] - { - new ToolbarBackground(), - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - new ToolbarSettingsButton(), - new ToolbarHomeButton - { - Action = () => OnHome?.Invoke() - }, - new ToolbarModeSelector() - } - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - new ToolbarDirectButton(), - new ToolbarChatButton(), - new ToolbarSocialButton(), - new ToolbarMusicButton(), - //new ToolbarButton - //{ - // Icon = FontAwesome.fa_search - //}, - userArea = new ToolbarUserArea(), - new ToolbarNotificationButton(), - } - } - }; - - RelativeSizeAxes = Axes.X; - Size = new Vector2(1, HEIGHT); - } - - public class ToolbarBackground : Container - { - private readonly Box solidBackground; - private readonly Box gradientBackground; - - public ToolbarBackground() - { - RelativeSizeAxes = Axes.Both; - Children = new Drawable[] - { - solidBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.1f), - Alpha = alpha_normal, - }, - gradientBackground = new Box - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Alpha = 0, - Height = 90, - Colour = ColourInfo.GradientVertical( - OsuColour.Gray(0.1f).Opacity(0.5f), OsuColour.Gray(0.1f).Opacity(0)), - }, - }; - } - - protected override bool OnHover(InputState state) - { - solidBackground.FadeTo(alpha_hovering, transition_time, Easing.OutQuint); - gradientBackground.FadeIn(transition_time, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(InputState state) - { - solidBackground.FadeTo(alpha_normal, transition_time, Easing.OutQuint); - gradientBackground.FadeOut(transition_time, Easing.OutQuint); - } - } - - protected override void PopIn() - { - this.MoveToY(0, transition_time, Easing.OutQuint); - this.FadeIn(transition_time / 2, Easing.OutQuint); - } - - protected override void PopOut() - { - userArea?.LoginOverlay.Hide(); - - this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); - this.FadeOut(transition_time); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Overlays.Toolbar +{ + public class Toolbar : OverlayContainer + { + public const float HEIGHT = 40; + public const float TOOLTIP_HEIGHT = 30; + + public Action OnHome; + + private readonly ToolbarUserArea userArea; + + protected override bool BlockPassThroughMouse => false; + + private const double transition_time = 500; + + private const float alpha_hovering = 0.8f; + private const float alpha_normal = 0.6f; + + public Toolbar() + { + Children = new Drawable[] + { + new ToolbarBackground(), + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new ToolbarSettingsButton(), + new ToolbarHomeButton + { + Action = () => OnHome?.Invoke() + }, + new ToolbarModeSelector() + } + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new ToolbarDirectButton(), + new ToolbarChatButton(), + new ToolbarSocialButton(), + new ToolbarMusicButton(), + //new ToolbarButton + //{ + // Icon = FontAwesome.fa_search + //}, + userArea = new ToolbarUserArea(), + new ToolbarNotificationButton(), + } + } + }; + + RelativeSizeAxes = Axes.X; + Size = new Vector2(1, HEIGHT); + } + + public class ToolbarBackground : Container + { + private readonly Box solidBackground; + private readonly Box gradientBackground; + + public ToolbarBackground() + { + RelativeSizeAxes = Axes.Both; + Children = new Drawable[] + { + solidBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.1f), + Alpha = alpha_normal, + }, + gradientBackground = new Box + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Alpha = 0, + Height = 90, + Colour = ColourInfo.GradientVertical( + OsuColour.Gray(0.1f).Opacity(0.5f), OsuColour.Gray(0.1f).Opacity(0)), + }, + }; + } + + protected override bool OnHover(InputState state) + { + solidBackground.FadeTo(alpha_hovering, transition_time, Easing.OutQuint); + gradientBackground.FadeIn(transition_time, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(InputState state) + { + solidBackground.FadeTo(alpha_normal, transition_time, Easing.OutQuint); + gradientBackground.FadeOut(transition_time, Easing.OutQuint); + } + } + + protected override void PopIn() + { + this.MoveToY(0, transition_time, Easing.OutQuint); + this.FadeIn(transition_time / 2, Easing.OutQuint); + } + + protected override void PopOut() + { + userArea?.LoginOverlay.Hide(); + + this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); + this.FadeOut(transition_time); + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index f3a25b79a1..c7870a72de 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -1,199 +1,199 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarButton : OsuClickableContainer - { - public const float WIDTH = Toolbar.HEIGHT * 1.4f; - - public void SetIcon(Drawable icon) - { - IconContainer.Icon = icon; - IconContainer.Show(); - } - - public void SetIcon(FontAwesome icon) => SetIcon(new SpriteIcon - { - Size = new Vector2(20), - Icon = icon - }); - - public FontAwesome Icon - { - set { SetIcon(value); } - } - - public string Text - { - get { return DrawableText.Text; } - set - { - DrawableText.Text = value; - } - } - - public string TooltipMain - { - get { return tooltip1.Text; } - set - { - tooltip1.Text = value; - } - } - - public string TooltipSub - { - get { return tooltip2.Text; } - set - { - tooltip2.Text = value; - } - } - - protected virtual Anchor TooltipAnchor => Anchor.TopLeft; - - protected ConstrainedIconContainer IconContainer; - protected SpriteText DrawableText; - protected Box HoverBackground; - private readonly FillFlowContainer tooltipContainer; - private readonly SpriteText tooltip1; - private readonly SpriteText tooltip2; - protected FillFlowContainer Flow; - - public ToolbarButton() : base(HoverSampleSet.Loud) - { - Width = WIDTH; - RelativeSizeAxes = Axes.Y; - - Children = new Drawable[] - { - HoverBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingMode.Additive, - Alpha = 0, - }, - Flow = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 }, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - IconContainer = new ConstrainedIconContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Alpha = 0, - }, - DrawableText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - }, - }, - tooltipContainer = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.Both, //stops us being considered in parent's autosize - Anchor = (TooltipAnchor & Anchor.x0) > 0 ? Anchor.BottomLeft : Anchor.BottomRight, - Origin = TooltipAnchor, - Position = new Vector2((TooltipAnchor & Anchor.x0) > 0 ? 5 : -5, 5), - Alpha = 0, - Children = new[] - { - tooltip1 = new OsuSpriteText - { - Anchor = TooltipAnchor, - Origin = TooltipAnchor, - Shadow = true, - TextSize = 22, - Font = @"Exo2.0-Bold", - }, - tooltip2 = new OsuSpriteText - { - Anchor = TooltipAnchor, - Origin = TooltipAnchor, - Shadow = true, - TextSize = 16 - } - } - } - }; - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - - protected override bool OnClick(InputState state) - { - HoverBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); - return base.OnClick(state); - } - - protected override bool OnHover(InputState state) - { - HoverBackground.FadeIn(200); - tooltipContainer.FadeIn(100); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - HoverBackground.FadeOut(200); - tooltipContainer.FadeOut(100); - } - } - - public class OpaqueBackground : Container - { - public OpaqueBackground() - { - RelativeSizeAxes = Axes.Both; - Masking = true; - MaskingSmoothness = 0; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(40), - Radius = 5, - }; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(30) - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourLight = OsuColour.Gray(40), - ColourDark = OsuColour.Gray(20), - }, - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarButton : OsuClickableContainer + { + public const float WIDTH = Toolbar.HEIGHT * 1.4f; + + public void SetIcon(Drawable icon) + { + IconContainer.Icon = icon; + IconContainer.Show(); + } + + public void SetIcon(FontAwesome icon) => SetIcon(new SpriteIcon + { + Size = new Vector2(20), + Icon = icon + }); + + public FontAwesome Icon + { + set { SetIcon(value); } + } + + public string Text + { + get { return DrawableText.Text; } + set + { + DrawableText.Text = value; + } + } + + public string TooltipMain + { + get { return tooltip1.Text; } + set + { + tooltip1.Text = value; + } + } + + public string TooltipSub + { + get { return tooltip2.Text; } + set + { + tooltip2.Text = value; + } + } + + protected virtual Anchor TooltipAnchor => Anchor.TopLeft; + + protected ConstrainedIconContainer IconContainer; + protected SpriteText DrawableText; + protected Box HoverBackground; + private readonly FillFlowContainer tooltipContainer; + private readonly SpriteText tooltip1; + private readonly SpriteText tooltip2; + protected FillFlowContainer Flow; + + public ToolbarButton() : base(HoverSampleSet.Loud) + { + Width = WIDTH; + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + HoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingMode.Additive, + Alpha = 0, + }, + Flow = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 }, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + IconContainer = new ConstrainedIconContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + Alpha = 0, + }, + DrawableText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }, + }, + tooltipContainer = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.Both, //stops us being considered in parent's autosize + Anchor = (TooltipAnchor & Anchor.x0) > 0 ? Anchor.BottomLeft : Anchor.BottomRight, + Origin = TooltipAnchor, + Position = new Vector2((TooltipAnchor & Anchor.x0) > 0 ? 5 : -5, 5), + Alpha = 0, + Children = new[] + { + tooltip1 = new OsuSpriteText + { + Anchor = TooltipAnchor, + Origin = TooltipAnchor, + Shadow = true, + TextSize = 22, + Font = @"Exo2.0-Bold", + }, + tooltip2 = new OsuSpriteText + { + Anchor = TooltipAnchor, + Origin = TooltipAnchor, + Shadow = true, + TextSize = 16 + } + } + } + }; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnClick(InputState state) + { + HoverBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); + return base.OnClick(state); + } + + protected override bool OnHover(InputState state) + { + HoverBackground.FadeIn(200); + tooltipContainer.FadeIn(100); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + HoverBackground.FadeOut(200); + tooltipContainer.FadeOut(100); + } + } + + public class OpaqueBackground : Container + { + public OpaqueBackground() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + MaskingSmoothness = 0; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(40), + Radius = 5, + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(30) + }, + new Triangles + { + RelativeSizeAxes = Axes.Both, + ColourLight = OsuColour.Gray(40), + ColourDark = OsuColour.Gray(20), + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index fd78db5a8b..3d9b943d16 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarChatButton : ToolbarOverlayToggleButton - { - public ToolbarChatButton() - { - SetIcon(FontAwesome.fa_comments); - } - - [BackgroundDependencyLoader(true)] - private void load(ChatOverlay chat) - { - StateContainer = chat; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarChatButton : ToolbarOverlayToggleButton + { + public ToolbarChatButton() + { + SetIcon(FontAwesome.fa_comments); + } + + [BackgroundDependencyLoader(true)] + private void load(ChatOverlay chat) + { + StateContainer = chat; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs index 5922b6212c..ffc5ecd682 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarDirectButton : ToolbarOverlayToggleButton - { - public ToolbarDirectButton() - { - SetIcon(FontAwesome.fa_osu_chevron_down_o); - } - - [BackgroundDependencyLoader(true)] - private void load(DirectOverlay direct) - { - StateContainer = direct; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarDirectButton : ToolbarOverlayToggleButton + { + public ToolbarDirectButton() + { + SetIcon(FontAwesome.fa_osu_chevron_down_o); + } + + [BackgroundDependencyLoader(true)] + private void load(DirectOverlay direct) + { + StateContainer = direct; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 3bce67765a..e79b73e725 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarHomeButton : ToolbarButton - { - public ToolbarHomeButton() - { - Icon = FontAwesome.fa_home; - TooltipMain = "Home"; - TooltipSub = "Return to the main menu"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarHomeButton : ToolbarButton + { + public ToolbarHomeButton() + { + Icon = FontAwesome.fa_home; + TooltipMain = "Home"; + TooltipSub = "Return to the main menu"; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs index 451af65ce7..90b9abb2e4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs @@ -1,57 +1,57 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarModeButton : ToolbarButton - { - private RulesetInfo ruleset; - public RulesetInfo Ruleset - { - get { return ruleset; } - set - { - ruleset = value; - - var rInstance = ruleset.CreateInstance(); - - TooltipMain = rInstance.Description; - TooltipSub = $"Play some {rInstance.Description}"; - SetIcon(rInstance.CreateIcon()); - } - } - - public bool Active - { - set - { - if (value) - { - IconContainer.Colour = Color4.White; - IconContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }; - } - else - { - IconContainer.Colour = new Color4(255, 194, 224, 255); - IconContainer.EdgeEffect = new EdgeEffectParameters(); - } - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - IconContainer.Scale *= 1.4f; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarModeButton : ToolbarButton + { + private RulesetInfo ruleset; + public RulesetInfo Ruleset + { + get { return ruleset; } + set + { + ruleset = value; + + var rInstance = ruleset.CreateInstance(); + + TooltipMain = rInstance.Description; + TooltipSub = $"Play some {rInstance.Description}"; + SetIcon(rInstance.CreateIcon()); + } + } + + public bool Active + { + set + { + if (value) + { + IconContainer.Colour = Color4.White; + IconContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = new Color4(255, 194, 224, 100), + Radius = 15, + Roundness = 15, + }; + } + else + { + IconContainer.Colour = new Color4(255, 194, 224, 255); + IconContainer.EdgeEffect = new EdgeEffectParameters(); + } + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + IconContainer.Scale *= 1.4f; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 6d58c78c37..05866f7002 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -1,127 +1,127 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Caching; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarModeSelector : Container - { - private const float padding = 10; - - private readonly FillFlowContainer modeButtons; - private readonly Drawable modeButtonLine; - private ToolbarModeButton activeButton; - - private readonly Bindable ruleset = new Bindable(); - - public ToolbarModeSelector() - { - RelativeSizeAxes = Axes.Y; - - Children = new[] - { - new OpaqueBackground(), - modeButtons = new FillFlowContainer - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = padding, Right = padding }, - }, - modeButtonLine = new Container - { - Size = new Vector2(padding * 2 + ToolbarButton.WIDTH, 3), - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - Masking = true, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - } - } - } - }; - } - - [BackgroundDependencyLoader(true)] - private void load(RulesetStore rulesets, OsuGame game) - { - foreach (var r in rulesets.AvailableRulesets) - { - modeButtons.Add(new ToolbarModeButton - { - Ruleset = r, - Action = delegate - { - ruleset.Value = r; - } - }); - } - - ruleset.ValueChanged += rulesetChanged; - ruleset.DisabledChanged += disabledChanged; - if (game != null) - ruleset.BindTo(game.Ruleset); - else - ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(); - } - - public override bool HandleKeyboardInput => !ruleset.Disabled; - public override bool HandleMouseInput => !ruleset.Disabled; - - private void disabledChanged(bool isDisabled) => this.FadeColour(isDisabled ? Color4.Gray : Color4.White, 300); - - protected override void Update() - { - base.Update(); - Size = new Vector2(modeButtons.DrawSize.X, 1); - } - - private void rulesetChanged(RulesetInfo ruleset) - { - foreach (ToolbarModeButton m in modeButtons.Children.Cast()) - { - bool isActive = m.Ruleset.ID == ruleset.ID; - m.Active = isActive; - if (isActive) - activeButton = m; - } - - activeMode.Invalidate(); - } - - private Cached activeMode = new Cached(); - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (!activeMode.IsValid) - { - modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, Easing.OutQuint); - activeMode.Validate(); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Caching; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarModeSelector : Container + { + private const float padding = 10; + + private readonly FillFlowContainer modeButtons; + private readonly Drawable modeButtonLine; + private ToolbarModeButton activeButton; + + private readonly Bindable ruleset = new Bindable(); + + public ToolbarModeSelector() + { + RelativeSizeAxes = Axes.Y; + + Children = new[] + { + new OpaqueBackground(), + modeButtons = new FillFlowContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding { Left = padding, Right = padding }, + }, + modeButtonLine = new Container + { + Size = new Vector2(padding * 2 + ToolbarButton.WIDTH, 3), + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = new Color4(255, 194, 224, 100), + Radius = 15, + Roundness = 15, + }, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + } + } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(RulesetStore rulesets, OsuGame game) + { + foreach (var r in rulesets.AvailableRulesets) + { + modeButtons.Add(new ToolbarModeButton + { + Ruleset = r, + Action = delegate + { + ruleset.Value = r; + } + }); + } + + ruleset.ValueChanged += rulesetChanged; + ruleset.DisabledChanged += disabledChanged; + if (game != null) + ruleset.BindTo(game.Ruleset); + else + ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(); + } + + public override bool HandleKeyboardInput => !ruleset.Disabled; + public override bool HandleMouseInput => !ruleset.Disabled; + + private void disabledChanged(bool isDisabled) => this.FadeColour(isDisabled ? Color4.Gray : Color4.White, 300); + + protected override void Update() + { + base.Update(); + Size = new Vector2(modeButtons.DrawSize.X, 1); + } + + private void rulesetChanged(RulesetInfo ruleset) + { + foreach (ToolbarModeButton m in modeButtons.Children.Cast()) + { + bool isActive = m.Ruleset.ID == ruleset.ID; + m.Active = isActive; + if (isActive) + activeButton = m; + } + + activeMode.Invalidate(); + } + + private Cached activeMode = new Cached(); + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (!activeMode.IsValid) + { + modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, Easing.OutQuint); + activeMode.Validate(); + } + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 428467fece..94a7e69e3f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarMusicButton : ToolbarOverlayToggleButton - { - public ToolbarMusicButton() - { - Icon = FontAwesome.fa_music; - } - - [BackgroundDependencyLoader(true)] - private void load(MusicController music) - { - StateContainer = music; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarMusicButton : ToolbarOverlayToggleButton + { + public ToolbarMusicButton() + { + Icon = FontAwesome.fa_music; + } + + [BackgroundDependencyLoader(true)] + private void load(MusicController music) + { + StateContainer = music; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index 9cff2e4a77..ef7e5f07bb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -1,112 +1,112 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarNotificationButton : ToolbarOverlayToggleButton - { - protected override Anchor TooltipAnchor => Anchor.TopRight; - - public BindableInt NotificationCount = new BindableInt(); - - private readonly CountCircle countDisplay; - - public ToolbarNotificationButton() - { - Icon = FontAwesome.fa_bars; - TooltipMain = "Notifications"; - TooltipSub = "Waiting for 'ya"; - - Add(countDisplay = new CountCircle - { - Alpha = 0, - Height = 16, - RelativePositionAxes = Axes.Both, - Origin = Anchor.Centre, - Position = new Vector2(0.7f, 0.25f), - }); - } - - [BackgroundDependencyLoader(true)] - private void load(NotificationOverlay notificationOverlay) - { - StateContainer = notificationOverlay; - - if (notificationOverlay != null) - NotificationCount.BindTo(notificationOverlay.UnreadCount); - - NotificationCount.ValueChanged += count => - { - if (count == 0) - countDisplay.FadeOut(200, Easing.OutQuint); - else - { - countDisplay.Count = count; - countDisplay.FadeIn(200, Easing.OutQuint); - } - }; - } - - private class CountCircle : CompositeDrawable - { - private readonly OsuSpriteText countText; - private readonly Circle circle; - - private int count; - - public int Count - { - get { return count; } - set - { - if (count == value) - return; - - if (value > count) - { - circle.FlashColour(Color4.White, 600, Easing.OutQuint); - this.ScaleTo(1.1f).Then().ScaleTo(1, 600, Easing.OutElastic); - } - - count = value; - countText.Text = value.ToString("#,0"); - } - } - - public CountCircle() - { - AutoSizeAxes = Axes.X; - - InternalChildren = new Drawable[] - { - circle = new Circle - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Red - }, - countText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -1, - TextSize = 14, - Padding = new MarginPadding(5), - Colour = Color4.White, - UseFullGlyphHeight = true, - Font = "Exo2.0-Bold", - } - }; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarNotificationButton : ToolbarOverlayToggleButton + { + protected override Anchor TooltipAnchor => Anchor.TopRight; + + public BindableInt NotificationCount = new BindableInt(); + + private readonly CountCircle countDisplay; + + public ToolbarNotificationButton() + { + Icon = FontAwesome.fa_bars; + TooltipMain = "Notifications"; + TooltipSub = "Waiting for 'ya"; + + Add(countDisplay = new CountCircle + { + Alpha = 0, + Height = 16, + RelativePositionAxes = Axes.Both, + Origin = Anchor.Centre, + Position = new Vector2(0.7f, 0.25f), + }); + } + + [BackgroundDependencyLoader(true)] + private void load(NotificationOverlay notificationOverlay) + { + StateContainer = notificationOverlay; + + if (notificationOverlay != null) + NotificationCount.BindTo(notificationOverlay.UnreadCount); + + NotificationCount.ValueChanged += count => + { + if (count == 0) + countDisplay.FadeOut(200, Easing.OutQuint); + else + { + countDisplay.Count = count; + countDisplay.FadeIn(200, Easing.OutQuint); + } + }; + } + + private class CountCircle : CompositeDrawable + { + private readonly OsuSpriteText countText; + private readonly Circle circle; + + private int count; + + public int Count + { + get { return count; } + set + { + if (count == value) + return; + + if (value > count) + { + circle.FlashColour(Color4.White, 600, Easing.OutQuint); + this.ScaleTo(1.1f).Then().ScaleTo(1, 600, Easing.OutElastic); + } + + count = value; + countText.Text = value.ToString("#,0"); + } + } + + public CountCircle() + { + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red + }, + countText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -1, + TextSize = 14, + Padding = new MarginPadding(5), + Colour = Color4.White, + UseFullGlyphHeight = true, + Font = "Exo2.0-Bold", + } + }; + } + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 0f7f34c36a..0c8483dec3 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarOverlayToggleButton : ToolbarButton - { - private readonly Box stateBackground; - - private OverlayContainer stateContainer; - - public OverlayContainer StateContainer - { - get { return stateContainer; } - set - { - stateContainer = value; - if (stateContainer != null) - { - Action = stateContainer.ToggleVisibility; - stateContainer.StateChanged += stateChanged; - } - } - } - - public ToolbarOverlayToggleButton() - { - Add(stateBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(150).Opacity(180), - Blending = BlendingMode.Additive, - Depth = 2, - Alpha = 0, - }); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - if (stateContainer != null) - stateContainer.StateChanged -= stateChanged; - } - - private void stateChanged(Visibility state) - { - switch (state) - { - case Visibility.Hidden: - stateBackground.FadeOut(200); - break; - case Visibility.Visible: - stateBackground.FadeIn(200); - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarOverlayToggleButton : ToolbarButton + { + private readonly Box stateBackground; + + private OverlayContainer stateContainer; + + public OverlayContainer StateContainer + { + get { return stateContainer; } + set + { + stateContainer = value; + if (stateContainer != null) + { + Action = stateContainer.ToggleVisibility; + stateContainer.StateChanged += stateChanged; + } + } + } + + public ToolbarOverlayToggleButton() + { + Add(stateBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(150).Opacity(180), + Blending = BlendingMode.Additive, + Depth = 2, + Alpha = 0, + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + if (stateContainer != null) + stateContainer.StateChanged -= stateChanged; + } + + private void stateChanged(Visibility state) + { + switch (state) + { + case Visibility.Hidden: + stateBackground.FadeOut(200); + break; + case Visibility.Visible: + stateBackground.FadeIn(200); + break; + } + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 1b0c821b54..5c3eaf7840 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarSettingsButton : ToolbarOverlayToggleButton - { - public ToolbarSettingsButton() - { - Icon = FontAwesome.fa_gear; - TooltipMain = "Settings"; - TooltipSub = "Change your settings"; - } - - [BackgroundDependencyLoader(true)] - private void load(MainSettings settings) - { - StateContainer = settings; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarSettingsButton : ToolbarOverlayToggleButton + { + public ToolbarSettingsButton() + { + Icon = FontAwesome.fa_gear; + TooltipMain = "Settings"; + TooltipSub = "Change your settings"; + } + + [BackgroundDependencyLoader(true)] + private void load(MainSettings settings) + { + StateContainer = settings; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 519210d6e2..f45950cb4c 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarSocialButton : ToolbarOverlayToggleButton - { - public ToolbarSocialButton() - { - Icon = FontAwesome.fa_users; - } - - [BackgroundDependencyLoader(true)] - private void load(SocialOverlay chat) - { - StateContainer = chat; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarSocialButton : ToolbarOverlayToggleButton + { + public ToolbarSocialButton() + { + Icon = FontAwesome.fa_users; + } + + [BackgroundDependencyLoader(true)] + private void load(SocialOverlay chat) + { + StateContainer = chat; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs index ba015119a0..c30d58b0d6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs @@ -1,41 +1,41 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarUserArea : Container - { - public LoginOverlay LoginOverlay; - private ToolbarUserButton button; - - public override RectangleF BoundingBox => button.BoundingBox; - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - Children = new Drawable[] { - button = new ToolbarUserButton - { - Action = () => LoginOverlay.ToggleVisibility(), - }, - LoginOverlay = new LoginOverlay - { - BypassAutoSizeAxes = Axes.Both, - Position = new Vector2(0, 1), - RelativePositionAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarUserArea : Container + { + public LoginOverlay LoginOverlay; + private ToolbarUserButton button; + + public override RectangleF BoundingBox => button.BoundingBox; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + Children = new Drawable[] { + button = new ToolbarUserButton + { + Action = () => LoginOverlay.ToggleVisibility(), + }, + LoginOverlay = new LoginOverlay + { + BypassAutoSizeAxes = Axes.Both, + Position = new Vector2(0, 1), + RelativePositionAxes = Axes.Y, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + } + }; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 16586adc0c..b01a4c48b6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.API; -using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarUserButton : ToolbarButton, IOnlineComponent - { - private readonly UpdateableAvatar avatar; - - public ToolbarUserButton() - { - AutoSizeAxes = Axes.X; - - DrawableText.Font = @"Exo2.0-MediumItalic"; - - Add(new OpaqueBackground { Depth = 1 }); - - Flow.Add(avatar = new UpdateableAvatar - { - Masking = true, - Size = new Vector2(32), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 4, - Colour = Color4.Black.Opacity(0.1f), - } - }); - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - api.Register(this); - } - - public void APIStateChanged(APIAccess api, APIState state) - { - switch (state) - { - default: - Text = @"Guest"; - avatar.User = new User(); - break; - case APIState.Online: - Text = api.LocalUser.Value.Username; - avatar.User = api.LocalUser; - break; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Users; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarUserButton : ToolbarButton, IOnlineComponent + { + private readonly UpdateableAvatar avatar; + + public ToolbarUserButton() + { + AutoSizeAxes = Axes.X; + + DrawableText.Font = @"Exo2.0-MediumItalic"; + + Add(new OpaqueBackground { Depth = 1 }); + + Flow.Add(avatar = new UpdateableAvatar + { + Masking = true, + Size = new Vector2(32), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 4, + Colour = Color4.Black.Opacity(0.1f), + } + }); + } + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + api.Register(this); + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + default: + Text = @"Guest"; + avatar.User = new User(); + break; + case APIState.Online: + Text = api.LocalUser.Value.Username; + avatar.User = api.LocalUser; + break; + } + } + } +} diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index aed0a6d7c6..c6e8c60f92 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -1,227 +1,227 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Profile; -using osu.Game.Overlays.Profile.Sections; -using osu.Game.Users; - -namespace osu.Game.Overlays -{ - public class UserProfileOverlay : WaveOverlayContainer - { - private ProfileSection lastSection; - private ProfileSection[] sections; - private GetUserRequest userReq; - private APIAccess api; - protected ProfileHeader Header; - private SectionsContainer sectionsContainer; - private ProfileTabControl tabs; - - public const float CONTENT_X_MARGIN = 50; - - public UserProfileOverlay() - { - FirstWaveColour = OsuColour.Gray(0.4f); - SecondWaveColour = OsuColour.Gray(0.3f); - ThirdWaveColour = OsuColour.Gray(0.2f); - FourthWaveColour = OsuColour.Gray(0.1f); - - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; - Width = 0.85f; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0), - Type = EdgeEffectType.Shadow, - Radius = 10 - }; - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - } - - protected override void PopIn() - { - base.PopIn(); - FadeEdgeEffectTo(0.5f, APPEAR_DURATION, Easing.In); - } - - protected override void PopOut() - { - base.PopOut(); - FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out); - } - - public void ShowUser(long userId) - { - if (userId == Header.User.Id) - return; - - ShowUser(new User { Id = userId }); - } - - public void ShowUser(User user, bool fetchOnline = true) - { - userReq?.Cancel(); - Clear(); - lastSection = null; - - sections = new ProfileSection[] - { - //new AboutSection(), - new RecentSection(), - new RanksSection(), - //new MedalsSection(), - new HistoricalSection(), - new BeatmapsSection(), - new KudosuSection() - }; - tabs = new ProfileTabControl - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = 30 - }; - - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }); - - Header = new ProfileHeader(user); - - Add(sectionsContainer = new SectionsContainer - { - RelativeSizeAxes = Axes.Both, - ExpandableHeader = Header, - FixedHeader = tabs, - HeaderBackground = new Box - { - Colour = OsuColour.Gray(34), - RelativeSizeAxes = Axes.Both - } - }); - sectionsContainer.SelectedSection.ValueChanged += s => - { - if (lastSection != s) - { - lastSection = s; - tabs.Current.Value = lastSection; - } - }; - - tabs.Current.ValueChanged += s => - { - if (lastSection == null) - { - lastSection = sectionsContainer.Children.FirstOrDefault(); - if (lastSection != null) - tabs.Current.Value = lastSection; - return; - } - if (lastSection != s) - { - lastSection = s; - sectionsContainer.ScrollTo(lastSection); - } - }; - - if (fetchOnline) - { - userReq = new GetUserRequest(user.Id); - userReq.Success += userLoadComplete; - api.Queue(userReq); - } - else - { - userReq = null; - userLoadComplete(user); - } - - Show(); - sectionsContainer.ScrollToTop(); - } - - private void userLoadComplete(User user) - { - Header.User = user; - - if (user.ProfileOrder != null) - { - foreach (string id in user.ProfileOrder) - { - var sec = sections.FirstOrDefault(s => s.Identifier == id); - if (sec != null) - { - sec.User.Value = user; - - sectionsContainer.Add(sec); - tabs.AddItem(sec); - } - } - } - } - - private class ProfileTabControl : PageTabControl - { - private readonly Box bottom; - - public ProfileTabControl() - { - TabContainer.RelativeSizeAxes &= ~Axes.X; - TabContainer.AutoSizeAxes |= Axes.X; - TabContainer.Anchor |= Anchor.x1; - TabContainer.Origin |= Anchor.x1; - Add(bottom = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - EdgeSmoothness = new Vector2(1) - }); - } - - protected override TabItem CreateTabItem(ProfileSection value) => new ProfileTabItem(value); - - protected override Dropdown CreateDropdown() => null; - - private class ProfileTabItem : PageTabItem - { - public ProfileTabItem(ProfileSection value) : base(value) - { - Text.Text = value.Title; - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - bottom.Colour = colours.Yellow; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Users; + +namespace osu.Game.Overlays +{ + public class UserProfileOverlay : WaveOverlayContainer + { + private ProfileSection lastSection; + private ProfileSection[] sections; + private GetUserRequest userReq; + private APIAccess api; + protected ProfileHeader Header; + private SectionsContainer sectionsContainer; + private ProfileTabControl tabs; + + public const float CONTENT_X_MARGIN = 50; + + public UserProfileOverlay() + { + FirstWaveColour = OsuColour.Gray(0.4f); + SecondWaveColour = OsuColour.Gray(0.3f); + ThirdWaveColour = OsuColour.Gray(0.2f); + FourthWaveColour = OsuColour.Gray(0.1f); + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Width = 0.85f; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0), + Type = EdgeEffectType.Shadow, + Radius = 10 + }; + } + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + this.api = api; + } + + protected override void PopIn() + { + base.PopIn(); + FadeEdgeEffectTo(0.5f, APPEAR_DURATION, Easing.In); + } + + protected override void PopOut() + { + base.PopOut(); + FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out); + } + + public void ShowUser(long userId) + { + if (userId == Header.User.Id) + return; + + ShowUser(new User { Id = userId }); + } + + public void ShowUser(User user, bool fetchOnline = true) + { + userReq?.Cancel(); + Clear(); + lastSection = null; + + sections = new ProfileSection[] + { + //new AboutSection(), + new RecentSection(), + new RanksSection(), + //new MedalsSection(), + new HistoricalSection(), + new BeatmapsSection(), + new KudosuSection() + }; + tabs = new ProfileTabControl + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 30 + }; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }); + + Header = new ProfileHeader(user); + + Add(sectionsContainer = new SectionsContainer + { + RelativeSizeAxes = Axes.Both, + ExpandableHeader = Header, + FixedHeader = tabs, + HeaderBackground = new Box + { + Colour = OsuColour.Gray(34), + RelativeSizeAxes = Axes.Both + } + }); + sectionsContainer.SelectedSection.ValueChanged += s => + { + if (lastSection != s) + { + lastSection = s; + tabs.Current.Value = lastSection; + } + }; + + tabs.Current.ValueChanged += s => + { + if (lastSection == null) + { + lastSection = sectionsContainer.Children.FirstOrDefault(); + if (lastSection != null) + tabs.Current.Value = lastSection; + return; + } + if (lastSection != s) + { + lastSection = s; + sectionsContainer.ScrollTo(lastSection); + } + }; + + if (fetchOnline) + { + userReq = new GetUserRequest(user.Id); + userReq.Success += userLoadComplete; + api.Queue(userReq); + } + else + { + userReq = null; + userLoadComplete(user); + } + + Show(); + sectionsContainer.ScrollToTop(); + } + + private void userLoadComplete(User user) + { + Header.User = user; + + if (user.ProfileOrder != null) + { + foreach (string id in user.ProfileOrder) + { + var sec = sections.FirstOrDefault(s => s.Identifier == id); + if (sec != null) + { + sec.User.Value = user; + + sectionsContainer.Add(sec); + tabs.AddItem(sec); + } + } + } + } + + private class ProfileTabControl : PageTabControl + { + private readonly Box bottom; + + public ProfileTabControl() + { + TabContainer.RelativeSizeAxes &= ~Axes.X; + TabContainer.AutoSizeAxes |= Axes.X; + TabContainer.Anchor |= Anchor.x1; + TabContainer.Origin |= Anchor.x1; + Add(bottom = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + EdgeSmoothness = new Vector2(1) + }); + } + + protected override TabItem CreateTabItem(ProfileSection value) => new ProfileTabItem(value); + + protected override Dropdown CreateDropdown() => null; + + private class ProfileTabItem : PageTabItem + { + public ProfileTabItem(ProfileSection value) : base(value) + { + Text.Text = value.Title; + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bottom.Colour = colours.Yellow; + } + } + } +} diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index adfc9c610f..b62c639ee3 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -1,83 +1,83 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Volume -{ - public class MuteButton : Container, IHasCurrentValue - { - public Bindable Current { get; } = new Bindable(); - - private Color4 hoveredColour, unhoveredColour; - private const float width = 100; - public const float HEIGHT = 35; - - public MuteButton() - { - Masking = true; - BorderThickness = 3; - CornerRadius = HEIGHT / 2; - Size = new Vector2(width, HEIGHT); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoveredColour = colours.YellowDark; - BorderColour = unhoveredColour = colours.Gray1.Opacity(0.9f); - - SpriteIcon icon; - AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - Alpha = 0.9f, - }, - icon = new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - } - }); - - Current.ValueChanged += newValue => - { - icon.Icon = newValue ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up; - icon.Margin = new MarginPadding { Left = newValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths - }; - Current.TriggerChange(); - } - - protected override bool OnHover(InputState state) - { - this.TransformTo("BorderColour", hoveredColour, 500, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(InputState state) - { - this.TransformTo("BorderColour", unhoveredColour, 500, Easing.OutQuint); - } - - protected override bool OnClick(InputState state) - { - Current.Value = !Current.Value; - return true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Volume +{ + public class MuteButton : Container, IHasCurrentValue + { + public Bindable Current { get; } = new Bindable(); + + private Color4 hoveredColour, unhoveredColour; + private const float width = 100; + public const float HEIGHT = 35; + + public MuteButton() + { + Masking = true; + BorderThickness = 3; + CornerRadius = HEIGHT / 2; + Size = new Vector2(width, HEIGHT); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredColour = colours.YellowDark; + BorderColour = unhoveredColour = colours.Gray1.Opacity(0.9f); + + SpriteIcon icon; + AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + Alpha = 0.9f, + }, + icon = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + } + }); + + Current.ValueChanged += newValue => + { + icon.Icon = newValue ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up; + icon.Margin = new MarginPadding { Left = newValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths + }; + Current.TriggerChange(); + } + + protected override bool OnHover(InputState state) + { + this.TransformTo("BorderColour", hoveredColour, 500, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(InputState state) + { + this.TransformTo("BorderColour", unhoveredColour, 500, Easing.OutQuint); + } + + protected override bool OnClick(InputState state) + { + Current.Value = !Current.Value; + return true; + } + } +} diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index a5be7dc445..572b3f0c27 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Game.Input.Bindings; - -namespace osu.Game.Overlays.Volume -{ - public class VolumeControlReceptor : Container, IKeyBindingHandler, IHandleGlobalInput - { - public Func ActionRequested; - - public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; - public bool OnReleased(GlobalAction action) => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; + +namespace osu.Game.Overlays.Volume +{ + public class VolumeControlReceptor : Container, IKeyBindingHandler, IHandleGlobalInput + { + public Func ActionRequested; + + public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; + public bool OnReleased(GlobalAction action) => false; + } +} diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index b1951f4d72..64106967f4 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -1,193 +1,193 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Globalization; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Bindings; -using osu.Framework.MathUtils; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Input.Bindings; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays.Volume -{ - public class VolumeMeter : Container, IKeyBindingHandler - { - private CircularProgress volumeCircle; - public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; - private readonly float circleSize; - private readonly Color4 meterColour; - private readonly string name; - - private OsuSpriteText text; - private BufferedContainer maxGlow; - - public VolumeMeter(string name, float circleSize, Color4 meterColour) - { - this.circleSize = circleSize; - this.meterColour = meterColour; - this.name = name; - - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Add(new Container - { - Size = new Vector2(120, 20), - CornerRadius = 10, - Masking = true, - Margin = new MarginPadding { Left = circleSize + 10 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - Alpha = 0.9f, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = "Exo2.0-Bold", - Text = name - } - } - }); - - CircularProgress bgProgress; - - Add(new CircularContainer - { - Masking = true, - Size = new Vector2(circleSize), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - Alpha = 0.9f, - }, - bgProgress = new CircularProgress - { - RelativeSizeAxes = Axes.Both, - InnerRadius = 0.05f, - Rotation = 180, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.Gray2, - Size = new Vector2(0.8f) - }, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.8f), - Padding = new MarginPadding(-Blur.KernelSize(5)), - Rotation = 180, - Child = (volumeCircle = new CircularProgress - { - RelativeSizeAxes = Axes.Both, - InnerRadius = 0.05f, - }).WithEffect(new GlowEffect - { - Colour = meterColour, - Strength = 2, - PadExtent = true - }), - }, - maxGlow = (text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = "Venera", - TextSize = 0.16f * circleSize - }).WithEffect(new GlowEffect - { - Colour = Color4.Transparent, - PadExtent = true, - }) - } - }); - - Bindable.ValueChanged += newVolume => { this.TransformTo("DisplayVolume", newVolume, 400, Easing.OutQuint); }; - bgProgress.Current.Value = 0.75f; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Bindable.TriggerChange(); - } - - private double displayVolume; - - protected double DisplayVolume - { - get => displayVolume; - set - { - displayVolume = value; - - if (displayVolume > 0.99f) - { - text.Text = "MAX"; - maxGlow.EffectColour = meterColour.Opacity(2f); - } - else - { - maxGlow.EffectColour = Color4.Transparent; - text.Text = Math.Round(displayVolume * 100).ToString(CultureInfo.CurrentCulture); - } - - volumeCircle.Current.Value = displayVolume * 0.75f; - } - } - - public double Volume - { - get => Bindable; - private set => Bindable.Value = value; - } - - public void Increase() => Volume += 0.05f; - - public void Decrease() => Volume -= 0.05f; - - public bool OnPressed(GlobalAction action) - { - if (!IsHovered) return false; - - switch (action) - { - case GlobalAction.DecreaseVolume: - Decrease(); - return true; - case GlobalAction.IncreaseVolume: - Increase(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Globalization; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays.Volume +{ + public class VolumeMeter : Container, IKeyBindingHandler + { + private CircularProgress volumeCircle; + public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + private readonly float circleSize; + private readonly Color4 meterColour; + private readonly string name; + + private OsuSpriteText text; + private BufferedContainer maxGlow; + + public VolumeMeter(string name, float circleSize, Color4 meterColour) + { + this.circleSize = circleSize; + this.meterColour = meterColour; + this.name = name; + + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Add(new Container + { + Size = new Vector2(120, 20), + CornerRadius = 10, + Masking = true, + Margin = new MarginPadding { Left = circleSize + 10 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + Alpha = 0.9f, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = "Exo2.0-Bold", + Text = name + } + } + }); + + CircularProgress bgProgress; + + Add(new CircularContainer + { + Masking = true, + Size = new Vector2(circleSize), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + Alpha = 0.9f, + }, + bgProgress = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + InnerRadius = 0.05f, + Rotation = 180, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.Gray2, + Size = new Vector2(0.8f) + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + Padding = new MarginPadding(-Blur.KernelSize(5)), + Rotation = 180, + Child = (volumeCircle = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + InnerRadius = 0.05f, + }).WithEffect(new GlowEffect + { + Colour = meterColour, + Strength = 2, + PadExtent = true + }), + }, + maxGlow = (text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = "Venera", + TextSize = 0.16f * circleSize + }).WithEffect(new GlowEffect + { + Colour = Color4.Transparent, + PadExtent = true, + }) + } + }); + + Bindable.ValueChanged += newVolume => { this.TransformTo("DisplayVolume", newVolume, 400, Easing.OutQuint); }; + bgProgress.Current.Value = 0.75f; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Bindable.TriggerChange(); + } + + private double displayVolume; + + protected double DisplayVolume + { + get => displayVolume; + set + { + displayVolume = value; + + if (displayVolume > 0.99f) + { + text.Text = "MAX"; + maxGlow.EffectColour = meterColour.Opacity(2f); + } + else + { + maxGlow.EffectColour = Color4.Transparent; + text.Text = Math.Round(displayVolume * 100).ToString(CultureInfo.CurrentCulture); + } + + volumeCircle.Current.Value = displayVolume * 0.75f; + } + } + + public double Volume + { + get => Bindable; + private set => Bindable.Value = value; + } + + public void Increase() => Volume += 0.05f; + + public void Decrease() => Volume -= 0.05f; + + public bool OnPressed(GlobalAction action) + { + if (!IsHovered) return false; + + switch (action) + { + case GlobalAction.DecreaseVolume: + Decrease(); + return true; + case GlobalAction.IncreaseVolume: + Increase(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => false; + } +} diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 17a4b139b0..da63495fec 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -1,147 +1,147 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Threading; -using osu.Game.Graphics; -using osu.Game.Input.Bindings; -using osu.Game.Overlays.Volume; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Overlays -{ - public class VolumeOverlay : OverlayContainer - { - private const float offset = 10; - - private VolumeMeter volumeMeterMaster; - private VolumeMeter volumeMeterEffect; - private VolumeMeter volumeMeterMusic; - private MuteButton muteButton; - - protected override bool BlockPassThroughMouse => false; - - private readonly BindableDouble muteAdjustment = new BindableDouble(); - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - - AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Y, - Width = 300, - Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0)) - }, - new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Spacing = new Vector2(0, offset), - Margin = new MarginPadding { Left = offset }, - Children = new Drawable[] - { - volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) - { - Margin = new MarginPadding { Top = 100 + MuteButton.HEIGHT } //to counter the mute button and re-center the volume meters - }, - volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), - volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), - muteButton = new MuteButton - { - Margin = new MarginPadding { Top = 100 } - } - } - }, - }); - - volumeMeterMaster.Bindable.BindTo(audio.Volume); - volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); - volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); - - muteButton.Current.ValueChanged += mute => - { - if (mute) - audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); - else - audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - volumeMeterMaster.Bindable.ValueChanged += _ => settingChanged(); - volumeMeterEffect.Bindable.ValueChanged += _ => settingChanged(); - volumeMeterMusic.Bindable.ValueChanged += _ => settingChanged(); - muteButton.Current.ValueChanged += _ => settingChanged(); - } - - private void settingChanged() - { - Show(); - schedulePopOut(); - } - - public bool Adjust(GlobalAction action) - { - switch (action) - { - case GlobalAction.DecreaseVolume: - if (State == Visibility.Hidden) - Show(); - else - volumeMeterMaster.Decrease(); - return true; - case GlobalAction.IncreaseVolume: - if (State == Visibility.Hidden) - Show(); - else - volumeMeterMaster.Increase(); - return true; - case GlobalAction.ToggleMute: - Show(); - muteButton.Current.Value = !muteButton.Current; - return true; - } - - return false; - } - - private ScheduledDelegate popOutDelegate; - - protected override void PopIn() - { - ClearTransforms(); - this.FadeIn(100); - - schedulePopOut(); - } - - protected override void PopOut() - { - this.FadeOut(100); - } - - private void schedulePopOut() - { - popOutDelegate?.Cancel(); - this.Delay(1000).Schedule(Hide, out popOutDelegate); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Graphics; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.Volume; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Overlays +{ + public class VolumeOverlay : OverlayContainer + { + private const float offset = 10; + + private VolumeMeter volumeMeterMaster; + private VolumeMeter volumeMeterEffect; + private VolumeMeter volumeMeterMusic; + private MuteButton muteButton; + + protected override bool BlockPassThroughMouse => false; + + private readonly BindableDouble muteAdjustment = new BindableDouble(); + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Y, + Width = 300, + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0)) + }, + new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Spacing = new Vector2(0, offset), + Margin = new MarginPadding { Left = offset }, + Children = new Drawable[] + { + volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) + { + Margin = new MarginPadding { Top = 100 + MuteButton.HEIGHT } //to counter the mute button and re-center the volume meters + }, + volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), + volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), + muteButton = new MuteButton + { + Margin = new MarginPadding { Top = 100 } + } + } + }, + }); + + volumeMeterMaster.Bindable.BindTo(audio.Volume); + volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); + volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); + + muteButton.Current.ValueChanged += mute => + { + if (mute) + audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); + else + audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + volumeMeterMaster.Bindable.ValueChanged += _ => settingChanged(); + volumeMeterEffect.Bindable.ValueChanged += _ => settingChanged(); + volumeMeterMusic.Bindable.ValueChanged += _ => settingChanged(); + muteButton.Current.ValueChanged += _ => settingChanged(); + } + + private void settingChanged() + { + Show(); + schedulePopOut(); + } + + public bool Adjust(GlobalAction action) + { + switch (action) + { + case GlobalAction.DecreaseVolume: + if (State == Visibility.Hidden) + Show(); + else + volumeMeterMaster.Decrease(); + return true; + case GlobalAction.IncreaseVolume: + if (State == Visibility.Hidden) + Show(); + else + volumeMeterMaster.Increase(); + return true; + case GlobalAction.ToggleMute: + Show(); + muteButton.Current.Value = !muteButton.Current; + return true; + } + + return false; + } + + private ScheduledDelegate popOutDelegate; + + protected override void PopIn() + { + ClearTransforms(); + this.FadeIn(100); + + schedulePopOut(); + } + + protected override void PopOut() + { + this.FadeOut(100); + } + + private void schedulePopOut() + { + popOutDelegate?.Cancel(); + this.Delay(1000).Schedule(Hide, out popOutDelegate); + } + } +} diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 074d83a5ad..6a083a77e5 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -1,203 +1,203 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using System; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays -{ - public abstract class WaveOverlayContainer : OsuFocusedOverlayContainer - { - protected const float APPEAR_DURATION = 800; - protected const float DISAPPEAR_DURATION = 500; - - private const Easing easing_show = Easing.OutSine; - private const Easing easing_hide = Easing.InSine; - - private readonly Wave firstWave; - private readonly Wave secondWave; - private readonly Wave thirdWave; - private readonly Wave fourthWave; - - private readonly Container wavesContainer; - - private readonly Container contentContainer; - - protected override bool BlockPassThroughKeyboard => true; - - protected override Container Content => contentContainer; - - protected Color4 FirstWaveColour - { - get - { - return firstWave.Colour; - } - set - { - if (firstWave.Colour == value) return; - firstWave.Colour = value; - } - } - - protected Color4 SecondWaveColour - { - get - { - return secondWave.Colour; - } - set - { - if (secondWave.Colour == value) return; - secondWave.Colour = value; - } - } - - protected Color4 ThirdWaveColour - { - get - { - return thirdWave.Colour; - } - set - { - if (thirdWave.Colour == value) return; - thirdWave.Colour = value; - } - } - - protected Color4 FourthWaveColour - { - get - { - return fourthWave.Colour; - } - set - { - if (fourthWave.Colour == value) return; - fourthWave.Colour = value; - } - } - - protected WaveOverlayContainer() - { - Masking = true; - - AddInternal(wavesContainer = new Container - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Masking = true, - Children = new[] - { - firstWave = new Wave - { - Rotation = 13, - FinalPosition = -930, - }, - secondWave = new Wave - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Rotation = -7, - FinalPosition = -560, - }, - thirdWave = new Wave - { - Rotation = 4, - FinalPosition = -390, - }, - fourthWave = new Wave - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Rotation = -2, - FinalPosition = -220, - }, - }, - }); - - AddInternal(contentContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - }); - } - - protected override void PopIn() - { - base.PopIn(); - - foreach (var w in wavesContainer.Children) - w.State = Visibility.Visible; - - this.FadeIn(100, Easing.OutQuint); - contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); - - this.FadeIn(100, Easing.OutQuint); - } - - protected override void PopOut() - { - base.PopOut(); - - this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); - contentContainer.MoveToY(DrawHeight * 2f, DISAPPEAR_DURATION, Easing.In); - - foreach (var w in wavesContainer.Children) - w.State = Visibility.Hidden; - - this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // This is done as an optimization, such that invisible parts of the waves - // are masked away, and thus do not consume fill rate. - wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y)); - } - - private class Wave : VisibilityContainer - { - public float FinalPosition; - - protected override bool StartHidden => true; - - public Wave() - { - RelativeSizeAxes = Axes.X; - Width = 1.5f; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(50), - Radius = 20f, - }; - - Child = new Box { RelativeSizeAxes = Axes.Both }; - } - - protected override void Update() - { - base.Update(); - - // We can not use RelativeSizeAxes for Height, because the height - // of our parent diminishes as the content moves up. - Height = Parent.Parent.DrawSize.Y * 1.5f; - } - - protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); - protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using System; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays +{ + public abstract class WaveOverlayContainer : OsuFocusedOverlayContainer + { + protected const float APPEAR_DURATION = 800; + protected const float DISAPPEAR_DURATION = 500; + + private const Easing easing_show = Easing.OutSine; + private const Easing easing_hide = Easing.InSine; + + private readonly Wave firstWave; + private readonly Wave secondWave; + private readonly Wave thirdWave; + private readonly Wave fourthWave; + + private readonly Container wavesContainer; + + private readonly Container contentContainer; + + protected override bool BlockPassThroughKeyboard => true; + + protected override Container Content => contentContainer; + + protected Color4 FirstWaveColour + { + get + { + return firstWave.Colour; + } + set + { + if (firstWave.Colour == value) return; + firstWave.Colour = value; + } + } + + protected Color4 SecondWaveColour + { + get + { + return secondWave.Colour; + } + set + { + if (secondWave.Colour == value) return; + secondWave.Colour = value; + } + } + + protected Color4 ThirdWaveColour + { + get + { + return thirdWave.Colour; + } + set + { + if (thirdWave.Colour == value) return; + thirdWave.Colour = value; + } + } + + protected Color4 FourthWaveColour + { + get + { + return fourthWave.Colour; + } + set + { + if (fourthWave.Colour == value) return; + fourthWave.Colour = value; + } + } + + protected WaveOverlayContainer() + { + Masking = true; + + AddInternal(wavesContainer = new Container + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Masking = true, + Children = new[] + { + firstWave = new Wave + { + Rotation = 13, + FinalPosition = -930, + }, + secondWave = new Wave + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Rotation = -7, + FinalPosition = -560, + }, + thirdWave = new Wave + { + Rotation = 4, + FinalPosition = -390, + }, + fourthWave = new Wave + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Rotation = -2, + FinalPosition = -220, + }, + }, + }); + + AddInternal(contentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); + } + + protected override void PopIn() + { + base.PopIn(); + + foreach (var w in wavesContainer.Children) + w.State = Visibility.Visible; + + this.FadeIn(100, Easing.OutQuint); + contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); + + this.FadeIn(100, Easing.OutQuint); + } + + protected override void PopOut() + { + base.PopOut(); + + this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); + contentContainer.MoveToY(DrawHeight * 2f, DISAPPEAR_DURATION, Easing.In); + + foreach (var w in wavesContainer.Children) + w.State = Visibility.Hidden; + + this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // This is done as an optimization, such that invisible parts of the waves + // are masked away, and thus do not consume fill rate. + wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y)); + } + + private class Wave : VisibilityContainer + { + public float FinalPosition; + + protected override bool StartHidden => true; + + public Wave() + { + RelativeSizeAxes = Axes.X; + Width = 1.5f; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(50), + Radius = 20f, + }; + + Child = new Box { RelativeSizeAxes = Axes.Both }; + } + + protected override void Update() + { + base.Update(); + + // We can not use RelativeSizeAxes for Height, because the height + // of our parent diminishes as the content moves up. + Height = Parent.Parent.DrawSize.Y * 1.5f; + } + + protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); + protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); + } + } +} diff --git a/osu.Game/Properties/AssemblyInfo.cs b/osu.Game/Properties/AssemblyInfo.cs index 9384740308..df9045b802 100644 --- a/osu.Game/Properties/AssemblyInfo.cs +++ b/osu.Game/Properties/AssemblyInfo.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Runtime.CompilerServices; - -// We publish our internal attributes to other sub-projects of the framework. -// Note, that we omit visual tests as they are meant to test the framework -// behavior "in the wild". - -[assembly: InternalsVisibleTo("osu.Game.Tests")] -[assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")] +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Runtime.CompilerServices; + +// We publish our internal attributes to other sub-projects of the framework. +// Note, that we omit visual tests as they are meant to test the framework +// behavior "in the wild". + +[assembly: InternalsVisibleTo("osu.Game.Tests")] +[assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")] diff --git a/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs index 56eac730b0..09c74d8bac 100644 --- a/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration.Tracking; - -namespace osu.Game.Rulesets.Configuration -{ - public interface IRulesetConfigManager : ITrackableConfigManager - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration.Tracking; + +namespace osu.Game.Rulesets.Configuration +{ + public interface IRulesetConfigManager : ITrackableConfigManager + { + } +} diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 9f244f6267..4ecf1eefb2 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Configuration; - -namespace osu.Game.Rulesets.Configuration -{ - public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager - where T : struct - { - protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) : base(settings, ruleset, variant) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Configuration; + +namespace osu.Game.Rulesets.Configuration +{ + public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager + where T : struct + { + protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) : base(settings, ruleset, variant) + { + } + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 9b33ad2563..1820053d3d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -1,261 +1,149 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Logging; -using osu.Framework.MathUtils; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Edit.Screens.Compose; -using osu.Game.Screens.Edit.Screens.Compose.Layers; -using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; - -namespace osu.Game.Rulesets.Edit -{ - public abstract class HitObjectComposer : CompositeDrawable - { - private readonly Ruleset ruleset; - - protected ICompositionTool CurrentTool { get; private set; } - - private RulesetContainer rulesetContainer; - private readonly List layerContainers = new List(); - - private readonly Bindable beatmap = new Bindable(); - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - - private IAdjustableClock adjustableClock; - - protected HitObjectComposer(Ruleset ruleset) - { - this.ruleset = ruleset; - - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader(true)] - private void load([NotNull] OsuGameBase osuGame, [NotNull] IAdjustableClock adjustableClock, [NotNull] IFrameBasedClock framedClock, [CanBeNull] BindableBeatDivisor beatDivisor) - { - this.adjustableClock = adjustableClock; - - if (beatDivisor != null) - this.beatDivisor.BindTo(beatDivisor); - - beatmap.BindTo(osuGame.Beatmap); - - try - { - rulesetContainer = CreateRulesetContainer(ruleset, beatmap.Value); - rulesetContainer.Clock = framedClock; - } - catch (Exception e) - { - Logger.Error(e, "Could not load beatmap sucessfully!"); - return; - } - - var layerBelowRuleset = new BorderLayer - { - RelativeSizeAxes = Axes.Both, - Child = CreateLayerContainer() - }; - - var layerAboveRuleset = CreateLayerContainer(); - layerAboveRuleset.Child = new HitObjectMaskLayer(rulesetContainer.Playfield, this); - - layerContainers.Add(layerBelowRuleset); - layerContainers.Add(layerAboveRuleset); - - RadioButtonCollection toolboxCollection; - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new FillFlowContainer - { - Name = "Sidebar", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 10 }, - Children = new Drawable[] - { - new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } } - } - }, - new Container - { - Name = "Content", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - layerBelowRuleset, - rulesetContainer, - layerAboveRuleset - } - } - }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 200), - } - }; - - toolboxCollection.Items = - CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) - .Prepend(new RadioButton("Select", () => setCompositionTool(null))) - .ToList(); - - toolboxCollection.Items[0].Select(); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - layerContainers.ForEach(l => - { - l.Anchor = rulesetContainer.Playfield.Anchor; - l.Origin = rulesetContainer.Playfield.Origin; - l.Position = rulesetContainer.Playfield.Position; - l.Size = rulesetContainer.Playfield.Size; - }); - } - - protected override bool OnWheel(InputState state) - { - if (state.Mouse.WheelDelta > 0) - SeekBackward(true); - else - SeekForward(true); - return true; - } - - /// - /// Seeks the current time one beat-snapped beat-length backwards. - /// - /// Whether to snap to the closest beat. - public void SeekBackward(bool snapped = false) => seek(-1, snapped); - - /// - /// Seeks the current time one beat-snapped beat-length forwards. - /// - /// Whether to snap to the closest beat. - public void SeekForward(bool snapped = false) => seek(1, snapped); - - private void seek(int direction, bool snapped) - { - var cpi = beatmap.Value.Beatmap.ControlPointInfo; - - var timingPoint = cpi.TimingPointAt(adjustableClock.CurrentTime); - if (direction < 0 && timingPoint.Time == adjustableClock.CurrentTime) - { - // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into - int activeIndex = cpi.TimingPoints.IndexOf(timingPoint); - while (activeIndex > 0 && adjustableClock.CurrentTime == timingPoint.Time) - timingPoint = cpi.TimingPoints[--activeIndex]; - } - - double seekAmount = timingPoint.BeatLength / beatDivisor; - double seekTime = adjustableClock.CurrentTime + seekAmount * direction; - - if (!snapped || cpi.TimingPoints.Count == 0) - { - adjustableClock.Seek(seekTime); - return; - } - - // We will be snapping to beats within timingPoint - seekTime -= timingPoint.Time; - - // Determine the index from timingPoint of the closest beat to seekTime, accounting for scrolling direction - int closestBeat; - if (direction > 0) - closestBeat = (int)Math.Floor(seekTime / seekAmount); - else - closestBeat = (int)Math.Ceiling(seekTime / seekAmount); - - seekTime = timingPoint.Time + closestBeat * seekAmount; - - // Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this. - // Instead, we'll go to the next beat in the direction when this is the case - if (Precision.AlmostEquals(adjustableClock.CurrentTime, seekTime)) - { - closestBeat += direction > 0 ? 1 : -1; - seekTime = timingPoint.Time + closestBeat * seekAmount; - } - - if (seekTime < timingPoint.Time && timingPoint != cpi.TimingPoints.First()) - seekTime = timingPoint.Time; - - var nextTimingPoint = cpi.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); - if (seekTime > nextTimingPoint?.Time) - seekTime = nextTimingPoint.Time; - - adjustableClock.Seek(seekTime); - } - - public void SeekTo(double seekTime, bool snapped = false) - { - if (!snapped) - { - adjustableClock.Seek(seekTime); - return; - } - - var timingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(seekTime); - double beatSnapLength = timingPoint.BeatLength / beatDivisor; - - // We will be snapping to beats within the timing point - seekTime -= timingPoint.Time; - - // Determine the index from the current timing point of the closest beat to seekTime - int closestBeat = (int)Math.Round(seekTime / beatSnapLength); - seekTime = timingPoint.Time + closestBeat * beatSnapLength; - - // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to - // the next timing point's start time - var nextTimingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); - if (seekTime > nextTimingPoint?.Time) - seekTime = nextTimingPoint.Time; - - adjustableClock.Seek(seekTime); - } - - private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; - - protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); - - protected abstract IReadOnlyList CompositionTools { get; } - - /// - /// Creates a for a specific . - /// - /// The to create the overlay for. - public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; - - /// - /// Creates a which outlines s - /// and handles hitobject pattern adjustments. - /// - public virtual MaskSelection CreateMaskSelection() => new MaskSelection(); - - /// - /// Creates a which provides a layer above or below the . - /// - protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Screens.Compose.Layers; +using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class HitObjectComposer : CompositeDrawable + { + private readonly Ruleset ruleset; + + protected ICompositionTool CurrentTool { get; private set; } + + private RulesetContainer rulesetContainer; + private readonly List layerContainers = new List(); + + private readonly Bindable beatmap = new Bindable(); + + protected HitObjectComposer(Ruleset ruleset) + { + this.ruleset = ruleset; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame, IFrameBasedClock framedClock) + { + beatmap.BindTo(osuGame.Beatmap); + + try + { + rulesetContainer = CreateRulesetContainer(ruleset, beatmap.Value); + rulesetContainer.Clock = framedClock; + } + catch (Exception e) + { + Logger.Error(e, "Could not load beatmap sucessfully!"); + return; + } + + var layerBelowRuleset = new BorderLayer + { + RelativeSizeAxes = Axes.Both, + Child = CreateLayerContainer() + }; + + var layerAboveRuleset = CreateLayerContainer(); + layerAboveRuleset.Child = new HitObjectMaskLayer(rulesetContainer.Playfield, this); + + layerContainers.Add(layerBelowRuleset); + layerContainers.Add(layerAboveRuleset); + + RadioButtonCollection toolboxCollection; + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + Name = "Sidebar", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } } + } + }, + new Container + { + Name = "Content", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + layerBelowRuleset, + rulesetContainer, + layerAboveRuleset + } + } + }, + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 200), + } + }; + + toolboxCollection.Items = + CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) + .Prepend(new RadioButton("Select", () => setCompositionTool(null))) + .ToList(); + + toolboxCollection.Items[0].Select(); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + layerContainers.ForEach(l => + { + l.Anchor = rulesetContainer.Playfield.Anchor; + l.Origin = rulesetContainer.Playfield.Origin; + l.Position = rulesetContainer.Playfield.Position; + l.Size = rulesetContainer.Playfield.Size; + }); + } + + private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; + + protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); + + protected abstract IReadOnlyList CompositionTools { get; } + + /// + /// Creates a for a specific . + /// + /// The to create the overlay for. + public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; + + /// + /// Creates a which outlines s + /// and handles hitobject pattern adjustments. + /// + public virtual MaskSelection CreateMaskSelection() => new MaskSelection(); + + /// + /// Creates a which provides a layer above or below the . + /// + protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both }; + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 9f055ffc5d..ad7c27ad80 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -1,146 +1,146 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Input; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.Edit -{ - /// - /// A mask placed above a adding editing functionality. - /// - public class HitObjectMask : CompositeDrawable, IStateful - { - /// - /// Invoked when this has been selected. - /// - public event Action Selected; - - /// - /// Invoked when this has been deselected. - /// - public event Action Deselected; - - /// - /// Invoked when this has requested selection. - /// Will fire even if already selected. Does not actually perform selection. - /// - public event Action SelectionRequested; - - /// - /// Invoked when this has requested drag. - /// - public event Action DragRequested; - - /// - /// The which this applies to. - /// - public readonly DrawableHitObject HitObject; - - protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == SelectionState.Selected; - public override bool HandleMouseInput => ShouldBeAlive; - public override bool RemoveWhenNotAlive => false; - - public HitObjectMask(DrawableHitObject hitObject) - { - HitObject = hitObject; - - AlwaysPresent = true; - Alpha = 0; - } - - private SelectionState state; - - public event Action StateChanged; - - public SelectionState State - { - get => state; - set - { - if (state == value) return; - - state = value; - switch (state) - { - case SelectionState.Selected: - Show(); - Selected?.Invoke(this); - break; - case SelectionState.NotSelected: - Hide(); - Deselected?.Invoke(this); - break; - } - } - } - - /// - /// Selects this , causing it to become visible. - /// - public void Select() => State = SelectionState.Selected; - - /// - /// Deselects this , causing it to become invisible. - /// - public void Deselect() => State = SelectionState.NotSelected; - - public bool IsSelected => State == SelectionState.Selected; - - private bool selectionRequested; - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - selectionRequested = false; - - if (State == SelectionState.NotSelected) - { - SelectionRequested?.Invoke(this, state); - selectionRequested = true; - } - - return IsSelected; - } - - protected override bool OnClick(InputState state) - { - if (State == SelectionState.Selected && !selectionRequested) - { - selectionRequested = true; - SelectionRequested?.Invoke(this, state); - return true; - } - - return base.OnClick(state); - } - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - DragRequested?.Invoke(this, state); - return true; - } - - /// - /// The screen-space point that causes this to be selected. - /// - public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; - - /// - /// The screen-space quad that outlines this for selections. - /// - public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; - } - - public enum SelectionState - { - NotSelected, - Selected - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// A mask placed above a adding editing functionality. + /// + public class HitObjectMask : CompositeDrawable, IStateful + { + /// + /// Invoked when this has been selected. + /// + public event Action Selected; + + /// + /// Invoked when this has been deselected. + /// + public event Action Deselected; + + /// + /// Invoked when this has requested selection. + /// Will fire even if already selected. Does not actually perform selection. + /// + public event Action SelectionRequested; + + /// + /// Invoked when this has requested drag. + /// + public event Action DragRequested; + + /// + /// The which this applies to. + /// + public readonly DrawableHitObject HitObject; + + protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == SelectionState.Selected; + public override bool HandleMouseInput => ShouldBeAlive; + public override bool RemoveWhenNotAlive => false; + + public HitObjectMask(DrawableHitObject hitObject) + { + HitObject = hitObject; + + AlwaysPresent = true; + Alpha = 0; + } + + private SelectionState state; + + public event Action StateChanged; + + public SelectionState State + { + get => state; + set + { + if (state == value) return; + + state = value; + switch (state) + { + case SelectionState.Selected: + Show(); + Selected?.Invoke(this); + break; + case SelectionState.NotSelected: + Hide(); + Deselected?.Invoke(this); + break; + } + } + } + + /// + /// Selects this , causing it to become visible. + /// + public void Select() => State = SelectionState.Selected; + + /// + /// Deselects this , causing it to become invisible. + /// + public void Deselect() => State = SelectionState.NotSelected; + + public bool IsSelected => State == SelectionState.Selected; + + private bool selectionRequested; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + selectionRequested = false; + + if (State == SelectionState.NotSelected) + { + SelectionRequested?.Invoke(this, state); + selectionRequested = true; + } + + return IsSelected; + } + + protected override bool OnClick(InputState state) + { + if (State == SelectionState.Selected && !selectionRequested) + { + selectionRequested = true; + SelectionRequested?.Invoke(this, state); + return true; + } + + return base.OnClick(state); + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + DragRequested?.Invoke(this, state); + return true; + } + + /// + /// The screen-space point that causes this to be selected. + /// + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; + + /// + /// The screen-space quad that outlines this for selections. + /// + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; + } + + public enum SelectionState + { + NotSelected, + Selected + } +} diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs index e153607f47..4de0c518e6 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Screens.Play.PlayerSettings; - -namespace osu.Game.Rulesets.Edit -{ - public class ToolboxGroup : PlayerSettingsGroup - { - protected override string Title => "toolbox"; - - public ToolboxGroup() - { - RelativeSizeAxes = Axes.X; - Width = 1; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Screens.Play.PlayerSettings; + +namespace osu.Game.Rulesets.Edit +{ + public class ToolboxGroup : PlayerSettingsGroup + { + protected override string Title => "toolbox"; + + public ToolboxGroup() + { + RelativeSizeAxes = Axes.X; + Width = 1; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs index a548795df3..2c3720fc8f 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Edit.Tools -{ - public class HitObjectCompositionTool : ICompositionTool - where T : HitObject - { - public string Name => typeof(T).Name; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Tools +{ + public class HitObjectCompositionTool : ICompositionTool + where T : HitObject + { + public string Name => typeof(T).Name; + } +} diff --git a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs index db5fecf525..ce8b139b43 100644 --- a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Edit.Tools -{ - public interface ICompositionTool - { - string Name { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Edit.Tools +{ + public interface ICompositionTool + { + string Name { get; } + } +} diff --git a/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs b/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs index fa101ed835..7107b6c763 100644 --- a/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs +++ b/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Edit.Types -{ - public interface IHasEditablePosition : IHasPosition - { - void OffsetPosition(Vector2 offset); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Edit.Types +{ + public interface IHasEditablePosition : IHasPosition + { + void OffsetPosition(Vector2 offset); + } +} diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index a1a27c0d43..74ee025823 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -1,111 +1,111 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Judgements -{ - /// - /// A drawable object which visualises the hit result of a . - /// - public class DrawableJudgement : Container - { - private const float judgement_size = 80; - - private OsuColour colours; - - protected readonly Judgement Judgement; - - public readonly DrawableHitObject JudgedObject; - - protected SpriteText JudgementText; - - /// - /// Creates a drawable which visualises a . - /// - /// The judgement to visualise. - /// The object which was judged. - public DrawableJudgement(Judgement judgement, DrawableHitObject judgedObject) - { - Judgement = judgement; - JudgedObject = judgedObject; - - Size = new Vector2(judgement_size); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - this.colours = colours; - - Child = new SkinnableDrawable($"Play/{Judgement.Result}", _ => JudgementText = new OsuSpriteText - { - Text = Judgement.Result.GetDescription().ToUpper(), - Font = @"Venera", - Colour = judgementColour(Judgement.Result), - Scale = new Vector2(0.85f, 1), - TextSize = 12 - }, restrictSize: false); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - this.FadeInFromZero(100, Easing.OutQuint); - - switch (Judgement.Result) - { - case HitResult.None: - break; - case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); - - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - this.RotateTo(40, 800, Easing.InQuint); - - this.Delay(600).FadeOut(200); - break; - default: - this.ScaleTo(0.9f); - this.ScaleTo(1, 500, Easing.OutElastic); - - this.Delay(100).FadeOut(400); - break; - } - - Expire(true); - } - - private Color4 judgementColour(HitResult judgement) - { - switch (judgement) - { - case HitResult.Perfect: - case HitResult.Great: - return colours.Blue; - case HitResult.Ok: - case HitResult.Good: - return colours.Green; - case HitResult.Meh: - return colours.Yellow; - case HitResult.Miss: - return colours.Red; - } - - return Color4.White; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Judgements +{ + /// + /// A drawable object which visualises the hit result of a . + /// + public class DrawableJudgement : Container + { + private const float judgement_size = 80; + + private OsuColour colours; + + protected readonly Judgement Judgement; + + public readonly DrawableHitObject JudgedObject; + + protected SpriteText JudgementText; + + /// + /// Creates a drawable which visualises a . + /// + /// The judgement to visualise. + /// The object which was judged. + public DrawableJudgement(Judgement judgement, DrawableHitObject judgedObject) + { + Judgement = judgement; + JudgedObject = judgedObject; + + Size = new Vector2(judgement_size); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + + Child = new SkinnableDrawable($"Play/{Judgement.Result}", _ => JudgementText = new OsuSpriteText + { + Text = Judgement.Result.GetDescription().ToUpper(), + Font = @"Venera", + Colour = judgementColour(Judgement.Result), + Scale = new Vector2(0.85f, 1), + TextSize = 12 + }, restrictSize: false); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeInFromZero(100, Easing.OutQuint); + + switch (Judgement.Result) + { + case HitResult.None: + break; + case HitResult.Miss: + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); + + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + this.RotateTo(40, 800, Easing.InQuint); + + this.Delay(600).FadeOut(200); + break; + default: + this.ScaleTo(0.9f); + this.ScaleTo(1, 500, Easing.OutElastic); + + this.Delay(100).FadeOut(400); + break; + } + + Expire(true); + } + + private Color4 judgementColour(HitResult judgement) + { + switch (judgement) + { + case HitResult.Perfect: + case HitResult.Great: + return colours.Blue; + case HitResult.Ok: + case HitResult.Good: + return colours.Green; + case HitResult.Meh: + return colours.Yellow; + case HitResult.Miss: + return colours.Red; + } + + return Color4.White; + } + } +} diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index c2216fea51..587f2c8d15 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -1,70 +1,70 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Judgements -{ - public class Judgement - { - /// - /// Whether this judgement is the result of a hit or a miss. - /// - public HitResult Result; - - /// - /// The maximum that can be achieved. - /// - public virtual HitResult MaxResult => HitResult.Perfect; - - /// - /// The combo prior to this judgement occurring. - /// - public int ComboAtJudgement; - - /// - /// The highest combo achieved prior to this judgement occurring. - /// - public int HighestComboAtJudgement; - - /// - /// Whether a successful hit occurred. - /// - public bool IsHit => Result > HitResult.Miss; - - /// - /// Whether this judgement is the final judgement for the hit object. - /// - public bool Final = true; - - /// - /// The offset from a perfect hit at which this judgement occurred. - /// Populated when added via . - /// - public double TimeOffset { get; set; } - - /// - /// Whether the should affect the combo portion of the score. - /// If false, the will be considered for the bonus portion of the score. - /// - public virtual bool AffectsCombo => true; - - /// - /// The numeric representation for the result achieved. - /// - public int NumericResult => NumericResultFor(Result); - - /// - /// The numeric representation for the maximum achievable result. - /// - public int MaxNumericResult => NumericResultFor(MaxResult); - - /// - /// Convert a to a numeric score representation. - /// - /// The value to convert. - /// The number. - protected virtual int NumericResultFor(HitResult result) => result > HitResult.Miss ? 1 : 0; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Judgements +{ + public class Judgement + { + /// + /// Whether this judgement is the result of a hit or a miss. + /// + public HitResult Result; + + /// + /// The maximum that can be achieved. + /// + public virtual HitResult MaxResult => HitResult.Perfect; + + /// + /// The combo prior to this judgement occurring. + /// + public int ComboAtJudgement; + + /// + /// The highest combo achieved prior to this judgement occurring. + /// + public int HighestComboAtJudgement; + + /// + /// Whether a successful hit occurred. + /// + public bool IsHit => Result > HitResult.Miss; + + /// + /// Whether this judgement is the final judgement for the hit object. + /// + public bool Final = true; + + /// + /// The offset from a perfect hit at which this judgement occurred. + /// Populated when added via . + /// + public double TimeOffset { get; set; } + + /// + /// Whether the should affect the combo portion of the score. + /// If false, the will be considered for the bonus portion of the score. + /// + public virtual bool AffectsCombo => true; + + /// + /// The numeric representation for the result achieved. + /// + public int NumericResult => NumericResultFor(Result); + + /// + /// The numeric representation for the maximum achievable result. + /// + public int MaxNumericResult => NumericResultFor(MaxResult); + + /// + /// Convert a to a numeric score representation. + /// + /// The value to convert. + /// The number. + protected virtual int NumericResultFor(HitResult result) => result > HitResult.Miss ? 1 : 0; + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index 81268e0ad4..6a4042a906 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mods -{ - /// - /// Represents a mod which can override (and block) a fail. - /// - public interface IApplicableFailOverride : IApplicableMod - { - /// - /// Whether we should allow failing at the current point in time. - /// - bool AllowFail { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Represents a mod which can override (and block) a fail. + /// + public interface IApplicableFailOverride : IApplicableMod + { + /// + /// Whether we should allow failing at the current point in time. + /// + bool AllowFail { get; } + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableMod.cs b/osu.Game/Rulesets/Mods/IApplicableMod.cs index 8e56a90d47..30046c3dc2 100644 --- a/osu.Game/Rulesets/Mods/IApplicableMod.cs +++ b/osu.Game/Rulesets/Mods/IApplicableMod.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mods -{ - /// - /// The base interface for a mod which can be applied in some way. - /// If this is not implemented by a mod, it will not be available for use in-game. - /// - public interface IApplicableMod - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + /// + /// The base interface for a mod which can be applied in some way. + /// If this is not implemented by a mod, it will not be available for use in-game. + /// + public interface IApplicableMod + { + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index d89d6f20d8..a03a003810 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// Interface for a that applies changes to a . - /// - /// The type of converted . - public interface IApplicableToBeatmapConverter : IApplicableMod - where TObject : HitObject - { - /// - /// Applies this to a . - /// - /// The to apply to. - void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that applies changes to a . + /// + /// The type of converted . + public interface IApplicableToBeatmapConverter : IApplicableMod + where TObject : HitObject + { + /// + /// Applies this to a . + /// + /// The to apply to. + void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToClock.cs b/osu.Game/Rulesets/Mods/IApplicableToClock.cs index 46c1648951..a9570d80c2 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToClock.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToClock.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Timing; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for mods that make adjustments to the track. - /// - public interface IApplicableToClock : IApplicableMod - { - void ApplyToClock(IAdjustableClock clock); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Timing; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make adjustments to the track. + /// + public interface IApplicableToClock : IApplicableMod + { + void ApplyToClock(IAdjustableClock clock); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs index a87ae701c1..3f32253544 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for mods that make general adjustments to difficulty. - /// - public interface IApplicableToDifficulty : IApplicableMod - { - void ApplyToDifficulty(BeatmapDifficulty difficulty); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make general adjustments to difficulty. + /// + public interface IApplicableToDifficulty : IApplicableMod + { + void ApplyToDifficulty(BeatmapDifficulty difficulty); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index cd39f90ac4..d871cdd322 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for s that can be applied to s. - /// - public interface IApplicableToDrawableHitObjects : IApplicableMod - { - /// - /// Applies this to a list of s. - /// - /// The list of s to apply to. - void ApplyToDrawableHitObjects(IEnumerable drawables); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for s that can be applied to s. + /// + public interface IApplicableToDrawableHitObjects : IApplicableMod + { + /// + /// Applies this to a list of s. + /// + /// The list of s to apply to. + void ApplyToDrawableHitObjects(IEnumerable drawables); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index c20abe0151..0fd2e398c8 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -1,20 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for s that can be applied to s. - /// - public interface IApplicableToHitObject : IApplicableMod - where TObject : HitObject - { - /// - /// Applies this to a . - /// - /// The to apply to. - void ApplyToHitObject(TObject hitObject); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for s that can be applied to s. + /// + public interface IApplicableToHitObject : IApplicableMod + where TObject : HitObject + { + /// + /// Applies this to a . + /// + /// The to apply to. + void ApplyToHitObject(TObject hitObject); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs b/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs index f03866e7bb..a3c5ab3df4 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for s that can be applied to s. - /// - public interface IApplicableToRulesetContainer : IApplicableMod - where TObject : HitObject - { - /// - /// Applies this to a . - /// - /// The to apply to. - void ApplyToRulesetContainer(RulesetContainer rulesetContainer); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for s that can be applied to s. + /// + public interface IApplicableToRulesetContainer : IApplicableMod + where TObject : HitObject + { + /// + /// Applies this to a . + /// + /// The to apply to. + void ApplyToRulesetContainer(RulesetContainer rulesetContainer); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index f45bbab194..772eac478c 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for mods that make general adjustments to score processor. - /// - public interface IApplicableToScoreProcessor : IApplicableMod - { - void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make general adjustments to score processor. + /// + public interface IApplicableToScoreProcessor : IApplicableMod + { + void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); + } +} diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 26cdcde9fe..9fb554b5c5 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -1,59 +1,59 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; -using System; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// The base class for gameplay modifiers. - /// - public abstract class Mod - { - /// - /// The name of this mod. - /// - public abstract string Name { get; } - - /// - /// The shortened name of this mod. - /// - public abstract string ShortenedName { get; } - - /// - /// The icon of this mod. - /// - public virtual FontAwesome Icon => FontAwesome.fa_question; - - /// - /// The type of this mod. - /// - public virtual ModType Type => ModType.Special; - - /// - /// The user readable description of this mod. - /// - public virtual string Description => string.Empty; - - /// - /// The score multiplier of this mod. - /// - public abstract double ScoreMultiplier { get; } - - /// - /// Returns true if this mod is implemented (and playable). - /// - public virtual bool HasImplementation => this is IApplicableMod; - - /// - /// Returns if this mod is ranked. - /// - public virtual bool Ranked => false; - - /// - /// The mods this mod cannot be enabled with. - /// - public virtual Type[] IncompatibleMods => new Type[] { }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using System; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// The base class for gameplay modifiers. + /// + public abstract class Mod + { + /// + /// The name of this mod. + /// + public abstract string Name { get; } + + /// + /// The shortened name of this mod. + /// + public abstract string ShortenedName { get; } + + /// + /// The icon of this mod. + /// + public virtual FontAwesome Icon => FontAwesome.fa_question; + + /// + /// The type of this mod. + /// + public virtual ModType Type => ModType.Special; + + /// + /// The user readable description of this mod. + /// + public virtual string Description => string.Empty; + + /// + /// The score multiplier of this mod. + /// + public abstract double ScoreMultiplier { get; } + + /// + /// Returns true if this mod is implemented (and playable). + /// + public virtual bool HasImplementation => this is IApplicableMod; + + /// + /// Returns if this mod is ranked. + /// + public virtual bool Ranked => false; + + /// + /// The mods this mod cannot be enabled with. + /// + public virtual Type[] IncompatibleMods => new Type[] { }; + } +} diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 9f45cada7e..75f37b993d 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mods -{ - public class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer - where T : HitObject - { - protected virtual Score CreateReplayScore(Beatmap beatmap) => new Score { Replay = new Replay() }; - - public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; - - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); - } - - public abstract class ModAutoplay : Mod, IApplicableFailOverride - { - public override string Name => "Autoplay"; - public override string ShortenedName => "AT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; - public override string Description => "Watch a perfect automated play through the song."; - public override double ScoreMultiplier => 0; - public bool AllowFail => false; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + public class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + where T : HitObject + { + protected virtual Score CreateReplayScore(Beatmap beatmap) => new Score { Replay = new Replay() }; + + public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; + + public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); + } + + public abstract class ModAutoplay : Mod, IApplicableFailOverride + { + public override string Name => "Autoplay"; + public override string ShortenedName => "AT"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; + public override string Description => "Watch a perfect automated play through the song."; + public override double ScoreMultiplier => 0; + public bool AllowFail => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; + } +} diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 015f7381fb..421e3a54e4 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public class ModCinema : ModAutoplay - { - public override string Name => "Cinema"; - public override string ShortenedName => "CN"; - public override bool HasImplementation => false; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; - public override string Description => "Watch the video without visual distractions."; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public class ModCinema : ModAutoplay + { + public override string Name => "Cinema"; + public override string ShortenedName => "CN"; + public override bool HasImplementation => false; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; + public override string Description => "Watch the video without visual distractions."; + } +} diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index da4263875b..f11d0c76ed 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Audio; -using osu.Framework.Timing; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModDaycore : ModHalfTime - { - public override string Name => "Daycore"; - public override string ShortenedName => "DC"; - public override FontAwesome Icon => FontAwesome.fa_question; - public override string Description => "Whoaaaaa..."; - - public override void ApplyToClock(IAdjustableClock clock) - { - var pitchAdjust = clock as IHasPitchAdjust; - if (pitchAdjust != null) - pitchAdjust.PitchAdjust = 0.75; - else - base.ApplyToClock(clock); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Audio; +using osu.Framework.Timing; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModDaycore : ModHalfTime + { + public override string Name => "Daycore"; + public override string ShortenedName => "DC"; + public override FontAwesome Icon => FontAwesome.fa_question; + public override string Description => "Whoaaaaa..."; + + public override void ApplyToClock(IAdjustableClock clock) + { + var pitchAdjust = clock as IHasPitchAdjust; + if (pitchAdjust != null) + pitchAdjust.PitchAdjust = 0.75; + else + base.ApplyToClock(clock); + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 6225a6feee..0d44495fce 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Timing; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModDoubleTime : Mod, IApplicableToClock - { - public override string Name => "Double Time"; - public override string ShortenedName => "DT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; - public override ModType Type => ModType.DifficultyIncrease; - public override string Description => "Zoooooooooom..."; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; - - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 1.5; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Timing; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModDoubleTime : Mod, IApplicableToClock + { + public override string Name => "Double Time"; + public override string ShortenedName => "DT"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => "Zoooooooooom..."; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; + + public virtual void ApplyToClock(IAdjustableClock clock) + { + clock.Rate = 1.5; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 7037edfa31..93abb82e5b 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModEasy : Mod, IApplicableToDifficulty - { - public override string Name => "Easy"; - public override string ShortenedName => "EZ"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; - public override ModType Type => ModType.DifficultyReduction; - public override double ScoreMultiplier => 0.5; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; - - public void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - const float ratio = 0.5f; - difficulty.CircleSize *= ratio; - difficulty.ApproachRate *= ratio; - difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModEasy : Mod, IApplicableToDifficulty + { + public override string Name => "Easy"; + public override string ShortenedName => "EZ"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; + public override ModType Type => ModType.DifficultyReduction; + public override double ScoreMultiplier => 0.5; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 0.5f; + difficulty.CircleSize *= ratio; + difficulty.ApproachRate *= ratio; + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 9858e00083..223263195c 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModFlashlight : Mod - { - public override string Name => "Flashlight"; - public override string ShortenedName => "FL"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; - public override ModType Type => ModType.DifficultyIncrease; - public override string Description => "Restricted view area."; - public override bool Ranked => true; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModFlashlight : Mod + { + public override string Name => "Flashlight"; + public override string ShortenedName => "FL"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => "Restricted view area."; + public override bool Ranked => true; + } +} diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 883225a66b..3ec4fb9f9f 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Timing; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModHalfTime : Mod, IApplicableToClock - { - public override string Name => "Half Time"; - public override string ShortenedName => "HT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; - public override ModType Type => ModType.DifficultyReduction; - public override string Description => "Less zoom..."; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; - - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 0.75; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Timing; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModHalfTime : Mod, IApplicableToClock + { + public override string Name => "Half Time"; + public override string ShortenedName => "HT"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; + public override ModType Type => ModType.DifficultyReduction; + public override string Description => "Less zoom..."; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; + + public virtual void ApplyToClock(IAdjustableClock clock) + { + clock.Rate = 0.75; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index c4c0f38faf..090bc737dd 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModHardRock : Mod, IApplicableToDifficulty - { - public override string Name => "Hard Rock"; - public override string ShortenedName => "HR"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; - public override ModType Type => ModType.DifficultyIncrease; - public override string Description => "Everything just got a bit harder..."; - public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; - - public void ApplyToDifficulty(BeatmapDifficulty difficulty) - { - const float ratio = 1.4f; - difficulty.CircleSize *= 1.3f; // CS uses a custom 1.3 ratio. - difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ratio, 10.0f); - difficulty.DrainRate *= ratio; - difficulty.OverallDifficulty *= ratio; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModHardRock : Mod, IApplicableToDifficulty + { + public override string Name => "Hard Rock"; + public override string ShortenedName => "HR"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => "Everything just got a bit harder..."; + public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 1.4f; + difficulty.CircleSize *= 1.3f; // CS uses a custom 1.3 ratio. + difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ratio, 10.0f); + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 303a46f629..b489a665d9 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModHidden : Mod - { - public override string Name => "Hidden"; - public override string ShortenedName => "HD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; - public override ModType Type => ModType.DifficultyIncrease; - public override bool Ranked => true; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModHidden : Mod + { + public override string Name => "Hidden"; + public override string ShortenedName => "HD"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override ModType Type => ModType.DifficultyIncrease; + public override bool Ranked => true; + } +} diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index c2925f440f..162d654965 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Audio; -using osu.Framework.Timing; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModNightcore : ModDoubleTime - { - public override string Name => "Nightcore"; - public override string ShortenedName => "NC"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; - public override string Description => "Uguuuuuuuu..."; - - public override void ApplyToClock(IAdjustableClock clock) - { - var pitchAdjust = clock as IHasPitchAdjust; - if (pitchAdjust != null) - pitchAdjust.PitchAdjust = 1.5; - else - base.ApplyToClock(clock); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Audio; +using osu.Framework.Timing; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModNightcore : ModDoubleTime + { + public override string Name => "Nightcore"; + public override string ShortenedName => "NC"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; + public override string Description => "Uguuuuuuuu..."; + + public override void ApplyToClock(IAdjustableClock clock) + { + var pitchAdjust = clock as IHasPitchAdjust; + if (pitchAdjust != null) + pitchAdjust.PitchAdjust = 1.5; + else + base.ApplyToClock(clock); + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 8a849825a2..7510f62432 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModNoFail : Mod, IApplicableFailOverride - { - public override string Name => "No Fail"; - public override string ShortenedName => "NF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; - public override ModType Type => ModType.DifficultyReduction; - public override string Description => "You can't fail, no matter what."; - public override double ScoreMultiplier => 0.5; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; - - /// - /// We never fail, 'yo. - /// - public bool AllowFail => false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModNoFail : Mod, IApplicableFailOverride + { + public override string Name => "No Fail"; + public override string ShortenedName => "NF"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override ModType Type => ModType.DifficultyReduction; + public override string Description => "You can't fail, no matter what."; + public override double ScoreMultiplier => 0.5; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; + + /// + /// We never fail, 'yo. + /// + public bool AllowFail => false; + } +} diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 116d13bf0a..802890866f 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModPerfect : ModSuddenDeath - { - public override string Name => "Perfect"; - public override string ShortenedName => "PF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; - public override string Description => "SS or quit."; - - protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModPerfect : ModSuddenDeath + { + public override string Name => "Perfect"; + public override string ShortenedName => "PF"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; + public override string Description => "SS or quit."; + + protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; + } +} diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index e8328c3ac7..e4137254e6 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModRelax : Mod - { - public override string Name => "Relax"; - public override string ShortenedName => "RX"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; - public override double ScoreMultiplier => 0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModRelax : Mod + { + public override string Name => "Relax"; + public override string ShortenedName => "RX"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; + public override double ScoreMultiplier => 0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; + } +} diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index ef9ff4c69e..48f7d496a5 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor - { - public override string Name => "Sudden Death"; - public override string ShortenedName => "SD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; - public override ModType Type => ModType.DifficultyIncrease; - public override string Description => "Miss and fail."; - public override double ScoreMultiplier => 1; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) - { - scoreProcessor.FailConditions += FailCondition; - } - - protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor + { + public override string Name => "Sudden Death"; + public override string ShortenedName => "SD"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; + public override ModType Type => ModType.DifficultyIncrease; + public override string Description => "Miss and fail."; + public override double ScoreMultiplier => 1; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + scoreProcessor.FailConditions += FailCondition; + } + + protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; + } +} diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index 8aeb880be8..1941724879 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mods -{ - public enum ModType - { - DifficultyReduction, - DifficultyIncrease, - Special, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + public enum ModType + { + DifficultyReduction, + DifficultyIncrease, + Special, + } +} diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index 5548313f8e..3c90a4eedb 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mods -{ - public class MultiMod : Mod - { - public override string Name => string.Empty; - public override string ShortenedName => string.Empty; - public override string Description => string.Empty; - public override double ScoreMultiplier => 0; - - public Mod[] Mods; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + public class MultiMod : Mod + { + public override string Name => string.Empty; + public override string ShortenedName => string.Empty; + public override string Description => string.Empty; + public override double ScoreMultiplier => 0; + + public Mod[] Mods; + } +} diff --git a/osu.Game/Rulesets/Objects/BezierApproximator.cs b/osu.Game/Rulesets/Objects/BezierApproximator.cs index 6ed83a5f57..6094934510 100644 --- a/osu.Game/Rulesets/Objects/BezierApproximator.cs +++ b/osu.Game/Rulesets/Objects/BezierApproximator.cs @@ -1,150 +1,150 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public class BezierApproximator - { - private readonly int count; - private readonly List controlPoints; - private readonly Vector2[] subdivisionBuffer1; - private readonly Vector2[] subdivisionBuffer2; - - private const float tolerance = 0.25f; - private const float tolerance_sq = tolerance * tolerance; - - public BezierApproximator(List controlPoints) - { - this.controlPoints = controlPoints; - count = controlPoints.Count; - - subdivisionBuffer1 = new Vector2[count]; - subdivisionBuffer2 = new Vector2[count * 2 - 1]; - } - - /// - /// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds. - /// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function - /// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts - /// need to have a denser approximation to be more "flat". - /// - /// The control points to check for flatness. - /// Whether the control points are flat enough. - private static bool isFlatEnough(Vector2[] controlPoints) - { - for (int i = 1; i < controlPoints.Length - 1; i++) - if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > tolerance_sq * 4) - return false; - - return true; - } - - /// - /// Subdivides n control points representing a bezier curve into 2 sets of n control points, each - /// describing a bezier curve equivalent to a half of the original curve. Effectively this splits - /// the original curve into 2 curves which result in the original curve when pieced back together. - /// - /// The control points to split. - /// Output: The control points corresponding to the left half of the curve. - /// Output: The control points corresponding to the right half of the curve. - private void subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r) - { - Vector2[] midpoints = subdivisionBuffer1; - - for (int i = 0; i < count; ++i) - midpoints[i] = controlPoints[i]; - - for (int i = 0; i < count; i++) - { - l[i] = midpoints[0]; - r[count - i - 1] = midpoints[count - i - 1]; - - for (int j = 0; j < count - i - 1; j++) - midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2; - } - } - - /// - /// This uses De Casteljau's algorithm to obtain an optimal - /// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points. - /// - /// The control points describing the bezier curve to be approximated. - /// The points representing the resulting piecewise-linear approximation. - private void approximate(Vector2[] controlPoints, List output) - { - Vector2[] l = subdivisionBuffer2; - Vector2[] r = subdivisionBuffer1; - - subdivide(controlPoints, l, r); - - for (int i = 0; i < count - 1; ++i) - l[count + i] = r[i + 1]; - - output.Add(controlPoints[0]); - for (int i = 1; i < count - 1; ++i) - { - int index = 2 * i; - Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]); - output.Add(p); - } - } - - /// - /// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing - /// the control points until their approximation error vanishes below a given threshold. - /// - /// A list of vectors representing the piecewise-linear approximation. - public List CreateBezier() - { - List output = new List(); - - if (count == 0) - return output; - - Stack toFlatten = new Stack(); - Stack freeBuffers = new Stack(); - - // "toFlatten" contains all the curves which are not yet approximated well enough. - // We use a stack to emulate recursion without the risk of running into a stack overflow. - // (More specifically, we iteratively and adaptively refine our curve with a - // Depth-first search - // over the tree resulting from the subdivisions we make.) - toFlatten.Push(controlPoints.ToArray()); - - Vector2[] leftChild = subdivisionBuffer2; - - while (toFlatten.Count > 0) - { - Vector2[] parent = toFlatten.Pop(); - if (isFlatEnough(parent)) - { - // If the control points we currently operate on are sufficiently "flat", we use - // an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation - // of the bezier curve represented by our control points, consisting of the same amount - // of points as there are control points. - approximate(parent, output); - freeBuffers.Push(parent); - continue; - } - - // If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep - // subdividing the curve we are currently operating on. - Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count]; - subdivide(parent, leftChild, rightChild); - - // We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration. - for (int i = 0; i < count; ++i) - parent[i] = leftChild[i]; - - toFlatten.Push(rightChild); - toFlatten.Push(parent); - } - - output.Add(controlPoints[count - 1]); - return output; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; + +namespace osu.Game.Rulesets.Objects +{ + public class BezierApproximator + { + private readonly int count; + private readonly List controlPoints; + private readonly Vector2[] subdivisionBuffer1; + private readonly Vector2[] subdivisionBuffer2; + + private const float tolerance = 0.25f; + private const float tolerance_sq = tolerance * tolerance; + + public BezierApproximator(List controlPoints) + { + this.controlPoints = controlPoints; + count = controlPoints.Count; + + subdivisionBuffer1 = new Vector2[count]; + subdivisionBuffer2 = new Vector2[count * 2 - 1]; + } + + /// + /// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds. + /// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function + /// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts + /// need to have a denser approximation to be more "flat". + /// + /// The control points to check for flatness. + /// Whether the control points are flat enough. + private static bool isFlatEnough(Vector2[] controlPoints) + { + for (int i = 1; i < controlPoints.Length - 1; i++) + if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > tolerance_sq * 4) + return false; + + return true; + } + + /// + /// Subdivides n control points representing a bezier curve into 2 sets of n control points, each + /// describing a bezier curve equivalent to a half of the original curve. Effectively this splits + /// the original curve into 2 curves which result in the original curve when pieced back together. + /// + /// The control points to split. + /// Output: The control points corresponding to the left half of the curve. + /// Output: The control points corresponding to the right half of the curve. + private void subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r) + { + Vector2[] midpoints = subdivisionBuffer1; + + for (int i = 0; i < count; ++i) + midpoints[i] = controlPoints[i]; + + for (int i = 0; i < count; i++) + { + l[i] = midpoints[0]; + r[count - i - 1] = midpoints[count - i - 1]; + + for (int j = 0; j < count - i - 1; j++) + midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2; + } + } + + /// + /// This uses De Casteljau's algorithm to obtain an optimal + /// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points. + /// + /// The control points describing the bezier curve to be approximated. + /// The points representing the resulting piecewise-linear approximation. + private void approximate(Vector2[] controlPoints, List output) + { + Vector2[] l = subdivisionBuffer2; + Vector2[] r = subdivisionBuffer1; + + subdivide(controlPoints, l, r); + + for (int i = 0; i < count - 1; ++i) + l[count + i] = r[i + 1]; + + output.Add(controlPoints[0]); + for (int i = 1; i < count - 1; ++i) + { + int index = 2 * i; + Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]); + output.Add(p); + } + } + + /// + /// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing + /// the control points until their approximation error vanishes below a given threshold. + /// + /// A list of vectors representing the piecewise-linear approximation. + public List CreateBezier() + { + List output = new List(); + + if (count == 0) + return output; + + Stack toFlatten = new Stack(); + Stack freeBuffers = new Stack(); + + // "toFlatten" contains all the curves which are not yet approximated well enough. + // We use a stack to emulate recursion without the risk of running into a stack overflow. + // (More specifically, we iteratively and adaptively refine our curve with a + // Depth-first search + // over the tree resulting from the subdivisions we make.) + toFlatten.Push(controlPoints.ToArray()); + + Vector2[] leftChild = subdivisionBuffer2; + + while (toFlatten.Count > 0) + { + Vector2[] parent = toFlatten.Pop(); + if (isFlatEnough(parent)) + { + // If the control points we currently operate on are sufficiently "flat", we use + // an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation + // of the bezier curve represented by our control points, consisting of the same amount + // of points as there are control points. + approximate(parent, output); + freeBuffers.Push(parent); + continue; + } + + // If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep + // subdividing the curve we are currently operating on. + Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count]; + subdivide(parent, leftChild, rightChild); + + // We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration. + for (int i = 0; i < count; ++i) + parent[i] = leftChild[i]; + + toFlatten.Push(rightChild); + toFlatten.Push(parent); + } + + output.Add(controlPoints[count - 1]); + return output; + } + } +} diff --git a/osu.Game/Rulesets/Objects/CatmullApproximator.cs b/osu.Game/Rulesets/Objects/CatmullApproximator.cs index 364b4e995a..fabb55e8f6 100644 --- a/osu.Game/Rulesets/Objects/CatmullApproximator.cs +++ b/osu.Game/Rulesets/Objects/CatmullApproximator.cs @@ -1,70 +1,70 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public class CatmullApproximator - { - /// - /// The amount of pieces to calculate for each controlpoint quadruplet. - /// - private const int detail = 50; - - private readonly List controlPoints; - - public CatmullApproximator(List controlPoints) - { - this.controlPoints = controlPoints; - } - - - /// - /// Creates a piecewise-linear approximation of a Catmull-Rom spline. - /// - /// A list of vectors representing the piecewise-linear approximation. - public List CreateCatmull() - { - var result = new List(); - - for (int i = 0; i < controlPoints.Count - 1; i++) - { - var v1 = i > 0 ? controlPoints[i - 1] : controlPoints[i]; - var v2 = controlPoints[i]; - var v3 = i < controlPoints.Count - 1 ? controlPoints[i + 1] : v2 + v2 - v1; - var v4 = i < controlPoints.Count - 2 ? controlPoints[i + 2] : v3 + v3 - v2; - - for (int c = 0; c < detail; c++) - { - result.Add(findPoint(ref v1, ref v2, ref v3, ref v4, (float)c / detail)); - result.Add(findPoint(ref v1, ref v2, ref v3, ref v4, (float)(c + 1) / detail)); - } - } - - return result; - } - - /// - /// Finds a point on the spline at the position of a parameter. - /// - /// The first vector. - /// The second vector. - /// The third vector. - /// The fourth vector. - /// The parameter at which to find the point on the spline, in the range [0, 1]. - /// The point on the spline at . - private Vector2 findPoint(ref Vector2 vec1, ref Vector2 vec2, ref Vector2 vec3, ref Vector2 vec4, float t) - { - float t2 = t * t; - float t3 = t * t2; - - Vector2 result; - result.X = 0.5f * (2f * vec2.X + (-vec1.X + vec3.X) * t + (2f * vec1.X - 5f * vec2.X + 4f * vec3.X - vec4.X) * t2 + (-vec1.X + 3f * vec2.X - 3f * vec3.X + vec4.X) * t3); - result.Y = 0.5f * (2f * vec2.Y + (-vec1.Y + vec3.Y) * t + (2f * vec1.Y - 5f * vec2.Y + 4f * vec3.Y - vec4.Y) * t2 + (-vec1.Y + 3f * vec2.Y - 3f * vec3.Y + vec4.Y) * t3); - - return result; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; + +namespace osu.Game.Rulesets.Objects +{ + public class CatmullApproximator + { + /// + /// The amount of pieces to calculate for each controlpoint quadruplet. + /// + private const int detail = 50; + + private readonly List controlPoints; + + public CatmullApproximator(List controlPoints) + { + this.controlPoints = controlPoints; + } + + + /// + /// Creates a piecewise-linear approximation of a Catmull-Rom spline. + /// + /// A list of vectors representing the piecewise-linear approximation. + public List CreateCatmull() + { + var result = new List(); + + for (int i = 0; i < controlPoints.Count - 1; i++) + { + var v1 = i > 0 ? controlPoints[i - 1] : controlPoints[i]; + var v2 = controlPoints[i]; + var v3 = i < controlPoints.Count - 1 ? controlPoints[i + 1] : v2 + v2 - v1; + var v4 = i < controlPoints.Count - 2 ? controlPoints[i + 2] : v3 + v3 - v2; + + for (int c = 0; c < detail; c++) + { + result.Add(findPoint(ref v1, ref v2, ref v3, ref v4, (float)c / detail)); + result.Add(findPoint(ref v1, ref v2, ref v3, ref v4, (float)(c + 1) / detail)); + } + } + + return result; + } + + /// + /// Finds a point on the spline at the position of a parameter. + /// + /// The first vector. + /// The second vector. + /// The third vector. + /// The fourth vector. + /// The parameter at which to find the point on the spline, in the range [0, 1]. + /// The point on the spline at . + private Vector2 findPoint(ref Vector2 vec1, ref Vector2 vec2, ref Vector2 vec3, ref Vector2 vec4, float t) + { + float t2 = t * t; + float t3 = t * t2; + + Vector2 result; + result.X = 0.5f * (2f * vec2.X + (-vec1.X + vec3.X) * t + (2f * vec1.X - 5f * vec2.X + 4f * vec3.X - vec4.X) * t2 + (-vec1.X + 3f * vec2.X - 3f * vec3.X + vec4.X) * t3); + result.Y = 0.5f * (2f * vec2.Y + (-vec1.Y + vec3.Y) * t + (2f * vec1.Y - 5f * vec2.Y + 4f * vec3.Y - vec4.Y) * t2 + (-vec1.Y + 3f * vec2.Y - 3f * vec3.Y + vec4.Y) * t3); + + return result; + } + } +} diff --git a/osu.Game/Rulesets/Objects/CircularArcApproximator.cs b/osu.Game/Rulesets/Objects/CircularArcApproximator.cs index cc319d1a8f..7fb8d8a40e 100644 --- a/osu.Game/Rulesets/Objects/CircularArcApproximator.cs +++ b/osu.Game/Rulesets/Objects/CircularArcApproximator.cs @@ -1,99 +1,99 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.MathUtils; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public class CircularArcApproximator - { - private readonly Vector2 a; - private readonly Vector2 b; - private readonly Vector2 c; - - private int amountPoints; - - private const float tolerance = 0.1f; - - public CircularArcApproximator(Vector2 a, Vector2 b, Vector2 c) - { - this.a = a; - this.b = b; - this.c = c; - } - - /// - /// Creates a piecewise-linear approximation of a circular arc curve. - /// - /// A list of vectors representing the piecewise-linear approximation. - public List CreateArc() - { - float aSq = (b - c).LengthSquared; - float bSq = (a - c).LengthSquared; - float cSq = (a - b).LengthSquared; - - // If we have a degenerate triangle where a side-length is almost zero, then give up and fall - // back to a more numerically stable method. - if (Precision.AlmostEquals(aSq, 0) || Precision.AlmostEquals(bSq, 0) || Precision.AlmostEquals(cSq, 0)) - return new List(); - - float s = aSq * (bSq + cSq - aSq); - float t = bSq * (aSq + cSq - bSq); - float u = cSq * (aSq + bSq - cSq); - - float sum = s + t + u; - - // If we have a degenerate triangle with an almost-zero size, then give up and fall - // back to a more numerically stable method. - if (Precision.AlmostEquals(sum, 0)) - return new List(); - - Vector2 centre = (s * a + t * b + u * c) / sum; - Vector2 dA = a - centre; - Vector2 dC = c - centre; - - float r = dA.Length; - - double thetaStart = Math.Atan2(dA.Y, dA.X); - double thetaEnd = Math.Atan2(dC.Y, dC.X); - - while (thetaEnd < thetaStart) - thetaEnd += 2 * Math.PI; - - double dir = 1; - double thetaRange = thetaEnd - thetaStart; - - // Decide in which direction to draw the circle, depending on which side of - // AC B lies. - Vector2 orthoAtoC = c - a; - orthoAtoC = new Vector2(orthoAtoC.Y, -orthoAtoC.X); - if (Vector2.Dot(orthoAtoC, b - a) < 0) - { - dir = -dir; - thetaRange = 2 * Math.PI - thetaRange; - } - - // We select the amount of points for the approximation by requiring the discrete curvature - // to be smaller than the provided tolerance. The exact angle required to meet the tolerance - // is: 2 * Math.Acos(1 - TOLERANCE / r) - // The special case is required for extremely short sliders where the radius is smaller than - // the tolerance. This is a pathological rather than a realistic case. - amountPoints = 2 * r <= tolerance ? 2 : Math.Max(2, (int)Math.Ceiling(thetaRange / (2 * Math.Acos(1 - tolerance / r)))); - - List output = new List(amountPoints); - - for (int i = 0; i < amountPoints; ++i) - { - double fract = (double)i / (amountPoints - 1); - double theta = thetaStart + dir * fract * thetaRange; - Vector2 o = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * r; - output.Add(centre + o); - } - - return output; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.MathUtils; +using OpenTK; + +namespace osu.Game.Rulesets.Objects +{ + public class CircularArcApproximator + { + private readonly Vector2 a; + private readonly Vector2 b; + private readonly Vector2 c; + + private int amountPoints; + + private const float tolerance = 0.1f; + + public CircularArcApproximator(Vector2 a, Vector2 b, Vector2 c) + { + this.a = a; + this.b = b; + this.c = c; + } + + /// + /// Creates a piecewise-linear approximation of a circular arc curve. + /// + /// A list of vectors representing the piecewise-linear approximation. + public List CreateArc() + { + float aSq = (b - c).LengthSquared; + float bSq = (a - c).LengthSquared; + float cSq = (a - b).LengthSquared; + + // If we have a degenerate triangle where a side-length is almost zero, then give up and fall + // back to a more numerically stable method. + if (Precision.AlmostEquals(aSq, 0) || Precision.AlmostEquals(bSq, 0) || Precision.AlmostEquals(cSq, 0)) + return new List(); + + float s = aSq * (bSq + cSq - aSq); + float t = bSq * (aSq + cSq - bSq); + float u = cSq * (aSq + bSq - cSq); + + float sum = s + t + u; + + // If we have a degenerate triangle with an almost-zero size, then give up and fall + // back to a more numerically stable method. + if (Precision.AlmostEquals(sum, 0)) + return new List(); + + Vector2 centre = (s * a + t * b + u * c) / sum; + Vector2 dA = a - centre; + Vector2 dC = c - centre; + + float r = dA.Length; + + double thetaStart = Math.Atan2(dA.Y, dA.X); + double thetaEnd = Math.Atan2(dC.Y, dC.X); + + while (thetaEnd < thetaStart) + thetaEnd += 2 * Math.PI; + + double dir = 1; + double thetaRange = thetaEnd - thetaStart; + + // Decide in which direction to draw the circle, depending on which side of + // AC B lies. + Vector2 orthoAtoC = c - a; + orthoAtoC = new Vector2(orthoAtoC.Y, -orthoAtoC.X); + if (Vector2.Dot(orthoAtoC, b - a) < 0) + { + dir = -dir; + thetaRange = 2 * Math.PI - thetaRange; + } + + // We select the amount of points for the approximation by requiring the discrete curvature + // to be smaller than the provided tolerance. The exact angle required to meet the tolerance + // is: 2 * Math.Acos(1 - TOLERANCE / r) + // The special case is required for extremely short sliders where the radius is smaller than + // the tolerance. This is a pathological rather than a realistic case. + amountPoints = 2 * r <= tolerance ? 2 : Math.Max(2, (int)Math.Ceiling(thetaRange / (2 * Math.Acos(1 - tolerance / r)))); + + List output = new List(amountPoints); + + for (int i = 0; i < amountPoints; ++i) + { + double fract = (double)i / (amountPoints - 1); + double theta = thetaStart + dir * fract * thetaRange; + Vector2 o = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * r; + output.Add(centre + o); + } + + return output; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs b/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs index 02b8f2e654..9a72c44d67 100644 --- a/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs +++ b/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Drawables -{ - public enum ArmedState - { - Idle, - Hit, - Miss - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Drawables +{ + public enum ArmedState + { + Idle, + Hit, + Miss + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index fdfef14a88..88990d435c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -1,245 +1,245 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Game.Audio; -using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Objects.Drawables -{ - public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColour - { - public readonly HitObject HitObject; - - /// - /// The colour used for various elements of this DrawableHitObject. - /// - public virtual Color4 AccentColour { get; set; } = Color4.Gray; - - // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first - protected virtual string SampleNamespace => null; - - protected SkinnableSound Samples; - - protected virtual IEnumerable GetSamples() => HitObject.Samples; - - private readonly Lazy> nestedHitObjects = new Lazy>(); - public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated; - public IReadOnlyList NestedHitObjects => nestedHitObjects.Value; - - public event Action OnJudgement; - public event Action OnJudgementRemoved; - - public IReadOnlyList Judgements => judgements; - private readonly List judgements = new List(); - - /// - /// Whether a visible judgement should be displayed when this representation is hit. - /// - public virtual bool DisplayJudgement => true; - - /// - /// Whether this and all of its nested s have been hit. - /// - public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit)); - - /// - /// Whether this and all of its nested s have been judged. - /// - public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged)); - - /// - /// Whether this can be judged. - /// - protected virtual bool ProvidesJudgement => true; - - private bool judgementOccurred; - private bool judgementFinalized => judgements.LastOrDefault()?.Final == true; - - public bool Interactive = true; - public override bool HandleKeyboardInput => Interactive; - public override bool HandleMouseInput => Interactive; - - public override bool RemoveWhenNotAlive => false; - public override bool RemoveCompletedTransforms => false; - protected override bool RequiresChildrenUpdate => true; - - public readonly Bindable State = new Bindable(); - - protected DrawableHitObject(HitObject hitObject) - { - HitObject = hitObject; - } - - [BackgroundDependencyLoader] - private void load() - { - var samples = GetSamples().ToArray(); - - if (samples.Any()) - { - if (HitObject.SampleControlPoint == null) - throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." - + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo - { - Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank, - Name = s.Name, - Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume, - Namespace = SampleNamespace - }).ToArray())); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - State.ValueChanged += state => - { - UpdateState(state); - - // apply any custom state overrides - ApplyCustomUpdateState?.Invoke(this, state); - - if (State == ArmedState.Hit) - PlaySamples(); - }; - - State.TriggerChange(); - } - - protected abstract void UpdateState(ArmedState state); - - /// - /// Bind to apply a custom state which can override the default implementation. - /// - public event Action ApplyCustomUpdateState; - - /// - /// Plays all the hitsounds for this . - /// - public void PlaySamples() => Samples?.Play(); - - protected override void Update() - { - base.Update(); - - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - - while (judgements.Count > 0) - { - var lastJudgement = judgements[judgements.Count - 1]; - if (lastJudgement.TimeOffset + endTime <= Time.Current) - break; - - judgements.RemoveAt(judgements.Count - 1); - State.Value = ArmedState.Idle; - - OnJudgementRemoved?.Invoke(this, lastJudgement); - } - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - UpdateJudgement(false); - } - - protected virtual void AddNested(DrawableHitObject h) - { - h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); - h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); - h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - - nestedHitObjects.Value.Add(h); - } - - /// - /// Notifies that a new judgement has occurred for this . - /// - /// The . - protected void AddJudgement(Judgement judgement) - { - judgementOccurred = true; - - // Ensure that the judgement is given a valid time offset, because this may not get set by the caller - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - judgement.TimeOffset = Time.Current - endTime; - - judgements.Add(judgement); - - switch (judgement.Result) - { - case HitResult.None: - break; - case HitResult.Miss: - State.Value = ArmedState.Miss; - break; - default: - State.Value = ArmedState.Hit; - break; - } - - OnJudgement?.Invoke(this, judgement); - } - - /// - /// Processes this , checking if any judgements have occurred. - /// - /// Whether the user triggered this process. - /// Whether a judgement has occurred from this or any nested s. - protected bool UpdateJudgement(bool userTriggered) - { - judgementOccurred = false; - - if (AllJudged) - return false; - - if (HasNestedHitObjects) - foreach (var d in NestedHitObjects) - judgementOccurred |= d.UpdateJudgement(userTriggered); - - if (!ProvidesJudgement || judgementFinalized || judgementOccurred) - return judgementOccurred; - - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - CheckForJudgements(userTriggered, Time.Current - endTime); - - return judgementOccurred; - } - - /// - /// Checks if any judgements have occurred for this . This method must construct - /// all s and notify of them through . - /// - /// Whether the user triggered this check. - /// The offset from the end time at which this check occurred. A > 0 - /// implies that this check occurred after the end time of . - protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) - { - } - } - - public abstract class DrawableHitObject : DrawableHitObject - where TObject : HitObject - { - public new readonly TObject HitObject; - - protected DrawableHitObject(TObject hitObject) - : base(hitObject) - { - HitObject = hitObject; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Objects.Drawables +{ + public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColour + { + public readonly HitObject HitObject; + + /// + /// The colour used for various elements of this DrawableHitObject. + /// + public virtual Color4 AccentColour { get; set; } = Color4.Gray; + + // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first + protected virtual string SampleNamespace => null; + + protected SkinnableSound Samples; + + protected virtual IEnumerable GetSamples() => HitObject.Samples; + + private readonly Lazy> nestedHitObjects = new Lazy>(); + public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated; + public IReadOnlyList NestedHitObjects => nestedHitObjects.Value; + + public event Action OnJudgement; + public event Action OnJudgementRemoved; + + public IReadOnlyList Judgements => judgements; + private readonly List judgements = new List(); + + /// + /// Whether a visible judgement should be displayed when this representation is hit. + /// + public virtual bool DisplayJudgement => true; + + /// + /// Whether this and all of its nested s have been hit. + /// + public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit)); + + /// + /// Whether this and all of its nested s have been judged. + /// + public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged)); + + /// + /// Whether this can be judged. + /// + protected virtual bool ProvidesJudgement => true; + + private bool judgementOccurred; + private bool judgementFinalized => judgements.LastOrDefault()?.Final == true; + + public bool Interactive = true; + public override bool HandleKeyboardInput => Interactive; + public override bool HandleMouseInput => Interactive; + + public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + protected override bool RequiresChildrenUpdate => true; + + public readonly Bindable State = new Bindable(); + + protected DrawableHitObject(HitObject hitObject) + { + HitObject = hitObject; + } + + [BackgroundDependencyLoader] + private void load() + { + var samples = GetSamples().ToArray(); + + if (samples.Any()) + { + if (HitObject.SampleControlPoint == null) + throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo + { + Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank, + Name = s.Name, + Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume, + Namespace = SampleNamespace + }).ToArray())); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + State.ValueChanged += state => + { + UpdateState(state); + + // apply any custom state overrides + ApplyCustomUpdateState?.Invoke(this, state); + + if (State == ArmedState.Hit) + PlaySamples(); + }; + + State.TriggerChange(); + } + + protected abstract void UpdateState(ArmedState state); + + /// + /// Bind to apply a custom state which can override the default implementation. + /// + public event Action ApplyCustomUpdateState; + + /// + /// Plays all the hitsounds for this . + /// + public void PlaySamples() => Samples?.Play(); + + protected override void Update() + { + base.Update(); + + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + + while (judgements.Count > 0) + { + var lastJudgement = judgements[judgements.Count - 1]; + if (lastJudgement.TimeOffset + endTime <= Time.Current) + break; + + judgements.RemoveAt(judgements.Count - 1); + State.Value = ArmedState.Idle; + + OnJudgementRemoved?.Invoke(this, lastJudgement); + } + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + UpdateJudgement(false); + } + + protected virtual void AddNested(DrawableHitObject h) + { + h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); + h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); + h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); + + nestedHitObjects.Value.Add(h); + } + + /// + /// Notifies that a new judgement has occurred for this . + /// + /// The . + protected void AddJudgement(Judgement judgement) + { + judgementOccurred = true; + + // Ensure that the judgement is given a valid time offset, because this may not get set by the caller + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + judgement.TimeOffset = Time.Current - endTime; + + judgements.Add(judgement); + + switch (judgement.Result) + { + case HitResult.None: + break; + case HitResult.Miss: + State.Value = ArmedState.Miss; + break; + default: + State.Value = ArmedState.Hit; + break; + } + + OnJudgement?.Invoke(this, judgement); + } + + /// + /// Processes this , checking if any judgements have occurred. + /// + /// Whether the user triggered this process. + /// Whether a judgement has occurred from this or any nested s. + protected bool UpdateJudgement(bool userTriggered) + { + judgementOccurred = false; + + if (AllJudged) + return false; + + if (HasNestedHitObjects) + foreach (var d in NestedHitObjects) + judgementOccurred |= d.UpdateJudgement(userTriggered); + + if (!ProvidesJudgement || judgementFinalized || judgementOccurred) + return judgementOccurred; + + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + CheckForJudgements(userTriggered, Time.Current - endTime); + + return judgementOccurred; + } + + /// + /// Checks if any judgements have occurred for this . This method must construct + /// all s and notify of them through . + /// + /// Whether the user triggered this check. + /// The offset from the end time at which this check occurred. A > 0 + /// implies that this check occurred after the end time of . + protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) + { + } + } + + public abstract class DrawableHitObject : DrawableHitObject + where TObject : HitObject + { + public new readonly TObject HitObject; + + protected DrawableHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs b/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs index d10149facd..e10b50e672 100644 --- a/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs +++ b/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Objects.Drawables -{ - public interface IDrawableHitObjectWithProxiedApproach - { - Drawable ProxiedLayer { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Objects.Drawables +{ + public interface IDrawableHitObjectWithProxiedApproach + { + Drawable ProxiedLayer { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs index 55dd0a16cc..ce365f04bd 100644 --- a/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs @@ -1,31 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Objects.Drawables -{ - /// - /// An interface that exposes properties required for scrolling hit objects to be properly displayed. - /// - internal interface IScrollingHitObject : IDrawable - { - /// - /// Time offset before the hit object start time at which this becomes visible and the time offset - /// after the hit object's end time after which it expires. - /// - /// - /// This provides only a default life time range, however classes inheriting from should override - /// their life times if more tight control is desired. - /// - /// - BindableDouble LifetimeOffset { get; } - - /// - /// Axes which this will scroll through. - /// This is set by the container which this scrolls through. - /// - Axes ScrollingAxes { set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Objects.Drawables +{ + /// + /// An interface that exposes properties required for scrolling hit objects to be properly displayed. + /// + internal interface IScrollingHitObject : IDrawable + { + /// + /// Time offset before the hit object start time at which this becomes visible and the time offset + /// after the hit object's end time after which it expires. + /// + /// + /// This provides only a default life time range, however classes inheriting from should override + /// their life times if more tight control is desired. + /// + /// + BindableDouble LifetimeOffset { get; } + + /// + /// Axes which this will scroll through. + /// This is set by the container which this scrolls through. + /// + Axes ScrollingAxes { set; } + } +} diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 75cb65eff0..13fa61f536 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -1,102 +1,102 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using Newtonsoft.Json; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Lists; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects -{ - /// - /// A HitObject describes an object in a Beatmap. - /// - /// HitObjects may contain more properties for which you should be checking through the IHas* types. - /// - /// - public class HitObject - { - /// - /// The time at which the HitObject starts. - /// - public virtual double StartTime { get; set; } - - private List samples; - - /// - /// The samples to be played when this hit object is hit. - /// - /// In the case of types, this is the sample of the curve body - /// and can be treated as the default samples for the hit object. - /// - /// - public List Samples - { - get => samples ?? (samples = new List()); - set => samples = value; - } - - [JsonIgnore] - public SampleControlPoint SampleControlPoint; - - /// - /// Whether this is in Kiai time. - /// - [JsonIgnore] - public bool Kiai { get; private set; } - - private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - - private HitWindows hitWindows; - - /// - /// The hit windows for this . - /// - public HitWindows HitWindows - { - get => hitWindows ?? (hitWindows = new HitWindows(overallDifficulty)); - protected set => hitWindows = value; - } - - private readonly SortedList nestedHitObjects = new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); - - [JsonIgnore] - public IReadOnlyList NestedHitObjects => nestedHitObjects; - - /// - /// Applies default values to this HitObject. - /// - /// The control points. - /// The difficulty settings to use. - public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - ApplyDefaultsToSelf(controlPointInfo, difficulty); - - nestedHitObjects.Clear(); - CreateNestedHitObjects(); - nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty)); - } - - protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - SampleControlPoint samplePoint = controlPointInfo.SamplePointAt(StartTime); - EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - - Kiai = effectPoint.KiaiMode; - SampleControlPoint = samplePoint; - - overallDifficulty = difficulty.OverallDifficulty; - hitWindows = null; - } - - protected virtual void CreateNestedHitObjects() - { - } - - protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Lists; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects +{ + /// + /// A HitObject describes an object in a Beatmap. + /// + /// HitObjects may contain more properties for which you should be checking through the IHas* types. + /// + /// + public class HitObject + { + /// + /// The time at which the HitObject starts. + /// + public virtual double StartTime { get; set; } + + private List samples; + + /// + /// The samples to be played when this hit object is hit. + /// + /// In the case of types, this is the sample of the curve body + /// and can be treated as the default samples for the hit object. + /// + /// + public List Samples + { + get => samples ?? (samples = new List()); + set => samples = value; + } + + [JsonIgnore] + public SampleControlPoint SampleControlPoint; + + /// + /// Whether this is in Kiai time. + /// + [JsonIgnore] + public bool Kiai { get; private set; } + + private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; + + private HitWindows hitWindows; + + /// + /// The hit windows for this . + /// + public HitWindows HitWindows + { + get => hitWindows ?? (hitWindows = new HitWindows(overallDifficulty)); + protected set => hitWindows = value; + } + + private readonly SortedList nestedHitObjects = new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); + + [JsonIgnore] + public IReadOnlyList NestedHitObjects => nestedHitObjects; + + /// + /// Applies default values to this HitObject. + /// + /// The control points. + /// The difficulty settings to use. + public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + ApplyDefaultsToSelf(controlPointInfo, difficulty); + + nestedHitObjects.Clear(); + CreateNestedHitObjects(); + nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty)); + } + + protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + SampleControlPoint samplePoint = controlPointInfo.SamplePointAt(StartTime); + EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); + + Kiai = effectPoint.KiaiMode; + SampleControlPoint = samplePoint; + + overallDifficulty = difficulty.OverallDifficulty; + hitWindows = null; + } + + protected virtual void CreateNestedHitObjects() + { + } + + protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject); + } +} diff --git a/osu.Game/Rulesets/Objects/HitObjectParser.cs b/osu.Game/Rulesets/Objects/HitObjectParser.cs index 5a38daa654..d44da4e16c 100644 --- a/osu.Game/Rulesets/Objects/HitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/HitObjectParser.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects -{ - public abstract class HitObjectParser - { - public abstract HitObject Parse(string text); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects +{ + public abstract class HitObjectParser + { + public abstract HitObject Parse(string text); + } +} diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index ddd9f9b5dc..bf0878a408 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -1,173 +1,173 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Objects -{ - public class HitWindows - { - private static readonly IReadOnlyDictionary base_ranges = new Dictionary - { - { HitResult.Perfect, (44.8, 38.8, 27.8) }, - { HitResult.Great, (128, 98, 68 ) }, - { HitResult.Good, (194, 164, 134) }, - { HitResult.Ok, (254, 224, 194) }, - { HitResult.Meh, (302, 272, 242) }, - { HitResult.Miss, (376, 346, 316) }, - }; - - /// - /// Hit window for a result. - /// The user can only achieve receive this result if is true. - /// - public double Perfect { get; protected set; } - - /// - /// Hit window for a result. - /// - public double Great { get; protected set; } - - /// - /// Hit window for a result. - /// - public double Good { get; protected set; } - - /// - /// Hit window for an result. - /// The user can only achieve this result if is true. - /// - public double Ok { get; protected set; } - - /// - /// Hit window for a result. - /// - public double Meh { get; protected set; } - - /// - /// Hit window for a result. - /// - public double Miss { get; protected set; } - - /// - /// Whether it's possible to achieve a result. - /// - public bool AllowsPerfect; - - /// - /// Whether it's possible to achieve a result. - /// - public bool AllowsOk; - - /// - /// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window. - /// - /// The parameter. - public HitWindows(double difficulty) - { - Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); - Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); - Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); - Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]); - Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); - Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); - } - - /// - /// Retrieves the for a time offset. - /// - /// The time offset. - /// The hit result, or if doesn't result in a judgement. - public HitResult ResultFor(double timeOffset) - { - timeOffset = Math.Abs(timeOffset); - - if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect)) - return HitResult.Perfect; - if (timeOffset <= HalfWindowFor(HitResult.Great)) - return HitResult.Great; - if (timeOffset <= HalfWindowFor(HitResult.Good)) - return HitResult.Good; - if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok)) - return HitResult.Ok; - if (timeOffset <= HalfWindowFor(HitResult.Meh)) - return HitResult.Meh; - if (timeOffset <= HalfWindowFor(HitResult.Miss)) - return HitResult.Miss; - - return HitResult.None; - } - - /// - /// Retrieves half the hit window for a . - /// This is useful if the hit window for one half of the hittable range of a is required. - /// - /// The expected . - /// One half of the hit window for . - public double HalfWindowFor(HitResult result) - { - switch (result) - { - case HitResult.Perfect: - return Perfect / 2; - case HitResult.Great: - return Great / 2; - case HitResult.Good: - return Good / 2; - case HitResult.Ok: - return Ok / 2; - case HitResult.Meh: - return Meh / 2; - case HitResult.Miss: - return Miss / 2; - default: - throw new ArgumentException(nameof(result)); - } - } - - /// - /// Given a time offset, whether the can ever be hit in the future with a non- result. - /// This happens if is less than what is required for a result. - /// - /// The time offset. - /// Whether the can be hit at any point in the future from this time offset. - public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh); - - /// - /// Multiplies all hit windows by a value. - /// - /// The hit windows to multiply. - /// The value to multiply each hit window by. - public static HitWindows operator *(HitWindows windows, double value) - { - windows.Perfect *= value; - windows.Great *= value; - windows.Good *= value; - windows.Ok *= value; - windows.Meh *= value; - windows.Miss *= value; - - return windows; - } - - /// - /// Divides all hit windows by a value. - /// - /// The hit windows to divide. - /// The value to divide each hit window by. - public static HitWindows operator /(HitWindows windows, double value) - { - windows.Perfect /= value; - windows.Great /= value; - windows.Good /= value; - windows.Ok /= value; - windows.Meh /= value; - windows.Miss /= value; - - return windows; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Objects +{ + public class HitWindows + { + private static readonly IReadOnlyDictionary base_ranges = new Dictionary + { + { HitResult.Perfect, (44.8, 38.8, 27.8) }, + { HitResult.Great, (128, 98, 68 ) }, + { HitResult.Good, (194, 164, 134) }, + { HitResult.Ok, (254, 224, 194) }, + { HitResult.Meh, (302, 272, 242) }, + { HitResult.Miss, (376, 346, 316) }, + }; + + /// + /// Hit window for a result. + /// The user can only achieve receive this result if is true. + /// + public double Perfect { get; protected set; } + + /// + /// Hit window for a result. + /// + public double Great { get; protected set; } + + /// + /// Hit window for a result. + /// + public double Good { get; protected set; } + + /// + /// Hit window for an result. + /// The user can only achieve this result if is true. + /// + public double Ok { get; protected set; } + + /// + /// Hit window for a result. + /// + public double Meh { get; protected set; } + + /// + /// Hit window for a result. + /// + public double Miss { get; protected set; } + + /// + /// Whether it's possible to achieve a result. + /// + public bool AllowsPerfect; + + /// + /// Whether it's possible to achieve a result. + /// + public bool AllowsOk; + + /// + /// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window. + /// + /// The parameter. + public HitWindows(double difficulty) + { + Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); + Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); + Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); + Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]); + Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); + Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); + } + + /// + /// Retrieves the for a time offset. + /// + /// The time offset. + /// The hit result, or if doesn't result in a judgement. + public HitResult ResultFor(double timeOffset) + { + timeOffset = Math.Abs(timeOffset); + + if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect)) + return HitResult.Perfect; + if (timeOffset <= HalfWindowFor(HitResult.Great)) + return HitResult.Great; + if (timeOffset <= HalfWindowFor(HitResult.Good)) + return HitResult.Good; + if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok)) + return HitResult.Ok; + if (timeOffset <= HalfWindowFor(HitResult.Meh)) + return HitResult.Meh; + if (timeOffset <= HalfWindowFor(HitResult.Miss)) + return HitResult.Miss; + + return HitResult.None; + } + + /// + /// Retrieves half the hit window for a . + /// This is useful if the hit window for one half of the hittable range of a is required. + /// + /// The expected . + /// One half of the hit window for . + public double HalfWindowFor(HitResult result) + { + switch (result) + { + case HitResult.Perfect: + return Perfect / 2; + case HitResult.Great: + return Great / 2; + case HitResult.Good: + return Good / 2; + case HitResult.Ok: + return Ok / 2; + case HitResult.Meh: + return Meh / 2; + case HitResult.Miss: + return Miss / 2; + default: + throw new ArgumentException(nameof(result)); + } + } + + /// + /// Given a time offset, whether the can ever be hit in the future with a non- result. + /// This happens if is less than what is required for a result. + /// + /// The time offset. + /// Whether the can be hit at any point in the future from this time offset. + public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh); + + /// + /// Multiplies all hit windows by a value. + /// + /// The hit windows to multiply. + /// The value to multiply each hit window by. + public static HitWindows operator *(HitWindows windows, double value) + { + windows.Perfect *= value; + windows.Great *= value; + windows.Good *= value; + windows.Ok *= value; + windows.Meh *= value; + windows.Miss *= value; + + return windows; + } + + /// + /// Divides all hit windows by a value. + /// + /// The hit windows to divide. + /// The value to divide each hit window by. + public static HitWindows operator /(HitWindows windows, double value) + { + windows.Perfect /= value; + windows.Great /= value; + windows.Good /= value; + windows.Ok /= value; + windows.Meh /= value; + windows.Miss /= value; + + return windows; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 366449a910..50035ea116 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : HitObject, IHasCombo, IHasXPosition - { - public float X { get; set; } - - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Catch +{ + /// + /// Legacy osu!catch Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasCombo, IHasXPosition + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index d0e7e09ad2..c7451dc978 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -1,52 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// A HitObjectParser to parse legacy osu!catch Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - protected override HitObject CreateHit(Vector2 position, bool newCombo) - { - return new ConvertHit - { - X = position.X, - NewCombo = newCombo, - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) - { - return new ConvertSlider - { - X = position.X, - NewCombo = newCombo, - ControlPoints = controlPoints, - Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, double endTime) - { - return new ConvertSpinner - { - EndTime = endTime - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) - { - return null; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Legacy.Catch +{ + /// + /// A HitObjectParser to parse legacy osu!catch Beatmaps. + /// + public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + X = position.X, + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + { + return new ConvertSlider + { + X = position.X, + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = length, + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + EndTime = endTime + }; + } + + protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + { + return null; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index 6132ea63a6..73e277a125 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo - { - public float X { get; set; } - - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Catch +{ + /// + /// Legacy osu!catch Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 1df7cce899..9dfe12f25e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime - { - public double EndTime { get; set; } - - public double Duration => EndTime - StartTime; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Catch +{ + /// + /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSpinner : HitObject, IHasEndTime + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 5084b28cf2..95abc4edb3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -1,315 +1,315 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Rulesets.Objects.Types; -using System; -using System.Collections.Generic; -using System.Globalization; -using osu.Game.Beatmaps.Formats; -using osu.Game.Audio; -using System.Linq; -using osu.Framework.MathUtils; - -namespace osu.Game.Rulesets.Objects.Legacy -{ - /// - /// A HitObjectParser to parse legacy Beatmaps. - /// - public abstract class ConvertHitObjectParser : HitObjectParser - { - public override HitObject Parse(string text) - { - try - { - string[] split = text.Split(','); - - ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax; - bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); - type &= ~ConvertHitObjectType.NewCombo; - - var soundType = (LegacySoundType)int.Parse(split[4]); - var bankInfo = new SampleBankInfo(); - - HitObject result = null; - - if ((type & ConvertHitObjectType.Circle) > 0) - { - result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo); - - if (split.Length > 5) - readCustomSampleBanks(split[5], bankInfo); - } - else if ((type & ConvertHitObjectType.Slider) > 0) - { - var pos = new Vector2(int.Parse(split[0]), int.Parse(split[1])); - - CurveType curveType = CurveType.Catmull; - double length = 0; - var points = new List { Vector2.Zero }; - - string[] pointsplit = split[5].Split('|'); - foreach (string t in pointsplit) - { - if (t.Length == 1) - { - switch (t) - { - case @"C": - curveType = CurveType.Catmull; - break; - case @"B": - curveType = CurveType.Bezier; - break; - case @"L": - curveType = CurveType.Linear; - break; - case @"P": - curveType = CurveType.PerfectCurve; - break; - } - continue; - } - - string[] temp = t.Split(':'); - points.Add(new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)) - pos); - } - - // osu-stable special-cased colinear perfect curves to a CurveType.Linear - bool isLinear(List p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - if (points.Count == 3 && curveType == CurveType.PerfectCurve && isLinear(points)) - curveType = CurveType.Linear; - - int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); - - if (repeatCount > 9000) - throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); - - // osu-stable treated the first span of the slider as a repeat, but no repeats are happening - repeatCount = Math.Max(0, repeatCount - 1); - - - if (split.Length > 7) - length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); - - if (split.Length > 10) - readCustomSampleBanks(split[10], bankInfo); - - // One node for each repeat + the start and end nodes - int nodes = repeatCount + 2; - - // Populate node sample bank infos with the default hit object sample bank - var nodeBankInfos = new List(); - for (int i = 0; i < nodes; i++) - nodeBankInfos.Add(bankInfo.Clone()); - - // Read any per-node sample banks - if (split.Length > 9 && split[9].Length > 0) - { - string[] sets = split[9].Split('|'); - for (int i = 0; i < nodes; i++) - { - if (i >= sets.Length) - break; - - SampleBankInfo info = nodeBankInfos[i]; - readCustomSampleBanks(sets[i], info); - } - } - - // Populate node sound types with the default hit object sound type - var nodeSoundTypes = new List(); - for (int i = 0; i < nodes; i++) - nodeSoundTypes.Add(soundType); - - // Read any per-node sound types - if (split.Length > 8 && split[8].Length > 0) - { - string[] adds = split[8].Split('|'); - for (int i = 0; i < nodes; i++) - { - if (i >= adds.Length) - break; - - int sound; - int.TryParse(adds[i], out sound); - nodeSoundTypes[i] = (LegacySoundType)sound; - } - } - - // Generate the final per-node samples - var nodeSamples = new List>(nodes); - for (int i = 0; i < nodes; i++) - nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - - result = CreateSlider(pos, combo, points, length, curveType, repeatCount, nodeSamples); - } - else if ((type & ConvertHitObjectType.Spinner) > 0) - { - result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture)); - - if (split.Length > 6) - readCustomSampleBanks(split[6], bankInfo); - } - else if ((type & ConvertHitObjectType.Hold) > 0) - { - // Note: Hold is generated by BMS converts - - double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); - - if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) - { - string[] ss = split[5].Split(':'); - endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); - readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); - } - - result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime); - } - - if (result == null) - throw new InvalidOperationException($@"Unknown hit object type {type}."); - - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); - result.Samples = convertSoundType(soundType, bankInfo); - - return result; - } - catch (FormatException) - { - throw new FormatException("One or more hit objects were malformed."); - } - } - - private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) - { - if (string.IsNullOrEmpty(str)) - return; - - string[] split = str.Split(':'); - - var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]); - var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]); - - // Let's not implement this for now, because this doesn't fit nicely into the bank structure - //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty; - - string stringBank = bank.ToString().ToLower(); - if (stringBank == @"none") - stringBank = null; - string stringAddBank = addbank.ToString().ToLower(); - if (stringAddBank == @"none") - stringAddBank = null; - - bankInfo.Normal = stringBank; - bankInfo.Add = stringAddBank; - - if (split.Length > 3) - bankInfo.Volume = int.Parse(split[3]); - } - - /// - /// Creates a legacy Hit-type hit object. - /// - /// The position of the hit object. - /// Whether the hit object creates a new combo. - /// The hit object. - protected abstract HitObject CreateHit(Vector2 position, bool newCombo); - - /// - /// Creats a legacy Slider-type hit object. - /// - /// The position of the hit object. - /// Whether the hit object creates a new combo. - /// The slider control points. - /// The slider length. - /// The slider curve type. - /// The slider repeat count. - /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. - /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); - - /// - /// Creates a legacy Spinner-type hit object. - /// - /// The position of the hit object. - /// The spinner end time. - /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, double endTime); - - /// - /// Creates a legacy Hold-type hit object. - /// - /// The position of the hit object. - /// Whether the hit object creates a new combo. - /// The hold end time. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, double endTime); - - private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) - { - var soundTypes = new List - { - new SampleInfo - { - Bank = bankInfo.Normal, - Name = SampleInfo.HIT_NORMAL, - Volume = bankInfo.Volume - } - }; - - if ((type & LegacySoundType.Finish) > 0) - { - soundTypes.Add(new SampleInfo - { - Bank = bankInfo.Add, - Name = SampleInfo.HIT_FINISH, - Volume = bankInfo.Volume - }); - } - - if ((type & LegacySoundType.Whistle) > 0) - { - soundTypes.Add(new SampleInfo - { - Bank = bankInfo.Add, - Name = SampleInfo.HIT_WHISTLE, - Volume = bankInfo.Volume - }); - } - - if ((type & LegacySoundType.Clap) > 0) - { - soundTypes.Add(new SampleInfo - { - Bank = bankInfo.Add, - Name = SampleInfo.HIT_CLAP, - Volume = bankInfo.Volume - }); - } - - return soundTypes; - } - - private class SampleBankInfo - { - public string Normal; - public string Add; - public int Volume; - - public SampleBankInfo Clone() - { - return (SampleBankInfo)MemberwiseClone(); - } - } - - [Flags] - private enum LegacySoundType - { - None = 0, - Normal = 1, - Whistle = 2, - Finish = 4, - Clap = 8 - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Rulesets.Objects.Types; +using System; +using System.Collections.Generic; +using System.Globalization; +using osu.Game.Beatmaps.Formats; +using osu.Game.Audio; +using System.Linq; +using osu.Framework.MathUtils; + +namespace osu.Game.Rulesets.Objects.Legacy +{ + /// + /// A HitObjectParser to parse legacy Beatmaps. + /// + public abstract class ConvertHitObjectParser : HitObjectParser + { + public override HitObject Parse(string text) + { + try + { + string[] split = text.Split(','); + + ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax; + bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); + type &= ~ConvertHitObjectType.NewCombo; + + var soundType = (LegacySoundType)int.Parse(split[4]); + var bankInfo = new SampleBankInfo(); + + HitObject result = null; + + if ((type & ConvertHitObjectType.Circle) > 0) + { + result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo); + + if (split.Length > 5) + readCustomSampleBanks(split[5], bankInfo); + } + else if ((type & ConvertHitObjectType.Slider) > 0) + { + var pos = new Vector2(int.Parse(split[0]), int.Parse(split[1])); + + CurveType curveType = CurveType.Catmull; + double length = 0; + var points = new List { Vector2.Zero }; + + string[] pointsplit = split[5].Split('|'); + foreach (string t in pointsplit) + { + if (t.Length == 1) + { + switch (t) + { + case @"C": + curveType = CurveType.Catmull; + break; + case @"B": + curveType = CurveType.Bezier; + break; + case @"L": + curveType = CurveType.Linear; + break; + case @"P": + curveType = CurveType.PerfectCurve; + break; + } + continue; + } + + string[] temp = t.Split(':'); + points.Add(new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)) - pos); + } + + // osu-stable special-cased colinear perfect curves to a CurveType.Linear + bool isLinear(List p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + if (points.Count == 3 && curveType == CurveType.PerfectCurve && isLinear(points)) + curveType = CurveType.Linear; + + int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); + + if (repeatCount > 9000) + throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + + // osu-stable treated the first span of the slider as a repeat, but no repeats are happening + repeatCount = Math.Max(0, repeatCount - 1); + + + if (split.Length > 7) + length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); + + if (split.Length > 10) + readCustomSampleBanks(split[10], bankInfo); + + // One node for each repeat + the start and end nodes + int nodes = repeatCount + 2; + + // Populate node sample bank infos with the default hit object sample bank + var nodeBankInfos = new List(); + for (int i = 0; i < nodes; i++) + nodeBankInfos.Add(bankInfo.Clone()); + + // Read any per-node sample banks + if (split.Length > 9 && split[9].Length > 0) + { + string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) + { + if (i >= sets.Length) + break; + + SampleBankInfo info = nodeBankInfos[i]; + readCustomSampleBanks(sets[i], info); + } + } + + // Populate node sound types with the default hit object sound type + var nodeSoundTypes = new List(); + for (int i = 0; i < nodes; i++) + nodeSoundTypes.Add(soundType); + + // Read any per-node sound types + if (split.Length > 8 && split[8].Length > 0) + { + string[] adds = split[8].Split('|'); + for (int i = 0; i < nodes; i++) + { + if (i >= adds.Length) + break; + + int sound; + int.TryParse(adds[i], out sound); + nodeSoundTypes[i] = (LegacySoundType)sound; + } + } + + // Generate the final per-node samples + var nodeSamples = new List>(nodes); + for (int i = 0; i < nodes; i++) + nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); + + result = CreateSlider(pos, combo, points, length, curveType, repeatCount, nodeSamples); + } + else if ((type & ConvertHitObjectType.Spinner) > 0) + { + result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture)); + + if (split.Length > 6) + readCustomSampleBanks(split[6], bankInfo); + } + else if ((type & ConvertHitObjectType.Hold) > 0) + { + // Note: Hold is generated by BMS converts + + double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + + if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) + { + string[] ss = split[5].Split(':'); + endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); + readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + } + + result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime); + } + + if (result == null) + throw new InvalidOperationException($@"Unknown hit object type {type}."); + + result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + result.Samples = convertSoundType(soundType, bankInfo); + + return result; + } + catch (FormatException) + { + throw new FormatException("One or more hit objects were malformed."); + } + } + + private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) + { + if (string.IsNullOrEmpty(str)) + return; + + string[] split = str.Split(':'); + + var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]); + var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]); + + // Let's not implement this for now, because this doesn't fit nicely into the bank structure + //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty; + + string stringBank = bank.ToString().ToLower(); + if (stringBank == @"none") + stringBank = null; + string stringAddBank = addbank.ToString().ToLower(); + if (stringAddBank == @"none") + stringAddBank = null; + + bankInfo.Normal = stringBank; + bankInfo.Add = stringAddBank; + + if (split.Length > 3) + bankInfo.Volume = int.Parse(split[3]); + } + + /// + /// Creates a legacy Hit-type hit object. + /// + /// The position of the hit object. + /// Whether the hit object creates a new combo. + /// The hit object. + protected abstract HitObject CreateHit(Vector2 position, bool newCombo); + + /// + /// Creats a legacy Slider-type hit object. + /// + /// The position of the hit object. + /// Whether the hit object creates a new combo. + /// The slider control points. + /// The slider length. + /// The slider curve type. + /// The slider repeat count. + /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. + /// The hit object. + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); + + /// + /// Creates a legacy Spinner-type hit object. + /// + /// The position of the hit object. + /// The spinner end time. + /// The hit object. + protected abstract HitObject CreateSpinner(Vector2 position, double endTime); + + /// + /// Creates a legacy Hold-type hit object. + /// + /// The position of the hit object. + /// Whether the hit object creates a new combo. + /// The hold end time. + protected abstract HitObject CreateHold(Vector2 position, bool newCombo, double endTime); + + private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) + { + var soundTypes = new List + { + new SampleInfo + { + Bank = bankInfo.Normal, + Name = SampleInfo.HIT_NORMAL, + Volume = bankInfo.Volume + } + }; + + if ((type & LegacySoundType.Finish) > 0) + { + soundTypes.Add(new SampleInfo + { + Bank = bankInfo.Add, + Name = SampleInfo.HIT_FINISH, + Volume = bankInfo.Volume + }); + } + + if ((type & LegacySoundType.Whistle) > 0) + { + soundTypes.Add(new SampleInfo + { + Bank = bankInfo.Add, + Name = SampleInfo.HIT_WHISTLE, + Volume = bankInfo.Volume + }); + } + + if ((type & LegacySoundType.Clap) > 0) + { + soundTypes.Add(new SampleInfo + { + Bank = bankInfo.Add, + Name = SampleInfo.HIT_CLAP, + Volume = bankInfo.Volume + }); + } + + return soundTypes; + } + + private class SampleBankInfo + { + public string Normal; + public string Add; + public int Volume; + + public SampleBankInfo Clone() + { + return (SampleBankInfo)MemberwiseClone(); + } + } + + [Flags] + private enum LegacySoundType + { + None = 0, + Normal = 1, + Whistle = 2, + Finish = 4, + Clap = 8 + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs index 16ea008216..c0626c3e56 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Rulesets.Objects.Legacy -{ - [Flags] - internal enum ConvertHitObjectType - { - Circle = 1 << 0, - Slider = 1 << 1, - NewCombo = 1 << 2, - Spinner = 1 << 3, - ColourHax = 112, - Hold = 1 << 7 - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Rulesets.Objects.Legacy +{ + [Flags] + internal enum ConvertHitObjectType + { + Circle = 1 << 0, + Slider = 1 << 1, + NewCombo = 1 << 2, + Spinner = 1 << 3, + ColourHax = 112, + Hold = 1 << 7 + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index df7c8b0a83..17848d1e6e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -1,49 +1,49 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using System.Collections.Generic; -using OpenTK; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Objects.Legacy -{ - internal abstract class ConvertSlider : HitObject, IHasCurve - { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - - /// - /// s don't need a curve since they're converted to ruleset-specific hitobjects. - /// - public SliderCurve Curve { get; } = null; - public List ControlPoints { get; set; } - public CurveType CurveType { get; set; } - - public double Distance { get; set; } - - public List> RepeatSamples { get; set; } - public int RepeatCount { get; set; } - - public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; - public double Duration => EndTime - StartTime; - - public double Velocity = 1; - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - Velocity = scoringDistance / timingPoint.BeatLength; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; +using OpenTK; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Rulesets.Objects.Legacy +{ + internal abstract class ConvertSlider : HitObject, IHasCurve + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + /// + /// s don't need a curve since they're converted to ruleset-specific hitobjects. + /// + public SliderCurve Curve { get; } = null; + public List ControlPoints { get; set; } + public CurveType CurveType { get; set; } + + public double Distance { get; set; } + + public List> RepeatSamples { get; set; } + public int RepeatCount { get; set; } + + public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; + public double Duration => EndTime - StartTime; + + public double Velocity = 1; + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + Velocity = scoringDistance / timingPoint.BeatLength; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs index d756dc71ee..0db5a1dff1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// Legacy osu!mania Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : HitObject, IHasXPosition, IHasCombo - { - public float X { get; set; } - - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// Legacy osu!mania Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasXPosition, IHasCombo + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index ad51b636d3..99ba1304e8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -1,57 +1,57 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Audio; -using osu.Game.Rulesets.Objects.Types; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// A HitObjectParser to parse legacy osu!mania Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - protected override HitObject CreateHit(Vector2 position, bool newCombo) - { - return new ConvertHit - { - X = position.X, - NewCombo = newCombo, - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) - { - return new ConvertSlider - { - X = position.X, - NewCombo = newCombo, - ControlPoints = controlPoints, - Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, double endTime) - { - return new ConvertSpinner - { - X = position.X, - EndTime = endTime - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) - { - return new ConvertHold - { - X = position.X, - EndTime = endTime - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// A HitObjectParser to parse legacy osu!mania Beatmaps. + /// + public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + X = position.X, + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + { + return new ConvertSlider + { + X = position.X, + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = length, + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + X = position.X, + EndTime = endTime + }; + } + + protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + { + return new ConvertHold + { + X = position.X, + EndTime = endTime + }; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 24006a4901..e3b35e2f8e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - internal sealed class ConvertHold : HitObject, IHasXPosition, IHasEndTime - { - public float X { get; set; } - - public double EndTime { get; set; } - - public double Duration => EndTime - StartTime; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + internal sealed class ConvertHold : HitObject, IHasXPosition, IHasEndTime + { + public float X { get; set; } + + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs index d70c58e262..32fb197c62 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// Legacy osu!mania Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo - { - public float X { get; set; } - - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// Legacy osu!mania Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index 0def535129..c9b3046698 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition - { - public double EndTime { get; set; } - - public double Duration => EndTime - StartTime; - - public float X { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + + public float X { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 330b8acfec..f83173f498 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : HitObject, IHasPosition, IHasCombo - { - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// Legacy osu! Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasPosition, IHasCombo + { + public Vector2 Position { get; set; } + + public float X => Position.X; + + public float Y => Position.Y; + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index d2a0530dd9..801e4ea449 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -1,54 +1,54 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using osu.Game.Rulesets.Objects.Types; -using System.Collections.Generic; -using osu.Game.Audio; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// A HitObjectParser to parse legacy osu! Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - protected override HitObject CreateHit(Vector2 position, bool newCombo) - { - return new ConvertHit - { - Position = position, - NewCombo = newCombo, - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) - { - return new ConvertSlider - { - Position = position, - NewCombo = newCombo, - ControlPoints = controlPoints, - Distance = Math.Max(0, length), - CurveType = curveType, - RepeatSamples = repeatSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, double endTime) - { - return new ConvertSpinner - { - Position = position, - EndTime = endTime - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) - { - return null; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; +using osu.Game.Audio; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// A HitObjectParser to parse legacy osu! Beatmaps. + /// + public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + Position = position, + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + { + return new ConvertSlider + { + Position = position, + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = Math.Max(0, length), + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + Position = position, + EndTime = endTime + }; + } + + protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + { + return null; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index 513ea0616b..c6033d482c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo - { - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// Legacy osu! Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo + { + public Vector2 Position { get; set; } + + public float X => Position.X; + + public float Y => Position.Y; + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 4dbcc6d017..28aac6862e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition - { - public double EndTime { get; set; } - - public double Duration => EndTime - StartTime; - - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// Legacy osu! Spinner-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + + public Vector2 Position { get; set; } + + public float X => Position.X; + + public float Y => Position.Y; + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs index 1eafc477fa..72d18664bf 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// Legacy osu!taiko Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : HitObject, IHasCombo - { - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// Legacy osu!taiko Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasCombo + { + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 1d3bbd0946..03b1a3187a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -1,50 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Game.Rulesets.Objects.Types; -using System.Collections.Generic; -using osu.Game.Audio; - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// A HitObjectParser to parse legacy osu!taiko Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - protected override HitObject CreateHit(Vector2 position, bool newCombo) - { - return new ConvertHit - { - NewCombo = newCombo, - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) - { - return new ConvertSlider - { - NewCombo = newCombo, - ControlPoints = controlPoints, - Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, double endTime) - { - return new ConvertSpinner - { - EndTime = endTime - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) - { - return null; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; +using osu.Game.Audio; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// A HitObjectParser to parse legacy osu!taiko Beatmaps. + /// + public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + { + return new ConvertSlider + { + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = length, + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + EndTime = endTime + }; + } + + protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime) + { + return null; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs index a5e22602b0..e810e687bd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// Legacy osu!taiko Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo - { - public bool NewCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// Legacy osu!taiko Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo + { + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 3047228cdc..193e50aed6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime - { - public double EndTime { get; set; } - - public double Duration => EndTime - StartTime; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSpinner : HitObject, IHasEndTime + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + } +} diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs index 2b16f463df..86fe74f9af 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderCurve.cs @@ -1,205 +1,205 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public class SliderCurve - { - public double Distance; - - public List ControlPoints; - - public CurveType CurveType = CurveType.PerfectCurve; - - public Vector2 Offset; - - private readonly List calculatedPath = new List(); - private readonly List cumulativeLength = new List(); - - private List calculateSubpath(List subControlPoints) - { - switch (CurveType) - { - case CurveType.Linear: - return subControlPoints; - case CurveType.PerfectCurve: - //we can only use CircularArc iff we have exactly three control points and no dissection. - if (ControlPoints.Count != 3 || subControlPoints.Count != 3) - break; - - // Here we have exactly 3 control points. Attempt to fit a circular arc. - List subpath = new CircularArcApproximator(subControlPoints[0], subControlPoints[1], subControlPoints[2]).CreateArc(); - - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) - break; - - return subpath; - case CurveType.Catmull: - return new CatmullApproximator(subControlPoints).CreateCatmull(); - } - - return new BezierApproximator(subControlPoints).CreateBezier(); - } - - private void calculatePath() - { - calculatedPath.Clear(); - - // Sliders may consist of various subpaths separated by two consecutive vertices - // with the same position. The following loop parses these subpaths and computes - // their shape independently, consecutively appending them to calculatedPath. - List subControlPoints = new List(); - for (int i = 0; i < ControlPoints.Count; ++i) - { - subControlPoints.Add(ControlPoints[i]); - if (i == ControlPoints.Count - 1 || ControlPoints[i] == ControlPoints[i + 1]) - { - List subpath = calculateSubpath(subControlPoints); - foreach (Vector2 t in subpath) - if (calculatedPath.Count == 0 || calculatedPath.Last() != t) - calculatedPath.Add(t); - - subControlPoints.Clear(); - } - } - } - - private void calculateCumulativeLengthAndTrimPath() - { - double l = 0; - - cumulativeLength.Clear(); - cumulativeLength.Add(l); - - for (int i = 0; i < calculatedPath.Count - 1; ++i) - { - Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; - double d = diff.Length; - - // Shorten slider curves that are too long compared to what's - // in the .osu file. - if (Distance - l < d) - { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Distance - l) / d); - calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - - l = Distance; - cumulativeLength.Add(l); - break; - } - - l += d; - cumulativeLength.Add(l); - } - - //TODO: Figure out if the following code is needed in some cases. Judging by the map - // "Transform" http://osu.ppy.sh/s/484689 it seems like we should _not_ be doing this. - // Lengthen slider curves that are too short compared to what's - // in the .osu file. - /*if (l < Length && calculatedPath.Count > 1) - { - Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; - double d = diff.Length; - - if (d <= 0) - return; - - calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d); - cumulativeLength[calculatedPath.Count - 1] = Length; - }*/ - } - - public void Calculate() - { - calculatePath(); - calculateCumulativeLengthAndTrimPath(); - } - - private int indexOfDistance(double d) - { - int i = cumulativeLength.BinarySearch(d); - if (i < 0) i = ~i; - - return i; - } - - private double progressToDistance(double progress) - { - return MathHelper.Clamp(progress, 0, 1) * Distance; - } - - private Vector2 interpolateVertices(int i, double d) - { - if (calculatedPath.Count == 0) - return Vector2.Zero; - - if (i <= 0) - return calculatedPath.First(); - else if (i >= calculatedPath.Count) - return calculatedPath.Last(); - - Vector2 p0 = calculatedPath[i - 1]; - Vector2 p1 = calculatedPath[i]; - - double d0 = cumulativeLength[i - 1]; - double d1 = cumulativeLength[i]; - - // Avoid division by and almost-zero number in case two points are extremely close to each other. - if (Precision.AlmostEquals(d0, d1)) - return p0; - - double w = (d - d0) / (d1 - d0); - return p0 + (p1 - p0) * (float)w; - } - - /// - /// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider) - /// to 1 (end of the slider) and stores the generated path in the given list. - /// - /// The list to be filled with the computed curve. - /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - public void GetPathToProgress(List path, double p0, double p1) - { - if (calculatedPath.Count == 0 && ControlPoints.Count > 0) - Calculate(); - - double d0 = progressToDistance(p0); - double d1 = progressToDistance(p1); - - path.Clear(); - - int i = 0; - for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } - - path.Add(interpolateVertices(i, d0) + Offset); - - for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) - path.Add(calculatedPath[i] + Offset); - - path.Add(interpolateVertices(i, d1) + Offset); - } - - /// - /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve) - /// to 1 (end of the curve). - /// - /// Ranges from 0 (beginning of the curve) to 1 (end of the curve). - /// - public Vector2 PositionAt(double progress) - { - if (calculatedPath.Count == 0 && ControlPoints.Count > 0) - Calculate(); - - double d = progressToDistance(progress); - return interpolateVertices(indexOfDistance(d), d) + Offset; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Objects +{ + public class SliderCurve + { + public double Distance; + + public List ControlPoints; + + public CurveType CurveType = CurveType.PerfectCurve; + + public Vector2 Offset; + + private readonly List calculatedPath = new List(); + private readonly List cumulativeLength = new List(); + + private List calculateSubpath(List subControlPoints) + { + switch (CurveType) + { + case CurveType.Linear: + return subControlPoints; + case CurveType.PerfectCurve: + //we can only use CircularArc iff we have exactly three control points and no dissection. + if (ControlPoints.Count != 3 || subControlPoints.Count != 3) + break; + + // Here we have exactly 3 control points. Attempt to fit a circular arc. + List subpath = new CircularArcApproximator(subControlPoints[0], subControlPoints[1], subControlPoints[2]).CreateArc(); + + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; + + return subpath; + case CurveType.Catmull: + return new CatmullApproximator(subControlPoints).CreateCatmull(); + } + + return new BezierApproximator(subControlPoints).CreateBezier(); + } + + private void calculatePath() + { + calculatedPath.Clear(); + + // Sliders may consist of various subpaths separated by two consecutive vertices + // with the same position. The following loop parses these subpaths and computes + // their shape independently, consecutively appending them to calculatedPath. + List subControlPoints = new List(); + for (int i = 0; i < ControlPoints.Count; ++i) + { + subControlPoints.Add(ControlPoints[i]); + if (i == ControlPoints.Count - 1 || ControlPoints[i] == ControlPoints[i + 1]) + { + List subpath = calculateSubpath(subControlPoints); + foreach (Vector2 t in subpath) + if (calculatedPath.Count == 0 || calculatedPath.Last() != t) + calculatedPath.Add(t); + + subControlPoints.Clear(); + } + } + } + + private void calculateCumulativeLengthAndTrimPath() + { + double l = 0; + + cumulativeLength.Clear(); + cumulativeLength.Add(l); + + for (int i = 0; i < calculatedPath.Count - 1; ++i) + { + Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; + double d = diff.Length; + + // Shorten slider curves that are too long compared to what's + // in the .osu file. + if (Distance - l < d) + { + calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Distance - l) / d); + calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); + + l = Distance; + cumulativeLength.Add(l); + break; + } + + l += d; + cumulativeLength.Add(l); + } + + //TODO: Figure out if the following code is needed in some cases. Judging by the map + // "Transform" http://osu.ppy.sh/s/484689 it seems like we should _not_ be doing this. + // Lengthen slider curves that are too short compared to what's + // in the .osu file. + /*if (l < Length && calculatedPath.Count > 1) + { + Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; + double d = diff.Length; + + if (d <= 0) + return; + + calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d); + cumulativeLength[calculatedPath.Count - 1] = Length; + }*/ + } + + public void Calculate() + { + calculatePath(); + calculateCumulativeLengthAndTrimPath(); + } + + private int indexOfDistance(double d) + { + int i = cumulativeLength.BinarySearch(d); + if (i < 0) i = ~i; + + return i; + } + + private double progressToDistance(double progress) + { + return MathHelper.Clamp(progress, 0, 1) * Distance; + } + + private Vector2 interpolateVertices(int i, double d) + { + if (calculatedPath.Count == 0) + return Vector2.Zero; + + if (i <= 0) + return calculatedPath.First(); + else if (i >= calculatedPath.Count) + return calculatedPath.Last(); + + Vector2 p0 = calculatedPath[i - 1]; + Vector2 p1 = calculatedPath[i]; + + double d0 = cumulativeLength[i - 1]; + double d1 = cumulativeLength[i]; + + // Avoid division by and almost-zero number in case two points are extremely close to each other. + if (Precision.AlmostEquals(d0, d1)) + return p0; + + double w = (d - d0) / (d1 - d0); + return p0 + (p1 - p0) * (float)w; + } + + /// + /// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider) + /// to 1 (end of the slider) and stores the generated path in the given list. + /// + /// The list to be filled with the computed curve. + /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + public void GetPathToProgress(List path, double p0, double p1) + { + if (calculatedPath.Count == 0 && ControlPoints.Count > 0) + Calculate(); + + double d0 = progressToDistance(p0); + double d1 = progressToDistance(p1); + + path.Clear(); + + int i = 0; + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } + + path.Add(interpolateVertices(i, d0) + Offset); + + for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) + path.Add(calculatedPath[i] + Offset); + + path.Add(interpolateVertices(i, d1) + Offset); + } + + /// + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve) + /// to 1 (end of the curve). + /// + /// Ranges from 0 (beginning of the curve) to 1 (end of the curve). + /// + public Vector2 PositionAt(double progress) + { + if (calculatedPath.Count == 0 && ControlPoints.Count > 0) + Calculate(); + + double d = progressToDistance(progress); + return interpolateVertices(indexOfDistance(d), d) + Offset; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/CurveType.cs b/osu.Game/Rulesets/Objects/Types/CurveType.cs index 9b6772539e..1cee6202b6 100644 --- a/osu.Game/Rulesets/Objects/Types/CurveType.cs +++ b/osu.Game/Rulesets/Objects/Types/CurveType.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - public enum CurveType - { - Catmull, - Bezier, - Linear, - PerfectCurve - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + public enum CurveType + { + Catmull, + Bezier, + Linear, + PerfectCurve + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs index a10f5d814d..cb8b6f495a 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that is part of a combo. - /// - public interface IHasCombo - { - /// - /// Whether the HitObject starts a new combo. - /// - bool NewCombo { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that is part of a combo. + /// + public interface IHasCombo + { + /// + /// Whether the HitObject starts a new combo. + /// + bool NewCombo { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs b/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs index 68474a6e2c..c5d0152ae7 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboIndex.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that is part of a combo and has extended information about its position relative to other combo objects. - /// - public interface IHasComboIndex : IHasCombo - { - /// - /// The offset of this hitobject in the current combo. - /// - int IndexInCurrentCombo { get; set; } - - /// - /// The offset of this hitobject in the current combo. - /// - int ComboIndex { get; set; } - - /// - /// Whether this is the last object in the current combo. - /// - bool LastInCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that is part of a combo and has extended information about its position relative to other combo objects. + /// + public interface IHasComboIndex : IHasCombo + { + /// + /// The offset of this hitobject in the current combo. + /// + int IndexInCurrentCombo { get; set; } + + /// + /// The offset of this hitobject in the current combo. + /// + int ComboIndex { get; set; } + + /// + /// Whether this is the last object in the current combo. + /// + bool LastInCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index 1d4f4e0f90..a3dded94ec 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that is part of a combo and has extended information about its position relative to other combo objects. - /// - public interface IHasComboInformation : IHasCombo - { - /// - /// The offset of this hitobject in the current combo. - /// - int IndexInCurrentCombo { get; set; } - - /// - /// The offset of this combo in relation to the beatmap. - /// - int ComboIndex { get; set; } - - /// - /// Whether this is the last object in the current combo. - /// - bool LastInCombo { get; set; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that is part of a combo and has extended information about its position relative to other combo objects. + /// + public interface IHasComboInformation : IHasCombo + { + /// + /// The offset of this hitobject in the current combo. + /// + int IndexInCurrentCombo { get; set; } + + /// + /// The offset of this combo in relation to the beatmap. + /// + int ComboIndex { get; set; } + + /// + /// Whether this is the last object in the current combo. + /// + bool LastInCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 251ad3e3cd..54dcb26ae2 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -1,64 +1,64 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK; - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that has a curve. - /// - public interface IHasCurve : IHasDistance, IHasRepeats - { - /// - /// The curve. - /// - SliderCurve Curve { get; } - - /// - /// The control points that shape the curve. - /// - List ControlPoints { get; } - - /// - /// The type of curve. - /// - CurveType CurveType { get; } - } - - public static class HasCurveExtensions - { - /// - /// Computes the position on the curve relative to how much of the has been completed. - /// - /// The curve. - /// [0, 1] where 0 is the start time of the and 1 is the end time of the . - /// The position on the curve. - public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) - => obj.Curve.PositionAt(obj.ProgressAt(progress)); - - /// - /// Computes the progress along the curve relative to how much of the has been completed. - /// - /// The curve. - /// [0, 1] where 0 is the start time of the and 1 is the end time of the . - /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - public static double ProgressAt(this IHasCurve obj, double progress) - { - double p = progress * obj.SpanCount() % 1; - if (obj.SpanAt(progress) % 2 == 1) - p = 1 - p; - return p; - } - - /// - /// Determines which span of the curve the progress point is on. - /// - /// The curve. - /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - /// [0, SpanCount) where 0 is the first run. - public static int SpanAt(this IHasCurve obj, double progress) - => (int)(progress * obj.SpanCount()); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that has a curve. + /// + public interface IHasCurve : IHasDistance, IHasRepeats + { + /// + /// The curve. + /// + SliderCurve Curve { get; } + + /// + /// The control points that shape the curve. + /// + List ControlPoints { get; } + + /// + /// The type of curve. + /// + CurveType CurveType { get; } + } + + public static class HasCurveExtensions + { + /// + /// Computes the position on the curve relative to how much of the has been completed. + /// + /// The curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . + /// The position on the curve. + public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) + => obj.Curve.PositionAt(obj.ProgressAt(progress)); + + /// + /// Computes the progress along the curve relative to how much of the has been completed. + /// + /// The curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . + /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + public static double ProgressAt(this IHasCurve obj, double progress) + { + double p = progress * obj.SpanCount() % 1; + if (obj.SpanAt(progress) % 2 == 1) + p = 1 - p; + return p; + } + + /// + /// Determines which span of the curve the progress point is on. + /// + /// The curve. + /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + /// [0, SpanCount) where 0 is the first run. + public static int SpanAt(this IHasCurve obj, double progress) + => (int)(progress * obj.SpanCount()); + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs index 98ef898f22..535eb21ff4 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that has a positional length. - /// - public interface IHasDistance : IHasEndTime - { - /// - /// The positional length of the HitObject. - /// - double Distance { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that has a positional length. + /// + public interface IHasDistance : IHasEndTime + { + /// + /// The positional length of the HitObject. + /// + double Distance { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 1fedb8e061..8383e879a3 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that ends at a different time than its start time. - /// - public interface IHasEndTime - { - /// - /// The time at which the HitObject ends. - /// - double EndTime { get; } - - /// - /// The duration of the HitObject. - /// - double Duration { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that ends at a different time than its start time. + /// + public interface IHasEndTime + { + /// + /// The time at which the HitObject ends. + /// + double EndTime { get; } + + /// + /// The duration of the HitObject. + /// + double Duration { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasHold.cs b/osu.Game/Rulesets/Objects/Types/IHasHold.cs index 1c33187e93..429c582bab 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasHold.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasHold.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A special type of HitObject, mostly used for legacy conversion of "holds". - /// - public interface IHasHold - { - /// - /// The time at which the hold ends. - /// - double EndTime { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A special type of HitObject, mostly used for legacy conversion of "holds". + /// + public interface IHasHold + { + /// + /// The time at which the hold ends. + /// + double EndTime { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs index 0d9cd5ad2e..eabae611d5 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that has a starting position. - /// - public interface IHasPosition : IHasXPosition, IHasYPosition - { - /// - /// The starting position of the HitObject. - /// - Vector2 Position { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that has a starting position. + /// + public interface IHasPosition : IHasXPosition, IHasYPosition + { + /// + /// The starting position of the HitObject. + /// + Vector2 Position { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 75dd3776f9..7d918ff160 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Audio; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that spans some length. - /// - public interface IHasRepeats : IHasEndTime - { - /// - /// The amount of times the HitObject repeats. - /// - int RepeatCount { get; } - - /// - /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). - /// - List> RepeatSamples { get; } - } - - public static class HasRepeatsExtensions - { - /// - /// The amount of times the length of this spans. - /// - /// The object that has repeats. - public static int SpanCount(this IHasRepeats obj) => obj.RepeatCount + 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Audio; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that spans some length. + /// + public interface IHasRepeats : IHasEndTime + { + /// + /// The amount of times the HitObject repeats. + /// + int RepeatCount { get; } + + /// + /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). + /// + List> RepeatSamples { get; } + } + + public static class HasRepeatsExtensions + { + /// + /// The amount of times the length of this spans. + /// + /// The object that has repeats. + public static int SpanCount(this IHasRepeats obj) => obj.RepeatCount + 1; + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs index f73f338778..49b1d7cb28 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that has a starting X-position. - /// - public interface IHasXPosition - { - /// - /// The starting X-position of this HitObject. - /// - float X { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that has a starting X-position. + /// + public interface IHasXPosition + { + /// + /// The starting X-position of this HitObject. + /// + float X { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs index fc2d21481f..8ec7ab39fe 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Objects.Types -{ - /// - /// A HitObject that has a starting Y-position. - /// - public interface IHasYPosition - { - /// - /// The starting Y-position of this HitObject. - /// - float Y { get; } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that has a starting Y-position. + /// + public interface IHasYPosition + { + /// + /// The starting Y-position of this HitObject. + /// + float Y { get; } + } +} diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 21aa77d4cd..3ac0c3297c 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects; -using osu.Game.Beatmaps; - -namespace osu.Game.Rulesets.Replays -{ - public abstract class AutoGenerator : IAutoGenerator - where T : HitObject - { - /// - /// Creates the auto replay and returns it. - /// Every subclass of OsuAutoGeneratorBase should implement this! - /// - public abstract Replay Generate(); - - #region Parameters - - /// - /// The beatmap we're making. - /// - protected Beatmap Beatmap; - - #endregion - - protected AutoGenerator(Beatmap beatmap) - { - Beatmap = beatmap; - } - - #region Constants - - // Shared amongst all modes - protected const double KEY_UP_DELAY = 50; - - #endregion - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Replays +{ + public abstract class AutoGenerator : IAutoGenerator + where T : HitObject + { + /// + /// Creates the auto replay and returns it. + /// Every subclass of OsuAutoGeneratorBase should implement this! + /// + public abstract Replay Generate(); + + #region Parameters + + /// + /// The beatmap we're making. + /// + protected Beatmap Beatmap; + + #endregion + + protected AutoGenerator(Beatmap beatmap) + { + Beatmap = beatmap; + } + + #region Constants + + // Shared amongst all modes + protected const double KEY_UP_DELAY = 50; + + #endregion + } +} diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 5ffd67423e..0f5490a182 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -1,126 +1,126 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Input; -using osu.Game.Input.Handlers; -using OpenTK; -using OpenTK.Input; -using KeyboardState = osu.Framework.Input.KeyboardState; -using MouseState = osu.Framework.Input.MouseState; - -namespace osu.Game.Rulesets.Replays -{ - /// - /// The ReplayHandler will take a replay and handle the propagation of updates to the input stack. - /// It handles logic of any frames which *must* be executed. - /// - public abstract class FramedReplayInputHandler : ReplayInputHandler - where TFrame : ReplayFrame - { - private readonly Replay replay; - - protected List Frames => replay.Frames; - - public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex]; - public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex]; - - private int currentFrameIndex; - - private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1); - - protected FramedReplayInputHandler(Replay replay) - { - this.replay = replay; - } - - private bool advanceFrame() - { - int newFrame = nextFrameIndex; - - //ensure we aren't at an extent. - if (newFrame == currentFrameIndex) return false; - - currentFrameIndex = newFrame; - return true; - } - - public override List GetPendingStates() => new List(); - - public bool AtLastFrame => currentFrameIndex == Frames.Count - 1; - public bool AtFirstFrame => currentFrameIndex == 0; - - private const double sixty_frame_time = 1000.0 / 60; - - protected double CurrentTime { get; private set; } - private int currentDirection; - - /// - /// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data. - /// Disabling this can make replay playback smoother (useful for autoplay, currently). - /// - public bool FrameAccuratePlayback = true; - - protected bool HasFrames => Frames.Count > 0; - - private bool inImportantSection => - HasFrames && FrameAccuratePlayback && - //a button is in a pressed state - IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && - //the next frame is within an allowable time span - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2; - - protected virtual bool IsImportant(TFrame frame) => false; - - /// - /// Update the current frame based on an incoming time value. - /// There are cases where we return a "must-use" time value that is different from the input. - /// This is to ensure accurate playback of replay data. - /// - /// The time which we should use for finding the current frame. - /// The usable time value. If null, we should not advance time as we do not have enough data. - public override double? SetFrameFromTime(double time) - { - currentDirection = time.CompareTo(CurrentTime); - if (currentDirection == 0) currentDirection = 1; - - if (HasFrames) - { - // check if the next frame is in the "future" for the current playback direction - if (currentDirection != time.CompareTo(NextFrame.Time)) - { - // if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null. - if (inImportantSection) - return null; - } - else if (advanceFrame()) - { - // If going backwards, we need to execute once _before_ the frame time to reverse any judgements - // that would occur as a result of this frame in forward playback - if (currentDirection == -1) - return CurrentTime = CurrentFrame.Time - 1; - return CurrentTime = CurrentFrame.Time; - } - } - - return CurrentTime = time; - } - - protected class ReplayMouseState : MouseState - { - public ReplayMouseState(Vector2 position) - { - Position = position; - } - } - - protected class ReplayKeyboardState : KeyboardState - { - public ReplayKeyboardState(List keys) - { - Keys = keys; - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Input; +using osu.Game.Input.Handlers; +using OpenTK; +using OpenTK.Input; +using KeyboardState = osu.Framework.Input.KeyboardState; +using MouseState = osu.Framework.Input.MouseState; + +namespace osu.Game.Rulesets.Replays +{ + /// + /// The ReplayHandler will take a replay and handle the propagation of updates to the input stack. + /// It handles logic of any frames which *must* be executed. + /// + public abstract class FramedReplayInputHandler : ReplayInputHandler + where TFrame : ReplayFrame + { + private readonly Replay replay; + + protected List Frames => replay.Frames; + + public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex]; + public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex]; + + private int currentFrameIndex; + + private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1); + + protected FramedReplayInputHandler(Replay replay) + { + this.replay = replay; + } + + private bool advanceFrame() + { + int newFrame = nextFrameIndex; + + //ensure we aren't at an extent. + if (newFrame == currentFrameIndex) return false; + + currentFrameIndex = newFrame; + return true; + } + + public override List GetPendingStates() => new List(); + + public bool AtLastFrame => currentFrameIndex == Frames.Count - 1; + public bool AtFirstFrame => currentFrameIndex == 0; + + private const double sixty_frame_time = 1000.0 / 60; + + protected double CurrentTime { get; private set; } + private int currentDirection; + + /// + /// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data. + /// Disabling this can make replay playback smoother (useful for autoplay, currently). + /// + public bool FrameAccuratePlayback = true; + + protected bool HasFrames => Frames.Count > 0; + + private bool inImportantSection => + HasFrames && FrameAccuratePlayback && + //a button is in a pressed state + IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && + //the next frame is within an allowable time span + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2; + + protected virtual bool IsImportant(TFrame frame) => false; + + /// + /// Update the current frame based on an incoming time value. + /// There are cases where we return a "must-use" time value that is different from the input. + /// This is to ensure accurate playback of replay data. + /// + /// The time which we should use for finding the current frame. + /// The usable time value. If null, we should not advance time as we do not have enough data. + public override double? SetFrameFromTime(double time) + { + currentDirection = time.CompareTo(CurrentTime); + if (currentDirection == 0) currentDirection = 1; + + if (HasFrames) + { + // check if the next frame is in the "future" for the current playback direction + if (currentDirection != time.CompareTo(NextFrame.Time)) + { + // if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null. + if (inImportantSection) + return null; + } + else if (advanceFrame()) + { + // If going backwards, we need to execute once _before_ the frame time to reverse any judgements + // that would occur as a result of this frame in forward playback + if (currentDirection == -1) + return CurrentTime = CurrentFrame.Time - 1; + return CurrentTime = CurrentFrame.Time; + } + } + + return CurrentTime = time; + } + + protected class ReplayMouseState : MouseState + { + public ReplayMouseState(Vector2 position) + { + Position = position; + } + } + + protected class ReplayKeyboardState : KeyboardState + { + public ReplayKeyboardState(List keys) + { + Keys = keys; + } + } + } +} diff --git a/osu.Game/Rulesets/Replays/IAutoGenerator.cs b/osu.Game/Rulesets/Replays/IAutoGenerator.cs index 7e26fb5758..4ef5f16f39 100644 --- a/osu.Game/Rulesets/Replays/IAutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/IAutoGenerator.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Replays -{ - public interface IAutoGenerator - { - Replay Generate(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Replays +{ + public interface IAutoGenerator + { + Replay Generate(); + } +} diff --git a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs index 945cb95e79..d39d765bfe 100644 --- a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; - -namespace osu.Game.Rulesets.Replays.Legacy -{ - public class LegacyReplayFrame : ReplayFrame - { - public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); - - public float? MouseX; - public float? MouseY; - - public bool MouseLeft => MouseLeft1 || MouseLeft2; - public bool MouseRight => MouseRight1 || MouseRight2; - - public bool MouseLeft1 => (ButtonState & ReplayButtonState.Left1) > 0; - public bool MouseRight1 => (ButtonState & ReplayButtonState.Right1) > 0; - public bool MouseLeft2 => (ButtonState & ReplayButtonState.Left2) > 0; - public bool MouseRight2 => (ButtonState & ReplayButtonState.Right2) > 0; - - public ReplayButtonState ButtonState; - - public LegacyReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState) - : base(time) - { - MouseX = mouseX; - MouseY = mouseY; - ButtonState = buttonState; - } - - public override string ToString() - { - return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}"; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; + +namespace osu.Game.Rulesets.Replays.Legacy +{ + public class LegacyReplayFrame : ReplayFrame + { + public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); + + public float? MouseX; + public float? MouseY; + + public bool MouseLeft => MouseLeft1 || MouseLeft2; + public bool MouseRight => MouseRight1 || MouseRight2; + + public bool MouseLeft1 => (ButtonState & ReplayButtonState.Left1) > 0; + public bool MouseRight1 => (ButtonState & ReplayButtonState.Right1) > 0; + public bool MouseLeft2 => (ButtonState & ReplayButtonState.Left2) > 0; + public bool MouseRight2 => (ButtonState & ReplayButtonState.Right2) > 0; + + public ReplayButtonState ButtonState; + + public LegacyReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState) + : base(time) + { + MouseX = mouseX; + MouseY = mouseY; + ButtonState = buttonState; + } + + public override string ToString() + { + return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}"; + } + } +} diff --git a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs b/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs index d0706411d2..ee09414287 100644 --- a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs +++ b/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Rulesets.Replays.Legacy -{ - [Flags] - public enum ReplayButtonState - { - None = 0, - Left1 = 1, - Right1 = 2, - Left2 = 4, - Right2 = 8, - Smoke = 16 - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Rulesets.Replays.Legacy +{ + [Flags] + public enum ReplayButtonState + { + None = 0, + Left1 = 1, + Right1 = 2, + Left2 = 4, + Right2 = 8, + Smoke = 16 + } +} diff --git a/osu.Game/Rulesets/Replays/Replay.cs b/osu.Game/Rulesets/Replays/Replay.cs index a0ea2c5655..8fbe4c8194 100644 --- a/osu.Game/Rulesets/Replays/Replay.cs +++ b/osu.Game/Rulesets/Replays/Replay.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Users; - -namespace osu.Game.Rulesets.Replays -{ - public class Replay - { - public User User; - public List Frames = new List(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Replays +{ + public class Replay + { + public User User; + public List Frames = new List(); + } +} diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs index 61a3646024..fedab8053f 100644 --- a/osu.Game/Rulesets/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Replays -{ - public class ReplayFrame - { - public double Time; - - public ReplayFrame() - { - } - - public ReplayFrame(double time) - { - Time = time; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Replays +{ + public class ReplayFrame + { + public double Time; + + public ReplayFrame() + { + } + + public ReplayFrame(double time) + { + Time = time; + } + } +} diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index 892f5ca2fe..adf35ce078 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Replays.Legacy; - -namespace osu.Game.Rulesets.Replays.Types -{ - /// - /// A type of which can be converted from a . - /// - public interface IConvertibleReplayFrame - { - /// - /// Populates this using values from a . - /// - /// The to extract values from. - /// The beatmap. - void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Replays.Legacy; + +namespace osu.Game.Rulesets.Replays.Types +{ + /// + /// A type of which can be converted from a . + /// + public interface IConvertibleReplayFrame + { + /// + /// Populates this using values from a . + /// + /// The to extract values from. + /// The beatmap. + void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap); + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index cba849a491..c2af4d566c 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -1,112 +1,112 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets -{ - public abstract class Ruleset - { - public readonly RulesetInfo RulesetInfo; - - public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { }; - - public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() - // Confine all mods of each mod type into a single IEnumerable - .SelectMany(GetModsFor) - // Filter out all null mods - .Where(mod => mod != null) - // Resolve MultiMods as their .Mods property - .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); - - public abstract IEnumerable GetModsFor(ModType type); - - public Mod GetAutoplayMod() => GetAllMods().First(mod => mod is ModAutoplay); - - protected Ruleset(RulesetInfo rulesetInfo = null) - { - RulesetInfo = rulesetInfo ?? createRulesetInfo(); - } - - /// - /// Attempt to create a hit renderer for a beatmap - /// - /// The beatmap to create the hit renderer for. - /// Whether the hit renderer should assume the beatmap is for the current ruleset. - /// Unable to successfully load the beatmap to be usable with this ruleset. - /// - public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset); - - public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null); - - public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null; - - public virtual HitObjectComposer CreateHitObjectComposer() => null; - - public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; - - public abstract string Description { get; } - - public virtual SettingsSubsection CreateSettings() => null; - - /// - /// Do not override this unless you are a legacy mode. - /// - public virtual int? LegacyID => null; - - /// - /// A unique short name to reference this ruleset in online requests. - /// - public abstract string ShortName { get; } - - /// - /// A list of available variant ids. - /// - public virtual IEnumerable AvailableVariants => new[] { 0 }; - - /// - /// Get a list of default keys for the specified variant. - /// - /// A variant. - /// A list of valid s. - public virtual IEnumerable GetDefaultKeyBindings(int variant = 0) => new KeyBinding[] { }; - - /// - /// Gets the name for a key binding variant. This is used for display in the settings overlay. - /// - /// The variant. - /// A descriptive name of the variant. - public virtual string GetVariantName(int variant) => string.Empty; - - /// - /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame - /// for conversion use. - /// - /// An empty frame for the current ruleset, or null if unsupported. - public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; - - /// - /// Create a ruleset info based on this ruleset. - /// - /// A filled . - private RulesetInfo createRulesetInfo() => new RulesetInfo - { - Name = Description, - ShortName = ShortName, - InstantiationInfo = GetType().AssemblyQualifiedName, - ID = LegacyID - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets +{ + public abstract class Ruleset + { + public readonly RulesetInfo RulesetInfo; + + public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { }; + + public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() + // Confine all mods of each mod type into a single IEnumerable + .SelectMany(GetModsFor) + // Filter out all null mods + .Where(mod => mod != null) + // Resolve MultiMods as their .Mods property + .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); + + public abstract IEnumerable GetModsFor(ModType type); + + public Mod GetAutoplayMod() => GetAllMods().First(mod => mod is ModAutoplay); + + protected Ruleset(RulesetInfo rulesetInfo = null) + { + RulesetInfo = rulesetInfo ?? createRulesetInfo(); + } + + /// + /// Attempt to create a hit renderer for a beatmap + /// + /// The beatmap to create the hit renderer for. + /// Whether the hit renderer should assume the beatmap is for the current ruleset. + /// Unable to successfully load the beatmap to be usable with this ruleset. + /// + public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset); + + public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null); + + public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null; + + public virtual HitObjectComposer CreateHitObjectComposer() => null; + + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; + + public abstract string Description { get; } + + public virtual SettingsSubsection CreateSettings() => null; + + /// + /// Do not override this unless you are a legacy mode. + /// + public virtual int? LegacyID => null; + + /// + /// A unique short name to reference this ruleset in online requests. + /// + public abstract string ShortName { get; } + + /// + /// A list of available variant ids. + /// + public virtual IEnumerable AvailableVariants => new[] { 0 }; + + /// + /// Get a list of default keys for the specified variant. + /// + /// A variant. + /// A list of valid s. + public virtual IEnumerable GetDefaultKeyBindings(int variant = 0) => new KeyBinding[] { }; + + /// + /// Gets the name for a key binding variant. This is used for display in the settings overlay. + /// + /// The variant. + /// A descriptive name of the variant. + public virtual string GetVariantName(int variant) => string.Empty; + + /// + /// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame + /// for conversion use. + /// + /// An empty frame for the current ruleset, or null if unsupported. + public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; + + /// + /// Create a ruleset info based on this ruleset. + /// + /// A filled . + private RulesetInfo createRulesetInfo() => new RulesetInfo + { + Name = Description, + ShortName = ShortName, + InstantiationInfo = GetType().AssemblyQualifiedName, + ID = LegacyID + }; + } +} diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 43da3670b0..10463fd961 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; - -namespace osu.Game.Rulesets -{ - public class RulesetInfo : IEquatable - { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] - public int? ID { get; set; } - - public string Name { get; set; } - - public string ShortName { get; set; } - - public string InstantiationInfo { get; set; } - - [JsonIgnore] - public bool Available { get; set; } - - public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); - - public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace osu.Game.Rulesets +{ + public class RulesetInfo : IEquatable + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] + public int? ID { get; set; } + + public string Name { get; set; } + + public string ShortName { get; set; } + + public string InstantiationInfo { get; set; } + + [JsonIgnore] + public bool Available { get; set; } + + public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); + + public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index e621c3cf2b..67a9a59d4a 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -1,122 +1,122 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using osu.Game.Database; - -namespace osu.Game.Rulesets -{ - /// - /// Todo: All of this needs to be moved to a RulesetStore. - /// - public class RulesetStore : DatabaseBackedStore - { - private static readonly Dictionary loaded_assemblies = new Dictionary(); - - static RulesetStore() - { - AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; - - foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll")) - loadRulesetFromFile(file); - } - - public RulesetStore(IDatabaseContextFactory factory) - : base(factory) - { - AddMissingRulesets(); - } - - /// - /// Retrieve a ruleset using a known ID. - /// - /// The ruleset's internal ID. - /// A ruleset, if available, else null. - public RulesetInfo GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.ID == id); - - /// - /// Retrieve a ruleset using a known short name. - /// - /// The ruleset's short name. - /// A ruleset, if available, else null. - public RulesetInfo GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); - - /// - /// All available rulesets. - /// - public IEnumerable AvailableRulesets; - - private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); - - private const string ruleset_library_prefix = "osu.Game.Rulesets"; - - protected void AddMissingRulesets() - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); - - //add all legacy modes in correct order - foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) - { - if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == r.RulesetInfo.ID) == null) - context.RulesetInfo.Add(r.RulesetInfo); - } - - context.SaveChanges(); - - //add any other modes - foreach (var r in instances.Where(r => r.LegacyID == null)) - if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null) - context.RulesetInfo.Add(r.RulesetInfo); - - context.SaveChanges(); - - //perform a consistency check - foreach (var r in context.RulesetInfo) - { - try - { - var instance = r.CreateInstance(); - - r.Name = instance.Description; - r.ShortName = instance.ShortName; - - r.Available = true; - } - catch - { - r.Available = false; - } - } - - context.SaveChanges(); - - AvailableRulesets = context.RulesetInfo.Where(r => r.Available).ToList(); - } - } - - private static void loadRulesetFromFile(string file) - { - var filename = Path.GetFileNameWithoutExtension(file); - - if (loaded_assemblies.Values.Any(t => t.Namespace == filename)) - return; - - try - { - var assembly = Assembly.LoadFrom(file); - loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset))); - } - catch (Exception) - { - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using osu.Game.Database; + +namespace osu.Game.Rulesets +{ + /// + /// Todo: All of this needs to be moved to a RulesetStore. + /// + public class RulesetStore : DatabaseBackedStore + { + private static readonly Dictionary loaded_assemblies = new Dictionary(); + + static RulesetStore() + { + AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; + + foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll")) + loadRulesetFromFile(file); + } + + public RulesetStore(IDatabaseContextFactory factory) + : base(factory) + { + AddMissingRulesets(); + } + + /// + /// Retrieve a ruleset using a known ID. + /// + /// The ruleset's internal ID. + /// A ruleset, if available, else null. + public RulesetInfo GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.ID == id); + + /// + /// Retrieve a ruleset using a known short name. + /// + /// The ruleset's short name. + /// A ruleset, if available, else null. + public RulesetInfo GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName); + + /// + /// All available rulesets. + /// + public IEnumerable AvailableRulesets; + + private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); + + private const string ruleset_library_prefix = "osu.Game.Rulesets"; + + protected void AddMissingRulesets() + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList(); + + //add all legacy modes in correct order + foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID)) + { + if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == r.RulesetInfo.ID) == null) + context.RulesetInfo.Add(r.RulesetInfo); + } + + context.SaveChanges(); + + //add any other modes + foreach (var r in instances.Where(r => r.LegacyID == null)) + if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null) + context.RulesetInfo.Add(r.RulesetInfo); + + context.SaveChanges(); + + //perform a consistency check + foreach (var r in context.RulesetInfo) + { + try + { + var instance = r.CreateInstance(); + + r.Name = instance.Description; + r.ShortName = instance.ShortName; + + r.Available = true; + } + catch + { + r.Available = false; + } + } + + context.SaveChanges(); + + AvailableRulesets = context.RulesetInfo.Where(r => r.Available).ToList(); + } + } + + private static void loadRulesetFromFile(string file) + { + var filename = Path.GetFileNameWithoutExtension(file); + + if (loaded_assemblies.Values.Any(t => t.Namespace == filename)) + return; + + try + { + var assembly = Assembly.LoadFrom(file); + loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset))); + } + catch (Exception) + { + } + } + } +} diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 05f3b82810..98874a4890 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Rulesets.Scoring -{ - public enum HitResult - { - /// - /// Indicates that the object has not been judged yet. - /// - [Description(@"")] - None, - - /// - /// Indicates that the object has been judged as a miss. - /// - [Description(@"Miss")] - Miss, - - [Description(@"Meh")] - Meh, - - /// - /// Optional judgement. - /// - [Description(@"OK")] - Ok, - - [Description(@"Good")] - Good, - - [Description(@"Great")] - Great, - - /// - /// Optional judgement. - /// - [Description(@"Perfect")] - Perfect, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Rulesets.Scoring +{ + public enum HitResult + { + /// + /// Indicates that the object has not been judged yet. + /// + [Description(@"")] + None, + + /// + /// Indicates that the object has been judged as a miss. + /// + [Description(@"Miss")] + Miss, + + [Description(@"Meh")] + Meh, + + /// + /// Optional judgement. + /// + [Description(@"OK")] + Ok, + + [Description(@"Good")] + Good, + + [Description(@"Great")] + Great, + + /// + /// Optional judgement. + /// + [Description(@"Perfect")] + Perfect, + } +} diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs index 9ebb62a368..5ee009ba98 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs @@ -1,152 +1,152 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using osu.Game.Beatmaps; -using osu.Game.IO.Legacy; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Users; -using SharpCompress.Compressors.LZMA; - -namespace osu.Game.Rulesets.Scoring.Legacy -{ - public class LegacyScoreParser - { - private readonly RulesetStore rulesets; - private readonly BeatmapManager beatmaps; - - public LegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps) - { - this.rulesets = rulesets; - this.beatmaps = beatmaps; - } - - private Beatmap currentBeatmap; - private Ruleset currentRuleset; - - public Score Parse(Stream stream) - { - Score score; - - using (SerializationReader sr = new SerializationReader(stream)) - { - score = new Score { Ruleset = rulesets.GetRuleset(sr.ReadByte()) }; - currentRuleset = score.Ruleset.CreateInstance(); - - /* score.Pass = true;*/ - var version = sr.ReadInt32(); - - /* score.FileChecksum = */ - var beatmapHash = sr.ReadString(); - score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash); - currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap; - - /* score.PlayerName = */ - score.User = new User { Username = sr.ReadString() }; - /* var localScoreChecksum = */ - sr.ReadString(); - /* score.Count300 = */ - sr.ReadUInt16(); - /* score.Count100 = */ - sr.ReadUInt16(); - /* score.Count50 = */ - sr.ReadUInt16(); - /* score.CountGeki = */ - sr.ReadUInt16(); - /* score.CountKatu = */ - sr.ReadUInt16(); - /* score.CountMiss = */ - sr.ReadUInt16(); - score.TotalScore = sr.ReadInt32(); - score.MaxCombo = sr.ReadUInt16(); - /* score.Perfect = */ - sr.ReadBoolean(); - /* score.EnabledMods = (Mods)*/ - sr.ReadInt32(); - /* score.HpGraphString = */ - sr.ReadString(); - /* score.Date = */ - sr.ReadDateTime(); - - var compressedReplay = sr.ReadByteArray(); - - if (version >= 20140721) - /*OnlineId =*/ - sr.ReadInt64(); - else if (version >= 20121008) - /*OnlineId =*/ - sr.ReadInt32(); - - using (var replayInStream = new MemoryStream(compressedReplay)) - { - byte[] properties = new byte[5]; - if (replayInStream.Read(properties, 0, 5) != 5) - throw new IOException("input .lzma is too short"); - long outSize = 0; - for (int i = 0; i < 8; i++) - { - int v = replayInStream.ReadByte(); - if (v < 0) - throw new IOException("Can't Read 1"); - outSize |= (long)(byte)v << (8 * i); - } - - long compressedSize = replayInStream.Length - replayInStream.Position; - - using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) - using (var reader = new StreamReader(lzma)) - { - score.Replay = new Replay { User = score.User }; - readLegacyReplay(score.Replay, reader); - } - } - } - - return score; - } - - private void readLegacyReplay(Replay replay, StreamReader reader) - { - float lastTime = 0; - - foreach (var l in reader.ReadToEnd().Split(',')) - { - var split = l.Split('|'); - - if (split.Length < 4) - continue; - - if (split[0] == "-12345") - { - // Todo: The seed is provided in split[3], which we'll need to use at some point - continue; - } - - var diff = float.Parse(split[0]); - lastTime += diff; - - // Todo: At some point we probably want to rewind and play back the negative-time frames - // but for now we'll achieve equal playback to stable by skipping negative frames - if (diff < 0) - continue; - - replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); - } - } - - private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) - { - var convertible = currentRuleset.CreateConvertibleReplayFrame(); - if (convertible == null) - throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); - convertible.ConvertFrom(legacyFrame, currentBeatmap); - - var frame = (ReplayFrame)convertible; - frame.Time = legacyFrame.Time; - - return frame; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using osu.Game.Beatmaps; +using osu.Game.IO.Legacy; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Users; +using SharpCompress.Compressors.LZMA; + +namespace osu.Game.Rulesets.Scoring.Legacy +{ + public class LegacyScoreParser + { + private readonly RulesetStore rulesets; + private readonly BeatmapManager beatmaps; + + public LegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps) + { + this.rulesets = rulesets; + this.beatmaps = beatmaps; + } + + private Beatmap currentBeatmap; + private Ruleset currentRuleset; + + public Score Parse(Stream stream) + { + Score score; + + using (SerializationReader sr = new SerializationReader(stream)) + { + score = new Score { Ruleset = rulesets.GetRuleset(sr.ReadByte()) }; + currentRuleset = score.Ruleset.CreateInstance(); + + /* score.Pass = true;*/ + var version = sr.ReadInt32(); + + /* score.FileChecksum = */ + var beatmapHash = sr.ReadString(); + score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash); + currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap; + + /* score.PlayerName = */ + score.User = new User { Username = sr.ReadString() }; + /* var localScoreChecksum = */ + sr.ReadString(); + /* score.Count300 = */ + sr.ReadUInt16(); + /* score.Count100 = */ + sr.ReadUInt16(); + /* score.Count50 = */ + sr.ReadUInt16(); + /* score.CountGeki = */ + sr.ReadUInt16(); + /* score.CountKatu = */ + sr.ReadUInt16(); + /* score.CountMiss = */ + sr.ReadUInt16(); + score.TotalScore = sr.ReadInt32(); + score.MaxCombo = sr.ReadUInt16(); + /* score.Perfect = */ + sr.ReadBoolean(); + /* score.EnabledMods = (Mods)*/ + sr.ReadInt32(); + /* score.HpGraphString = */ + sr.ReadString(); + /* score.Date = */ + sr.ReadDateTime(); + + var compressedReplay = sr.ReadByteArray(); + + if (version >= 20140721) + /*OnlineId =*/ + sr.ReadInt64(); + else if (version >= 20121008) + /*OnlineId =*/ + sr.ReadInt32(); + + using (var replayInStream = new MemoryStream(compressedReplay)) + { + byte[] properties = new byte[5]; + if (replayInStream.Read(properties, 0, 5) != 5) + throw new IOException("input .lzma is too short"); + long outSize = 0; + for (int i = 0; i < 8; i++) + { + int v = replayInStream.ReadByte(); + if (v < 0) + throw new IOException("Can't Read 1"); + outSize |= (long)(byte)v << (8 * i); + } + + long compressedSize = replayInStream.Length - replayInStream.Position; + + using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) + using (var reader = new StreamReader(lzma)) + { + score.Replay = new Replay { User = score.User }; + readLegacyReplay(score.Replay, reader); + } + } + } + + return score; + } + + private void readLegacyReplay(Replay replay, StreamReader reader) + { + float lastTime = 0; + + foreach (var l in reader.ReadToEnd().Split(',')) + { + var split = l.Split('|'); + + if (split.Length < 4) + continue; + + if (split[0] == "-12345") + { + // Todo: The seed is provided in split[3], which we'll need to use at some point + continue; + } + + var diff = float.Parse(split[0]); + lastTime += diff; + + // Todo: At some point we probably want to rewind and play back the negative-time frames + // but for now we'll achieve equal playback to stable by skipping negative frames + if (diff < 0) + continue; + + replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); + } + } + + private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) + { + var convertible = currentRuleset.CreateConvertibleReplayFrame(); + if (convertible == null) + throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); + convertible.ConvertFrom(legacyFrame, currentBeatmap); + + var frame = (ReplayFrame)convertible; + frame.Time = legacyFrame.Time; + + return frame; + } + } +} diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs index c047a421fd..0f115fff6b 100644 --- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs @@ -1,43 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.Scoring -{ - public abstract class PerformanceCalculator - { - public abstract double Calculate(Dictionary categoryDifficulty = null); - } - - public abstract class PerformanceCalculator : PerformanceCalculator - where TObject : HitObject - { - private readonly Dictionary attributes = new Dictionary(); - protected IDictionary Attributes => attributes; - - protected readonly Beatmap Beatmap; - protected readonly Score Score; - - protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score) - { - Score = score; - - var converter = CreateBeatmapConverter(); - - foreach (var mod in score.Mods.OfType>()) - mod.ApplyToBeatmapConverter(converter); - - Beatmap = converter.Convert(beatmap); - - var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods); - diffCalc.Calculate(attributes); - } - - protected abstract BeatmapConverter CreateBeatmapConverter(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Scoring +{ + public abstract class PerformanceCalculator + { + public abstract double Calculate(Dictionary categoryDifficulty = null); + } + + public abstract class PerformanceCalculator : PerformanceCalculator + where TObject : HitObject + { + private readonly Dictionary attributes = new Dictionary(); + protected IDictionary Attributes => attributes; + + protected readonly Beatmap Beatmap; + protected readonly Score Score; + + protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score) + { + Score = score; + + var converter = CreateBeatmapConverter(); + + foreach (var mod in score.Mods.OfType>()) + mod.ApplyToBeatmapConverter(converter); + + Beatmap = converter.Convert(beatmap); + + var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods); + diffCalc.Calculate(attributes); + } + + protected abstract BeatmapConverter CreateBeatmapConverter(); + } +} diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index ed79333a16..dfe7ff0195 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -1,45 +1,45 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Users; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Scoring -{ - public class Score - { - public ScoreRank Rank { get; set; } - - public double TotalScore { get; set; } - - public double Accuracy { get; set; } - - public double Health { get; set; } = 1; - - public double? PP { get; set; } - - public int MaxCombo { get; set; } - - public int Combo { get; set; } - - public RulesetInfo Ruleset { get; set; } - - public Mod[] Mods { get; set; } = { }; - - public User User; - - public Replay Replay; - - public BeatmapInfo Beatmap; - - public long OnlineScoreID; - - public DateTimeOffset Date; - - public Dictionary Statistics = new Dictionary(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Users; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Scoring +{ + public class Score + { + public ScoreRank Rank { get; set; } + + public double TotalScore { get; set; } + + public double Accuracy { get; set; } + + public double Health { get; set; } = 1; + + public double? PP { get; set; } + + public int MaxCombo { get; set; } + + public int Combo { get; set; } + + public RulesetInfo Ruleset { get; set; } + + public Mod[] Mods { get; set; } = { }; + + public User User; + + public Replay Replay; + + public BeatmapInfo Beatmap; + + public long OnlineScoreID; + + public DateTimeOffset Date; + + public Dictionary Statistics = new Dictionary(); + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e8271059ca..345930ed04 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -1,327 +1,327 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Diagnostics; -using osu.Framework.Configuration; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Scoring -{ - public abstract class ScoreProcessor - { - /// - /// Invoked when the is in a failed state. - /// This may occur regardless of whether an event is invoked. - /// Return true if the fail was permitted. - /// - public event Func Failed; - - /// - /// Invoked when all s have been judged. - /// - public event Action AllJudged; - - /// - /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the . - /// - public event Action NewJudgement; - - /// - /// Additional conditions on top of that cause a failing state. - /// - public event Func FailConditions; - - /// - /// The current total score. - /// - public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 }; - - /// - /// The current accuracy. - /// - public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; - - /// - /// The current health. - /// - public readonly BindableDouble Health = new BindableDouble { MinValue = 0, MaxValue = 1 }; - - /// - /// The current combo. - /// - public readonly BindableInt Combo = new BindableInt(); - - /// - /// The current rank. - /// - public readonly Bindable Rank = new Bindable(ScoreRank.X); - - /// - /// THe highest combo achieved by this score. - /// - public readonly BindableInt HighestCombo = new BindableInt(); - - /// - /// Whether all s have been processed. - /// - protected virtual bool HasCompleted => false; - - /// - /// Whether this ScoreProcessor has already triggered the failed state. - /// - public virtual bool HasFailed { get; private set; } - - /// - /// The default conditions for failing. - /// - protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; - - protected ScoreProcessor() - { - Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; - } - - private ScoreRank rankFrom(double acc) - { - if (acc == 1) - return ScoreRank.X; - if (acc > 0.95) - return ScoreRank.S; - if (acc > 0.9) - return ScoreRank.A; - if (acc > 0.8) - return ScoreRank.B; - if (acc > 0.7) - return ScoreRank.C; - return ScoreRank.D; - } - - /// - /// Resets this ScoreProcessor to a default state. - /// - /// Whether to store the current state of the for future use. - protected virtual void Reset(bool storeResults) - { - TotalScore.Value = 0; - Accuracy.Value = 1; - Health.Value = 1; - Combo.Value = 0; - Rank.Value = ScoreRank.X; - HighestCombo.Value = 0; - - HasFailed = false; - } - - /// - /// Checks if the score is in a failed state and notifies subscribers. - /// - /// This can only ever notify subscribers once. - /// - /// - protected void UpdateFailed() - { - if (HasFailed) - return; - - if (!DefaultFailCondition && FailConditions?.Invoke(this) != true) - return; - - if (Failed?.Invoke() != false) - HasFailed = true; - } - - /// - /// Notifies subscribers of that a new judgement has occurred. - /// - /// The judgement to notify subscribers of. - protected void NotifyNewJudgement(Judgement judgement) - { - NewJudgement?.Invoke(judgement); - - if (HasCompleted) - AllJudged?.Invoke(); - } - - /// - /// Retrieve a score populated with data for the current play this processor is responsible for. - /// - public virtual void PopulateScore(Score score) - { - score.TotalScore = TotalScore; - score.Combo = Combo; - score.MaxCombo = HighestCombo; - score.Accuracy = Accuracy; - score.Rank = Rank; - score.Date = DateTimeOffset.Now; - score.Health = Health; - } - } - - public class ScoreProcessor : ScoreProcessor - where TObject : HitObject - { - private const double base_portion = 0.3; - private const double combo_portion = 0.7; - private const double max_score = 1000000; - - public readonly Bindable Mode = new Bindable(); - - protected sealed override bool HasCompleted => JudgedHits == MaxHits; - - protected int MaxHits { get; private set; } - protected int JudgedHits { get; private set; } - - private double maxHighestCombo; - - private double maxBaseScore; - private double rollingMaxBaseScore; - private double baseScore; - private double bonusScore; - - protected ScoreProcessor() - { - } - - public ScoreProcessor(RulesetContainer rulesetContainer) - { - Debug.Assert(base_portion + combo_portion == 1.0); - - rulesetContainer.OnJudgement += AddJudgement; - rulesetContainer.OnJudgementRemoved += RemoveJudgement; - - SimulateAutoplay(rulesetContainer.Beatmap); - Reset(true); - - if (maxBaseScore == 0 || maxHighestCombo == 0) - { - Mode.Value = ScoringMode.Exponential; - Mode.Disabled = true; - } - } - - /// - /// Simulates an autoplay of s that will be judged by this - /// by adding s for each in the . - /// - /// This is required for to work, otherwise will be used. - /// - /// - /// The containing the s that will be judged by this . - protected virtual void SimulateAutoplay(Beatmap beatmap) { } - - /// - /// Adds a judgement to this ScoreProcessor. - /// - /// The judgement to add. - protected void AddJudgement(Judgement judgement) - { - OnNewJudgement(judgement); - updateScore(); - - UpdateFailed(); - NotifyNewJudgement(judgement); - } - - protected void RemoveJudgement(Judgement judgement) - { - OnJudgementRemoved(judgement); - updateScore(); - } - - /// - /// Applies a judgement. - /// - /// The judgement to apply/ - protected virtual void OnNewJudgement(Judgement judgement) - { - judgement.ComboAtJudgement = Combo; - judgement.HighestComboAtJudgement = HighestCombo; - - if (judgement.AffectsCombo) - { - switch (judgement.Result) - { - case HitResult.None: - break; - case HitResult.Miss: - Combo.Value = 0; - break; - default: - Combo.Value++; - break; - } - - baseScore += judgement.NumericResult; - rollingMaxBaseScore += judgement.MaxNumericResult; - - JudgedHits++; - } - else if (judgement.IsHit) - bonusScore += judgement.NumericResult; - } - - /// - /// Removes a judgement. This should reverse everything in . - /// - /// The judgement to remove. - protected virtual void OnJudgementRemoved(Judgement judgement) - { - Combo.Value = judgement.ComboAtJudgement; - HighestCombo.Value = judgement.HighestComboAtJudgement; - - if (judgement.AffectsCombo) - { - baseScore -= judgement.NumericResult; - rollingMaxBaseScore -= judgement.MaxNumericResult; - - JudgedHits--; - } - else if (judgement.IsHit) - bonusScore -= judgement.NumericResult; - } - - private void updateScore() - { - if (rollingMaxBaseScore != 0) - Accuracy.Value = baseScore / rollingMaxBaseScore; - - switch (Mode.Value) - { - case ScoringMode.Standardised: - TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore; - break; - case ScoringMode.Exponential: - TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2); - break; - } - } - - protected override void Reset(bool storeResults) - { - if (storeResults) - { - MaxHits = JudgedHits; - maxHighestCombo = HighestCombo; - maxBaseScore = baseScore; - } - - base.Reset(storeResults); - - JudgedHits = 0; - baseScore = 0; - rollingMaxBaseScore = 0; - bonusScore = 0; - } - } - - public enum ScoringMode - { - Standardised, - Exponential - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Diagnostics; +using osu.Framework.Configuration; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Scoring +{ + public abstract class ScoreProcessor + { + /// + /// Invoked when the is in a failed state. + /// This may occur regardless of whether an event is invoked. + /// Return true if the fail was permitted. + /// + public event Func Failed; + + /// + /// Invoked when all s have been judged. + /// + public event Action AllJudged; + + /// + /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the . + /// + public event Action NewJudgement; + + /// + /// Additional conditions on top of that cause a failing state. + /// + public event Func FailConditions; + + /// + /// The current total score. + /// + public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 }; + + /// + /// The current accuracy. + /// + public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; + + /// + /// The current health. + /// + public readonly BindableDouble Health = new BindableDouble { MinValue = 0, MaxValue = 1 }; + + /// + /// The current combo. + /// + public readonly BindableInt Combo = new BindableInt(); + + /// + /// The current rank. + /// + public readonly Bindable Rank = new Bindable(ScoreRank.X); + + /// + /// THe highest combo achieved by this score. + /// + public readonly BindableInt HighestCombo = new BindableInt(); + + /// + /// Whether all s have been processed. + /// + protected virtual bool HasCompleted => false; + + /// + /// Whether this ScoreProcessor has already triggered the failed state. + /// + public virtual bool HasFailed { get; private set; } + + /// + /// The default conditions for failing. + /// + protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; + + protected ScoreProcessor() + { + Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; + Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; + } + + private ScoreRank rankFrom(double acc) + { + if (acc == 1) + return ScoreRank.X; + if (acc > 0.95) + return ScoreRank.S; + if (acc > 0.9) + return ScoreRank.A; + if (acc > 0.8) + return ScoreRank.B; + if (acc > 0.7) + return ScoreRank.C; + return ScoreRank.D; + } + + /// + /// Resets this ScoreProcessor to a default state. + /// + /// Whether to store the current state of the for future use. + protected virtual void Reset(bool storeResults) + { + TotalScore.Value = 0; + Accuracy.Value = 1; + Health.Value = 1; + Combo.Value = 0; + Rank.Value = ScoreRank.X; + HighestCombo.Value = 0; + + HasFailed = false; + } + + /// + /// Checks if the score is in a failed state and notifies subscribers. + /// + /// This can only ever notify subscribers once. + /// + /// + protected void UpdateFailed() + { + if (HasFailed) + return; + + if (!DefaultFailCondition && FailConditions?.Invoke(this) != true) + return; + + if (Failed?.Invoke() != false) + HasFailed = true; + } + + /// + /// Notifies subscribers of that a new judgement has occurred. + /// + /// The judgement to notify subscribers of. + protected void NotifyNewJudgement(Judgement judgement) + { + NewJudgement?.Invoke(judgement); + + if (HasCompleted) + AllJudged?.Invoke(); + } + + /// + /// Retrieve a score populated with data for the current play this processor is responsible for. + /// + public virtual void PopulateScore(Score score) + { + score.TotalScore = TotalScore; + score.Combo = Combo; + score.MaxCombo = HighestCombo; + score.Accuracy = Accuracy; + score.Rank = Rank; + score.Date = DateTimeOffset.Now; + score.Health = Health; + } + } + + public class ScoreProcessor : ScoreProcessor + where TObject : HitObject + { + private const double base_portion = 0.3; + private const double combo_portion = 0.7; + private const double max_score = 1000000; + + public readonly Bindable Mode = new Bindable(); + + protected sealed override bool HasCompleted => JudgedHits == MaxHits; + + protected int MaxHits { get; private set; } + protected int JudgedHits { get; private set; } + + private double maxHighestCombo; + + private double maxBaseScore; + private double rollingMaxBaseScore; + private double baseScore; + private double bonusScore; + + protected ScoreProcessor() + { + } + + public ScoreProcessor(RulesetContainer rulesetContainer) + { + Debug.Assert(base_portion + combo_portion == 1.0); + + rulesetContainer.OnJudgement += AddJudgement; + rulesetContainer.OnJudgementRemoved += RemoveJudgement; + + SimulateAutoplay(rulesetContainer.Beatmap); + Reset(true); + + if (maxBaseScore == 0 || maxHighestCombo == 0) + { + Mode.Value = ScoringMode.Exponential; + Mode.Disabled = true; + } + } + + /// + /// Simulates an autoplay of s that will be judged by this + /// by adding s for each in the . + /// + /// This is required for to work, otherwise will be used. + /// + /// + /// The containing the s that will be judged by this . + protected virtual void SimulateAutoplay(Beatmap beatmap) { } + + /// + /// Adds a judgement to this ScoreProcessor. + /// + /// The judgement to add. + protected void AddJudgement(Judgement judgement) + { + OnNewJudgement(judgement); + updateScore(); + + UpdateFailed(); + NotifyNewJudgement(judgement); + } + + protected void RemoveJudgement(Judgement judgement) + { + OnJudgementRemoved(judgement); + updateScore(); + } + + /// + /// Applies a judgement. + /// + /// The judgement to apply/ + protected virtual void OnNewJudgement(Judgement judgement) + { + judgement.ComboAtJudgement = Combo; + judgement.HighestComboAtJudgement = HighestCombo; + + if (judgement.AffectsCombo) + { + switch (judgement.Result) + { + case HitResult.None: + break; + case HitResult.Miss: + Combo.Value = 0; + break; + default: + Combo.Value++; + break; + } + + baseScore += judgement.NumericResult; + rollingMaxBaseScore += judgement.MaxNumericResult; + + JudgedHits++; + } + else if (judgement.IsHit) + bonusScore += judgement.NumericResult; + } + + /// + /// Removes a judgement. This should reverse everything in . + /// + /// The judgement to remove. + protected virtual void OnJudgementRemoved(Judgement judgement) + { + Combo.Value = judgement.ComboAtJudgement; + HighestCombo.Value = judgement.HighestComboAtJudgement; + + if (judgement.AffectsCombo) + { + baseScore -= judgement.NumericResult; + rollingMaxBaseScore -= judgement.MaxNumericResult; + + JudgedHits--; + } + else if (judgement.IsHit) + bonusScore -= judgement.NumericResult; + } + + private void updateScore() + { + if (rollingMaxBaseScore != 0) + Accuracy.Value = baseScore / rollingMaxBaseScore; + + switch (Mode.Value) + { + case ScoringMode.Standardised: + TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore; + break; + case ScoringMode.Exponential: + TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2); + break; + } + } + + protected override void Reset(bool storeResults) + { + if (storeResults) + { + MaxHits = JudgedHits; + maxHighestCombo = HighestCombo; + maxBaseScore = baseScore; + } + + base.Reset(storeResults); + + JudgedHits = 0; + baseScore = 0; + rollingMaxBaseScore = 0; + bonusScore = 0; + } + } + + public enum ScoringMode + { + Standardised, + Exponential + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreRank.cs b/osu.Game/Rulesets/Scoring/ScoreRank.cs index 5df52ddf95..d4113bb08a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreRank.cs +++ b/osu.Game/Rulesets/Scoring/ScoreRank.cs @@ -1,29 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Rulesets.Scoring -{ - public enum ScoreRank - { - [Description(@"F")] - F, - [Description(@"F")] - D, - [Description(@"C")] - C, - [Description(@"B")] - B, - [Description(@"A")] - A, - [Description(@"S")] - S, - [Description(@"SPlus")] - SH, - [Description(@"SS")] - X, - [Description(@"SSPlus")] - XH, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Rulesets.Scoring +{ + public enum ScoreRank + { + [Description(@"F")] + F, + [Description(@"F")] + D, + [Description(@"C")] + C, + [Description(@"B")] + B, + [Description(@"A")] + A, + [Description(@"S")] + S, + [Description(@"SPlus")] + SH, + [Description(@"SS")] + X, + [Description(@"SSPlus")] + XH, + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index cb2b76cdcf..957fd037e0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -1,56 +1,56 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.IPC; -using osu.Game.Rulesets.Scoring.Legacy; - -namespace osu.Game.Rulesets.Scoring -{ - public class ScoreStore : DatabaseBackedStore, ICanAcceptFiles - { - private readonly Storage storage; - - private readonly BeatmapManager beatmaps; - private readonly RulesetStore rulesets; - - private const string replay_folder = @"replays"; - - public event Action ScoreImported; - - // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) - private ScoreIPCChannel ipc; - - public ScoreStore(Storage storage, DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory) - { - this.storage = storage; - this.beatmaps = beatmaps; - this.rulesets = rulesets; - - if (importHost != null) - ipc = new ScoreIPCChannel(importHost, this); - } - - public string[] HandledExtensions => new[] { ".osr" }; - - public void Import(params string[] paths) - { - foreach (var path in paths) - { - var score = ReadReplayFile(path); - if (score != null) - ScoreImported?.Invoke(score); - } - } - - public Score ReadReplayFile(string replayFilename) - { - using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename))) - return new LegacyScoreParser(rulesets, beatmaps).Parse(s); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IPC; +using osu.Game.Rulesets.Scoring.Legacy; + +namespace osu.Game.Rulesets.Scoring +{ + public class ScoreStore : DatabaseBackedStore, ICanAcceptFiles + { + private readonly Storage storage; + + private readonly BeatmapManager beatmaps; + private readonly RulesetStore rulesets; + + private const string replay_folder = @"replays"; + + public event Action ScoreImported; + + // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) + private ScoreIPCChannel ipc; + + public ScoreStore(Storage storage, DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory) + { + this.storage = storage; + this.beatmaps = beatmaps; + this.rulesets = rulesets; + + if (importHost != null) + ipc = new ScoreIPCChannel(importHost, this); + } + + public string[] HandledExtensions => new[] { ".osr" }; + + public void Import(params string[] paths) + { + foreach (var path in paths) + { + var score = ReadReplayFile(path); + if (score != null) + ScoreImported?.Invoke(score); + } + } + + public Score ReadReplayFile(string replayFilename) + { + using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename))) + return new LegacyScoreParser(rulesets, beatmaps).Parse(s); + } + } +} diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 450b5da7b8..a5bd6bfde7 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -1,66 +1,66 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.IO.Serialization; - -namespace osu.Game.Rulesets.Timing -{ - /// - /// A control point which adds an aggregated multiplier based on the provided 's BeatLength and 's SpeedMultiplier. - /// - public class MultiplierControlPoint : IJsonSerializable, IComparable - { - /// - /// The time in milliseconds at which this starts. - /// - public double StartTime; - - /// - /// The multiplier which this provides. - /// - public double Multiplier => 1000 / TimingPoint.BeatLength * DifficultyPoint.SpeedMultiplier; - - /// - /// The that provides the timing information for this . - /// - public TimingControlPoint TimingPoint = new TimingControlPoint(); - - /// - /// The that provides additional difficulty information for this . - /// - public DifficultyControlPoint DifficultyPoint = new DifficultyControlPoint(); - - /// - /// Creates a . This is required for JSON serialization - /// - public MultiplierControlPoint() - { - } - - /// - /// Creates a . - /// - /// The start time of this . - public MultiplierControlPoint(double startTime) - { - StartTime = startTime; - } - - /// - /// Creates a by copying another . - /// - /// The start time of this . - /// The to copy. - public MultiplierControlPoint(double startTime, MultiplierControlPoint other) - : this(startTime) - { - TimingPoint = other.TimingPoint; - DifficultyPoint = other.DifficultyPoint; - } - - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - public int CompareTo(MultiplierControlPoint other) => StartTime.CompareTo(other?.StartTime); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.IO.Serialization; + +namespace osu.Game.Rulesets.Timing +{ + /// + /// A control point which adds an aggregated multiplier based on the provided 's BeatLength and 's SpeedMultiplier. + /// + public class MultiplierControlPoint : IJsonSerializable, IComparable + { + /// + /// The time in milliseconds at which this starts. + /// + public double StartTime; + + /// + /// The multiplier which this provides. + /// + public double Multiplier => 1000 / TimingPoint.BeatLength * DifficultyPoint.SpeedMultiplier; + + /// + /// The that provides the timing information for this . + /// + public TimingControlPoint TimingPoint = new TimingControlPoint(); + + /// + /// The that provides additional difficulty information for this . + /// + public DifficultyControlPoint DifficultyPoint = new DifficultyControlPoint(); + + /// + /// Creates a . This is required for JSON serialization + /// + public MultiplierControlPoint() + { + } + + /// + /// Creates a . + /// + /// The start time of this . + public MultiplierControlPoint(double startTime) + { + StartTime = startTime; + } + + /// + /// Creates a by copying another . + /// + /// The start time of this . + /// The to copy. + public MultiplierControlPoint(double startTime, MultiplierControlPoint other) + : this(startTime) + { + TimingPoint = other.TimingPoint; + DifficultyPoint = other.DifficultyPoint; + } + + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + public int CompareTo(MultiplierControlPoint other) => StartTime.CompareTo(other?.StartTime); + } +} diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index ecb10dfba2..1b6841c9bd 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -1,30 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.UI -{ - public class HitObjectContainer : CompositeDrawable - { - public virtual IEnumerable Objects => InternalChildren.Cast(); - public virtual IEnumerable AliveObjects => AliveInternalChildren.Cast(); - - public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); - public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); - - protected override int Compare(Drawable x, Drawable y) - { - if (!(x is DrawableHitObject xObj) || !(y is DrawableHitObject yObj)) - return base.Compare(x, y); - - // Put earlier hitobjects towards the end of the list, so they handle input first - int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); - return i == 0 ? CompareReverseChildID(x, y) : i; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.UI +{ + public class HitObjectContainer : CompositeDrawable + { + public virtual IEnumerable Objects => InternalChildren.Cast(); + public virtual IEnumerable AliveObjects => AliveInternalChildren.Cast(); + + public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); + public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); + + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is DrawableHitObject xObj) || !(y is DrawableHitObject yObj)) + return base.Compare(x, y); + + // Put earlier hitobjects towards the end of the list, so they handle input first + int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); + return i == 0 ? CompareReverseChildID(x, y) : i; + } + } +} diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index 1291b9fc98..e0d62aba30 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; - -namespace osu.Game.Rulesets.UI -{ - public class JudgementContainer : Container - where T : DrawableJudgement - { - public override void Add(T judgement) - { - if (judgement == null) throw new ArgumentNullException(nameof(judgement)); - - // remove any existing judgements for the judged object. - // this can be the case when rewinding. - RemoveAll(c => c.JudgedObject == judgement.JudgedObject); - - base.Add(judgement); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Rulesets.UI +{ + public class JudgementContainer : Container + where T : DrawableJudgement + { + public override void Add(T judgement) + { + if (judgement == null) throw new ArgumentNullException(nameof(judgement)); + + // remove any existing judgements for the judged object. + // this can be the case when rewinding. + RemoveAll(c => c.JudgedObject == judgement.JudgedObject); + + base.Add(judgement); + } + } +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index f01069118c..cdc1248049 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -1,112 +1,112 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics; -using osu.Game.Rulesets.Mods; -using OpenTK; - -namespace osu.Game.Rulesets.UI -{ - public class ModIcon : Container, IHasTooltip - { - private readonly SpriteIcon modIcon; - private readonly SpriteIcon background; - - private const float size = 80; - - public FontAwesome Icon - { - get { return modIcon.Icon; } - set { modIcon.Icon = value; } - } - - private readonly ModType type; - - public virtual string TooltipText { get; } - - public ModIcon(Mod mod) - { - if (mod == null) throw new ArgumentNullException(nameof(mod)); - - type = mod.Type; - - TooltipText = mod.Name; - - Size = new Vector2(size); - - Children = new Drawable[] - { - background = new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(size), - Icon = FontAwesome.fa_osu_mod_bg, - Y = -6.5f, - Shadow = true, - }, - modIcon = new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = OsuColour.Gray(84), - Size = new Vector2(size - 35), - Icon = mod.Icon - }, - }; - } - - private Color4 backgroundColour; - private Color4 highlightedColour; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - switch (type) - { - default: - case ModType.DifficultyIncrease: - backgroundColour = colours.Yellow; - highlightedColour = colours.YellowLight; - break; - case ModType.DifficultyReduction: - backgroundColour = colours.Green; - highlightedColour = colours.GreenLight; - break; - case ModType.Special: - backgroundColour = colours.Blue; - highlightedColour = colours.BlueLight; - break; - } - - applyStyle(); - } - - private bool highlighted; - - public bool Highlighted - { - get - { - return highlighted; - } - - set - { - highlighted = value; - applyStyle(); - } - } - - private void applyStyle() - { - background.Colour = highlighted ? highlightedColour : backgroundColour; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using OpenTK; + +namespace osu.Game.Rulesets.UI +{ + public class ModIcon : Container, IHasTooltip + { + private readonly SpriteIcon modIcon; + private readonly SpriteIcon background; + + private const float size = 80; + + public FontAwesome Icon + { + get { return modIcon.Icon; } + set { modIcon.Icon = value; } + } + + private readonly ModType type; + + public virtual string TooltipText { get; } + + public ModIcon(Mod mod) + { + if (mod == null) throw new ArgumentNullException(nameof(mod)); + + type = mod.Type; + + TooltipText = mod.Name; + + Size = new Vector2(size); + + Children = new Drawable[] + { + background = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(size), + Icon = FontAwesome.fa_osu_mod_bg, + Y = -6.5f, + Shadow = true, + }, + modIcon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = OsuColour.Gray(84), + Size = new Vector2(size - 35), + Icon = mod.Icon + }, + }; + } + + private Color4 backgroundColour; + private Color4 highlightedColour; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + switch (type) + { + default: + case ModType.DifficultyIncrease: + backgroundColour = colours.Yellow; + highlightedColour = colours.YellowLight; + break; + case ModType.DifficultyReduction: + backgroundColour = colours.Green; + highlightedColour = colours.GreenLight; + break; + case ModType.Special: + backgroundColour = colours.Blue; + highlightedColour = colours.BlueLight; + break; + } + + applyStyle(); + } + + private bool highlighted; + + public bool Highlighted + { + get + { + return highlighted; + } + + set + { + highlighted = value; + applyStyle(); + } + } + + private void applyStyle() + { + background.Colour = highlighted ? highlightedColour : backgroundColour; + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index bbf20c2c26..f2c9b49900 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -1,83 +1,83 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Framework.Allocation; - -namespace osu.Game.Rulesets.UI -{ - public abstract class Playfield : ScalableContainer - { - /// - /// The HitObjects contained in this Playfield. - /// - public HitObjectContainer HitObjects { get; private set; } - - /// - /// All the s nested inside this playfield. - /// - public IReadOnlyList NestedPlayfields => nestedPlayfields; - private List nestedPlayfields; - - /// - /// A container for keeping track of DrawableHitObjects. - /// - /// The width to scale the internal coordinate space to. - /// May be null if scaling based on is desired. If is also null, no scaling will occur. - /// - /// The height to scale the internal coordinate space to. - /// May be null if scaling based on is desired. If is also null, no scaling will occur. - /// - protected Playfield(float? customWidth = null, float? customHeight = null) - : base(customWidth, customHeight) - { - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - HitObjects = CreateHitObjectContainer(); - HitObjects.RelativeSizeAxes = Axes.Both; - - Add(HitObjects); - } - - /// - /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. - /// - public virtual void PostProcess() => nestedPlayfields?.ForEach(p => p.PostProcess()); - - /// - /// Adds a DrawableHitObject to this Playfield. - /// - /// The DrawableHitObject to add. - public virtual void Add(DrawableHitObject h) => HitObjects.Add(h); - - /// - /// Remove a DrawableHitObject from this Playfield. - /// - /// The DrawableHitObject to remove. - public virtual void Remove(DrawableHitObject h) => HitObjects.Remove(h); - - /// - /// Registers a as a nested . - /// This does not add the to the draw hierarchy. - /// - /// The to add. - protected void AddNested(Playfield otherPlayfield) - { - if (nestedPlayfields == null) - nestedPlayfields = new List(); - - nestedPlayfields.Add(otherPlayfield); - } - - /// - /// Creates the container that will be used to contain the s. - /// - protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Allocation; + +namespace osu.Game.Rulesets.UI +{ + public abstract class Playfield : ScalableContainer + { + /// + /// The HitObjects contained in this Playfield. + /// + public HitObjectContainer HitObjects { get; private set; } + + /// + /// All the s nested inside this playfield. + /// + public IReadOnlyList NestedPlayfields => nestedPlayfields; + private List nestedPlayfields; + + /// + /// A container for keeping track of DrawableHitObjects. + /// + /// The width to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + /// The height to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + protected Playfield(float? customWidth = null, float? customHeight = null) + : base(customWidth, customHeight) + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + HitObjects = CreateHitObjectContainer(); + HitObjects.RelativeSizeAxes = Axes.Both; + + Add(HitObjects); + } + + /// + /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. + /// + public virtual void PostProcess() => nestedPlayfields?.ForEach(p => p.PostProcess()); + + /// + /// Adds a DrawableHitObject to this Playfield. + /// + /// The DrawableHitObject to add. + public virtual void Add(DrawableHitObject h) => HitObjects.Add(h); + + /// + /// Remove a DrawableHitObject from this Playfield. + /// + /// The DrawableHitObject to remove. + public virtual void Remove(DrawableHitObject h) => HitObjects.Remove(h); + + /// + /// Registers a as a nested . + /// This does not add the to the draw hierarchy. + /// + /// The to add. + protected void AddNested(Playfield otherPlayfield) + { + if (nestedPlayfields == null) + nestedPlayfields = new List(); + + nestedPlayfields.Add(otherPlayfield); + } + + /// + /// Creates the container that will be used to contain the s. + /// + protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); + } +} diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 81418fecd4..d1f1807937 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -1,394 +1,394 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using osu.Framework.Configuration; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Input; -using osu.Game.Configuration; -using osu.Game.Input.Handlers; -using osu.Game.Overlays; -using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; -using OpenTK; - -namespace osu.Game.Rulesets.UI -{ - /// - /// Base RulesetContainer. Doesn't hold objects. - /// - /// Should not be derived - derive instead. - /// - /// - public abstract class RulesetContainer : Container - { - /// - /// The selected variant. - /// - public virtual int Variant => 0; - - /// - /// The input manager for this RulesetContainer. - /// - internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; - - /// - /// The key conversion input manager for this RulesetContainer. - /// - public PassThroughInputManager KeyBindingInputManager; - - /// - /// Whether a replay is currently loaded. - /// - public readonly BindableBool HasReplayLoaded = new BindableBool(); - - public abstract IEnumerable Objects { get; } - - private readonly Lazy playfield; - /// - /// The playfield. - /// - public Playfield Playfield => playfield.Value; - - /// - /// The cursor provided by this . May be null if no cursor is provided. - /// - public readonly CursorContainer Cursor; - - protected readonly Ruleset Ruleset; - - private IRulesetConfigManager rulesetConfig; - private OnScreenDisplay onScreenDisplay; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - - /// - /// A visual representation of a . - /// - /// The ruleset being repesented. - protected RulesetContainer(Ruleset ruleset) - { - Ruleset = ruleset; - playfield = new Lazy(CreatePlayfield); - - Cursor = CreateCursor(); - } - - [BackgroundDependencyLoader(true)] - private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings) - { - this.onScreenDisplay = onScreenDisplay; - - rulesetConfig = CreateConfig(Ruleset, settings); - - if (rulesetConfig != null) - { - dependencies.Cache(rulesetConfig); - onScreenDisplay?.BeginTracking(this, rulesetConfig); - } - } - - public abstract ScoreProcessor CreateScoreProcessor(); - - /// - /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. - /// - /// The input manager. - public abstract PassThroughInputManager CreateInputManager(); - - protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; - - public Replay Replay { get; private set; } - - /// - /// Sets a replay to be used, overriding local input. - /// - /// The replay, null for local input. - public virtual void SetReplay(Replay replay) - { - if (ReplayInputManager == null) - throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); - - Replay = replay; - ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; - - HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; - } - - - /// - /// Creates the cursor. May be null if the doesn't provide a custom cursor. - /// - protected virtual CursorContainer CreateCursor() => null; - - protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null; - - /// - /// Creates a Playfield. - /// - /// The Playfield. - protected abstract Playfield CreatePlayfield(); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (rulesetConfig != null) - { - onScreenDisplay?.StopTracking(this, rulesetConfig); - rulesetConfig = null; - } - } - } - - /// - /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield - /// and does not load drawable hit objects. - /// - /// Should not be derived - derive instead. - /// - /// - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - { - public event Action OnJudgement; - public event Action OnJudgementRemoved; - - /// - /// The Beatmap - /// - public Beatmap Beatmap; - - /// - /// All the converted hit objects contained by this hit renderer. - /// - public override IEnumerable Objects => Beatmap.HitObjects; - - /// - /// The mods which are to be applied. - /// - protected IEnumerable Mods; - - /// - /// The this was created with. - /// - protected readonly WorkingBeatmap WorkingBeatmap; - - /// - /// Whether the specified beatmap is assumed to be specific to the current ruleset. - /// - public readonly bool IsForCurrentRuleset; - - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - - protected override Container Content => content; - private Container content; - - /// - /// Whether to assume the beatmap passed into this is for the current ruleset. - /// Creates a hit renderer for a beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - /// Whether to assume the beatmap is for the current ruleset. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset) - : base(ruleset) - { - Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); - - WorkingBeatmap = workingBeatmap; - IsForCurrentRuleset = isForCurrentRuleset; - // ReSharper disable once PossibleNullReferenceException - Mods = workingBeatmap.Mods.Value; - - RelativeSizeAxes = Axes.Both; - - BeatmapConverter converter = CreateBeatmapConverter(); - BeatmapProcessor processor = CreateBeatmapProcessor(); - - // Check if the beatmap can be converted - if (!converter.CanConvert(workingBeatmap.Beatmap)) - throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter})."); - - // Apply conversion adjustments before converting - foreach (var mod in Mods.OfType>()) - mod.ApplyToBeatmapConverter(converter); - - // Convert the beatmap - Beatmap = converter.Convert(workingBeatmap.Beatmap); - - // Apply difficulty adjustments from mods before using Difficulty. - foreach (var mod in Mods.OfType()) - mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); - - // Post-process the beatmap - processor.PostProcess(Beatmap); - - // Apply defaults - foreach (var h in Beatmap.HitObjects) - h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); - - KeyBindingInputManager = CreateInputManager(); - KeyBindingInputManager.RelativeSizeAxes = Axes.Both; - - // Add mods, should always be the last thing applied to give full control to mods - applyMods(Mods); - } - - [BackgroundDependencyLoader] - private void load() - { - KeyBindingInputManager.Add(content = new Container - { - RelativeSizeAxes = Axes.Both, - }); - - AddInternal(KeyBindingInputManager); - KeyBindingInputManager.Add(Playfield); - - if (Cursor != null) - KeyBindingInputManager.Add(Cursor); - - loadObjects(); - } - - /// - /// Applies the active mods to this RulesetContainer. - /// - /// - private void applyMods(IEnumerable mods) - { - if (mods == null) - return; - - foreach (var mod in mods.OfType>()) - foreach (var obj in Beatmap.HitObjects) - mod.ApplyToHitObject(obj); - - foreach (var mod in mods.OfType>()) - mod.ApplyToRulesetContainer(this); - } - - public override void SetReplay(Replay replay) - { - base.SetReplay(replay); - - if (ReplayInputManager?.ReplayInputHandler != null) - ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; - } - - /// - /// Creates and adds drawable representations of hit objects to the play field. - /// - private void loadObjects() - { - foreach (TObject h in Beatmap.HitObjects) - { - var drawableObject = GetVisualRepresentation(h); - - if (drawableObject == null) - continue; - - drawableObject.OnJudgement += (d, j) => OnJudgement?.Invoke(j); - drawableObject.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(j); - - Playfield.Add(drawableObject); - } - - Playfield.PostProcess(); - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(Playfield.HitObjects.Objects); - } - - protected override void Update() - { - base.Update(); - - Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea; - } - - /// - /// Creates a processor to perform post-processing operations - /// on HitObjects in converted Beatmaps. - /// - /// The Beatmap processor. - protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor(); - - /// - /// Computes the size of the in relative coordinate space after aspect adjustments. - /// - /// The aspect-adjusted size. - protected virtual Vector2 GetAspectAdjustedSize() => Vector2.One; - - /// - /// The area of this that is available for the to use. - /// Must be specified in relative coordinate space to this . - /// This affects the final size of the but does not affect the 's scale. - /// - protected virtual Vector2 PlayfieldArea => new Vector2(0.75f); // A sane default - - /// - /// Creates a converter to convert Beatmap to a specific mode. - /// - /// The Beatmap converter. - protected abstract BeatmapConverter CreateBeatmapConverter(); - - /// - /// Creates a DrawableHitObject from a HitObject. - /// - /// The HitObject to make drawable. - /// The DrawableHitObject. - protected abstract DrawableHitObject GetVisualRepresentation(TObject h); - } - - /// - /// A derivable RulesetContainer that manages the Playfield and HitObjects. - /// - /// The type of Playfield contained by this RulesetContainer. - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - where TPlayfield : Playfield - { - /// - /// The playfield. - /// - protected new TPlayfield Playfield => (TPlayfield)base.Playfield; - - /// - /// Creates a hit renderer for a beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - /// Whether to assume the beatmap is for the current ruleset. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(ruleset, beatmap, isForCurrentRuleset) - { - } - } - - public class BeatmapInvalidForRulesetException : ArgumentException - { - public BeatmapInvalidForRulesetException(string text) - : base(text) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; +using osu.Game.Configuration; +using osu.Game.Input.Handlers; +using osu.Game.Overlays; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using OpenTK; + +namespace osu.Game.Rulesets.UI +{ + /// + /// Base RulesetContainer. Doesn't hold objects. + /// + /// Should not be derived - derive instead. + /// + /// + public abstract class RulesetContainer : Container + { + /// + /// The selected variant. + /// + public virtual int Variant => 0; + + /// + /// The input manager for this RulesetContainer. + /// + internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; + + /// + /// The key conversion input manager for this RulesetContainer. + /// + public PassThroughInputManager KeyBindingInputManager; + + /// + /// Whether a replay is currently loaded. + /// + public readonly BindableBool HasReplayLoaded = new BindableBool(); + + public abstract IEnumerable Objects { get; } + + private readonly Lazy playfield; + /// + /// The playfield. + /// + public Playfield Playfield => playfield.Value; + + /// + /// The cursor provided by this . May be null if no cursor is provided. + /// + public readonly CursorContainer Cursor; + + protected readonly Ruleset Ruleset; + + private IRulesetConfigManager rulesetConfig; + private OnScreenDisplay onScreenDisplay; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + /// + /// A visual representation of a . + /// + /// The ruleset being repesented. + protected RulesetContainer(Ruleset ruleset) + { + Ruleset = ruleset; + playfield = new Lazy(CreatePlayfield); + + Cursor = CreateCursor(); + } + + [BackgroundDependencyLoader(true)] + private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings) + { + this.onScreenDisplay = onScreenDisplay; + + rulesetConfig = CreateConfig(Ruleset, settings); + + if (rulesetConfig != null) + { + dependencies.Cache(rulesetConfig); + onScreenDisplay?.BeginTracking(this, rulesetConfig); + } + } + + public abstract ScoreProcessor CreateScoreProcessor(); + + /// + /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. + /// + /// The input manager. + public abstract PassThroughInputManager CreateInputManager(); + + protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + + public Replay Replay { get; private set; } + + /// + /// Sets a replay to be used, overriding local input. + /// + /// The replay, null for local input. + public virtual void SetReplay(Replay replay) + { + if (ReplayInputManager == null) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); + + Replay = replay; + ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; + + HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; + } + + + /// + /// Creates the cursor. May be null if the doesn't provide a custom cursor. + /// + protected virtual CursorContainer CreateCursor() => null; + + protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null; + + /// + /// Creates a Playfield. + /// + /// The Playfield. + protected abstract Playfield CreatePlayfield(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (rulesetConfig != null) + { + onScreenDisplay?.StopTracking(this, rulesetConfig); + rulesetConfig = null; + } + } + } + + /// + /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield + /// and does not load drawable hit objects. + /// + /// Should not be derived - derive instead. + /// + /// + /// The type of HitObject contained by this RulesetContainer. + public abstract class RulesetContainer : RulesetContainer + where TObject : HitObject + { + public event Action OnJudgement; + public event Action OnJudgementRemoved; + + /// + /// The Beatmap + /// + public Beatmap Beatmap; + + /// + /// All the converted hit objects contained by this hit renderer. + /// + public override IEnumerable Objects => Beatmap.HitObjects; + + /// + /// The mods which are to be applied. + /// + protected IEnumerable Mods; + + /// + /// The this was created with. + /// + protected readonly WorkingBeatmap WorkingBeatmap; + + /// + /// Whether the specified beatmap is assumed to be specific to the current ruleset. + /// + public readonly bool IsForCurrentRuleset; + + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); + + protected override Container Content => content; + private Container content; + + /// + /// Whether to assume the beatmap passed into this is for the current ruleset. + /// Creates a hit renderer for a beatmap. + /// + /// The ruleset being repesented. + /// The beatmap to create the hit renderer for. + /// Whether to assume the beatmap is for the current ruleset. + protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset) + : base(ruleset) + { + Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); + + WorkingBeatmap = workingBeatmap; + IsForCurrentRuleset = isForCurrentRuleset; + // ReSharper disable once PossibleNullReferenceException + Mods = workingBeatmap.Mods.Value; + + RelativeSizeAxes = Axes.Both; + + BeatmapConverter converter = CreateBeatmapConverter(); + BeatmapProcessor processor = CreateBeatmapProcessor(); + + // Check if the beatmap can be converted + if (!converter.CanConvert(workingBeatmap.Beatmap)) + throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter})."); + + // Apply conversion adjustments before converting + foreach (var mod in Mods.OfType>()) + mod.ApplyToBeatmapConverter(converter); + + // Convert the beatmap + Beatmap = converter.Convert(workingBeatmap.Beatmap); + + // Apply difficulty adjustments from mods before using Difficulty. + foreach (var mod in Mods.OfType()) + mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); + + // Post-process the beatmap + processor.PostProcess(Beatmap); + + // Apply defaults + foreach (var h in Beatmap.HitObjects) + h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); + + KeyBindingInputManager = CreateInputManager(); + KeyBindingInputManager.RelativeSizeAxes = Axes.Both; + + // Add mods, should always be the last thing applied to give full control to mods + applyMods(Mods); + } + + [BackgroundDependencyLoader] + private void load() + { + KeyBindingInputManager.Add(content = new Container + { + RelativeSizeAxes = Axes.Both, + }); + + AddInternal(KeyBindingInputManager); + KeyBindingInputManager.Add(Playfield); + + if (Cursor != null) + KeyBindingInputManager.Add(Cursor); + + loadObjects(); + } + + /// + /// Applies the active mods to this RulesetContainer. + /// + /// + private void applyMods(IEnumerable mods) + { + if (mods == null) + return; + + foreach (var mod in mods.OfType>()) + foreach (var obj in Beatmap.HitObjects) + mod.ApplyToHitObject(obj); + + foreach (var mod in mods.OfType>()) + mod.ApplyToRulesetContainer(this); + } + + public override void SetReplay(Replay replay) + { + base.SetReplay(replay); + + if (ReplayInputManager?.ReplayInputHandler != null) + ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + } + + /// + /// Creates and adds drawable representations of hit objects to the play field. + /// + private void loadObjects() + { + foreach (TObject h in Beatmap.HitObjects) + { + var drawableObject = GetVisualRepresentation(h); + + if (drawableObject == null) + continue; + + drawableObject.OnJudgement += (d, j) => OnJudgement?.Invoke(j); + drawableObject.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(j); + + Playfield.Add(drawableObject); + } + + Playfield.PostProcess(); + + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(Playfield.HitObjects.Objects); + } + + protected override void Update() + { + base.Update(); + + Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea; + } + + /// + /// Creates a processor to perform post-processing operations + /// on HitObjects in converted Beatmaps. + /// + /// The Beatmap processor. + protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor(); + + /// + /// Computes the size of the in relative coordinate space after aspect adjustments. + /// + /// The aspect-adjusted size. + protected virtual Vector2 GetAspectAdjustedSize() => Vector2.One; + + /// + /// The area of this that is available for the to use. + /// Must be specified in relative coordinate space to this . + /// This affects the final size of the but does not affect the 's scale. + /// + protected virtual Vector2 PlayfieldArea => new Vector2(0.75f); // A sane default + + /// + /// Creates a converter to convert Beatmap to a specific mode. + /// + /// The Beatmap converter. + protected abstract BeatmapConverter CreateBeatmapConverter(); + + /// + /// Creates a DrawableHitObject from a HitObject. + /// + /// The HitObject to make drawable. + /// The DrawableHitObject. + protected abstract DrawableHitObject GetVisualRepresentation(TObject h); + } + + /// + /// A derivable RulesetContainer that manages the Playfield and HitObjects. + /// + /// The type of Playfield contained by this RulesetContainer. + /// The type of HitObject contained by this RulesetContainer. + public abstract class RulesetContainer : RulesetContainer + where TObject : HitObject + where TPlayfield : Playfield + { + /// + /// The playfield. + /// + protected new TPlayfield Playfield => (TPlayfield)base.Playfield; + + /// + /// Creates a hit renderer for a beatmap. + /// + /// The ruleset being repesented. + /// The beatmap to create the hit renderer for. + /// Whether to assume the beatmap is for the current ruleset. + protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + } + + public class BeatmapInvalidForRulesetException : ArgumentException + { + public BeatmapInvalidForRulesetException(string text) + : base(text) + { + } + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 3f8a17e23d..58a66a5224 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -1,270 +1,270 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Timing; -using osu.Game.Configuration; -using osu.Game.Input.Bindings; -using osu.Game.Input.Handlers; -using osu.Game.Screens.Play; -using OpenTK.Input; - -namespace osu.Game.Rulesets.UI -{ - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler - where T : struct - { - public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer - { - public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - } - - protected readonly KeyBindingContainer KeyBindingContainer; - - protected override Container Content => KeyBindingContainer; - - protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - { - InternalChild = KeyBindingContainer = new RulesetKeyBindingContainer(ruleset, variant, unique); - } - - #region Action mapping (for replays) - - private List lastPressedActions = new List(); - - protected override void HandleNewState(InputState state) - { - base.HandleNewState(state); - - var replayState = state as ReplayInputHandler.ReplayState; - - if (replayState == null) return; - - // Here we handle states specifically coming from a replay source. - // These have extra action information rather than keyboard keys or mouse buttons. - - List newActions = replayState.PressedActions; - - foreach (var released in lastPressedActions.Except(newActions)) - KeyBindingContainer.TriggerReleased(released); - - foreach (var pressed in newActions.Except(lastPressedActions)) - KeyBindingContainer.TriggerPressed(pressed); - - lastPressedActions = newActions; - } - - #endregion - - #region IHasReplayHandler - - private ReplayInputHandler replayInputHandler; - public ReplayInputHandler ReplayInputHandler - { - get - { - return replayInputHandler; - } - set - { - if (replayInputHandler != null) RemoveHandler(replayInputHandler); - - replayInputHandler = value; - UseParentState = replayInputHandler == null; - - if (replayInputHandler != null) - AddHandler(replayInputHandler); - } - } - - #endregion - - #region Clock control - - private ManualClock clock; - private IFrameBasedClock parentClock; - - protected override void LoadComplete() - { - base.LoadComplete(); - - //our clock will now be our parent's clock, but we want to replace this to allow manual control. - parentClock = Clock; - - ProcessCustomClock = false; - Clock = new FramedClock(clock = new ManualClock - { - CurrentTime = parentClock.CurrentTime, - Rate = parentClock.Rate, - }); - } - - /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. - /// - private bool requireMoreUpdateLoops; - - /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. - /// - private bool validState; - - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - - private bool isAttached => replayInputHandler != null && !UseParentState; - - private const int max_catch_up_updates_per_frame = 50; - - public override bool UpdateSubTree() - { - requireMoreUpdateLoops = true; - validState = true; - - int loops = 0; - - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) - { - if (!base.UpdateSubTree()) - return false; - - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - - if (isAttached) - { - // When handling replay input, we need to consider the possibility of fast-forwarding, which may cause the clock to be updated - // to a point very far into the future, then playing a frame at that time. In such a case, lifetime MUST be updated before - // input is handled. This is why base.Update is not called from the derived Update when handling replay input, and is instead - // called manually at the correct time here. - base.Update(); - } - } - - return true; - } - - protected override void Update() - { - if (parentClock == null) return; - - clock.Rate = parentClock.Rate; - clock.IsRunning = parentClock.IsRunning; - - if (!isAttached) - { - clock.CurrentTime = parentClock.CurrentTime; - } - else - { - double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime); - - if (newTime == null) - { - // we shouldn't execute for this time value. probably waiting on more replay data. - validState = false; - return; - } - - clock.CurrentTime = newTime.Value; - } - - requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; - - // The manual clock time has changed in the above code. The framed clock now needs to be updated - // to ensure that the its time is valid for our children before input is processed - Clock.ProcessFrame(); - - if (!isAttached) - { - // For non-replay input handling, this provides equivalent input ordering as if Update was not overridden - base.Update(); - } - } - - #endregion - - #region Setting application (disables etc.) - - private Bindable mouseDisabled; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); - } - - protected override void TransformState(InputState state) - { - base.TransformState(state); - - // we don't want to transform the state if a replay is present (for now, at least). - if (replayInputHandler != null) return; - - var mouse = state.Mouse as Framework.Input.MouseState; - - if (mouse != null) - { - if (mouseDisabled.Value) - { - mouse.SetPressed(MouseButton.Left, false); - mouse.SetPressed(MouseButton.Right, false); - } - } - } - - #endregion - - #region Key Counter Attachment - - public void Attach(KeyCounterCollection keyCounter) - { - var receptor = new ActionReceptor(keyCounter); - Add(receptor); - keyCounter.SetReceptor(receptor); - - keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); - } - - public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler - { - public ActionReceptor(KeyCounterCollection target) - : base(target) - { - } - - public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action)); - - public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action)); - } - - #endregion - } - - /// - /// Expose the in a capable . - /// - public interface IHasReplayHandler - { - ReplayInputHandler ReplayInputHandler { get; set; } - } - - /// - /// Supports attaching a . - /// Keys will be populated automatically and a receptor will be injected inside. - /// - public interface ICanAttachKeyCounter - { - void Attach(KeyCounterCollection keyCounter); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Timing; +using osu.Game.Configuration; +using osu.Game.Input.Bindings; +using osu.Game.Input.Handlers; +using osu.Game.Screens.Play; +using OpenTK.Input; + +namespace osu.Game.Rulesets.UI +{ + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler + where T : struct + { + public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer + { + public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + } + + protected readonly KeyBindingContainer KeyBindingContainer; + + protected override Container Content => KeyBindingContainer; + + protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + { + InternalChild = KeyBindingContainer = new RulesetKeyBindingContainer(ruleset, variant, unique); + } + + #region Action mapping (for replays) + + private List lastPressedActions = new List(); + + protected override void HandleNewState(InputState state) + { + base.HandleNewState(state); + + var replayState = state as ReplayInputHandler.ReplayState; + + if (replayState == null) return; + + // Here we handle states specifically coming from a replay source. + // These have extra action information rather than keyboard keys or mouse buttons. + + List newActions = replayState.PressedActions; + + foreach (var released in lastPressedActions.Except(newActions)) + KeyBindingContainer.TriggerReleased(released); + + foreach (var pressed in newActions.Except(lastPressedActions)) + KeyBindingContainer.TriggerPressed(pressed); + + lastPressedActions = newActions; + } + + #endregion + + #region IHasReplayHandler + + private ReplayInputHandler replayInputHandler; + public ReplayInputHandler ReplayInputHandler + { + get + { + return replayInputHandler; + } + set + { + if (replayInputHandler != null) RemoveHandler(replayInputHandler); + + replayInputHandler = value; + UseParentState = replayInputHandler == null; + + if (replayInputHandler != null) + AddHandler(replayInputHandler); + } + } + + #endregion + + #region Clock control + + private ManualClock clock; + private IFrameBasedClock parentClock; + + protected override void LoadComplete() + { + base.LoadComplete(); + + //our clock will now be our parent's clock, but we want to replace this to allow manual control. + parentClock = Clock; + + ProcessCustomClock = false; + Clock = new FramedClock(clock = new ManualClock + { + CurrentTime = parentClock.CurrentTime, + Rate = parentClock.Rate, + }); + } + + /// + /// Whether we are running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + private bool requireMoreUpdateLoops; + + /// + /// Whether we are in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + private bool validState; + + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + + private bool isAttached => replayInputHandler != null && !UseParentState; + + private const int max_catch_up_updates_per_frame = 50; + + public override bool UpdateSubTree() + { + requireMoreUpdateLoops = true; + validState = true; + + int loops = 0; + + while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + { + if (!base.UpdateSubTree()) + return false; + + UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); + + if (isAttached) + { + // When handling replay input, we need to consider the possibility of fast-forwarding, which may cause the clock to be updated + // to a point very far into the future, then playing a frame at that time. In such a case, lifetime MUST be updated before + // input is handled. This is why base.Update is not called from the derived Update when handling replay input, and is instead + // called manually at the correct time here. + base.Update(); + } + } + + return true; + } + + protected override void Update() + { + if (parentClock == null) return; + + clock.Rate = parentClock.Rate; + clock.IsRunning = parentClock.IsRunning; + + if (!isAttached) + { + clock.CurrentTime = parentClock.CurrentTime; + } + else + { + double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime); + + if (newTime == null) + { + // we shouldn't execute for this time value. probably waiting on more replay data. + validState = false; + return; + } + + clock.CurrentTime = newTime.Value; + } + + requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; + + // The manual clock time has changed in the above code. The framed clock now needs to be updated + // to ensure that the its time is valid for our children before input is processed + Clock.ProcessFrame(); + + if (!isAttached) + { + // For non-replay input handling, this provides equivalent input ordering as if Update was not overridden + base.Update(); + } + } + + #endregion + + #region Setting application (disables etc.) + + private Bindable mouseDisabled; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); + } + + protected override void TransformState(InputState state) + { + base.TransformState(state); + + // we don't want to transform the state if a replay is present (for now, at least). + if (replayInputHandler != null) return; + + var mouse = state.Mouse as Framework.Input.MouseState; + + if (mouse != null) + { + if (mouseDisabled.Value) + { + mouse.SetPressed(MouseButton.Left, false); + mouse.SetPressed(MouseButton.Right, false); + } + } + } + + #endregion + + #region Key Counter Attachment + + public void Attach(KeyCounterCollection keyCounter) + { + var receptor = new ActionReceptor(keyCounter); + Add(receptor); + keyCounter.SetReceptor(receptor); + + keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); + } + + public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler + { + public ActionReceptor(KeyCounterCollection target) + : base(target) + { + } + + public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action)); + + public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action)); + } + + #endregion + } + + /// + /// Expose the in a capable . + /// + public interface IHasReplayHandler + { + ReplayInputHandler ReplayInputHandler { get; set; } + } + + /// + /// Supports attaching a . + /// Keys will be populated automatically and a receptor will be injected inside. + /// + public interface ICanAttachKeyCounter + { + void Attach(KeyCounterCollection keyCounter); + } +} diff --git a/osu.Game/Rulesets/UI/ScalableContainer.cs b/osu.Game/Rulesets/UI/ScalableContainer.cs index 04e6db9578..5ee03be7ee 100644 --- a/osu.Game/Rulesets/UI/ScalableContainer.cs +++ b/osu.Game/Rulesets/UI/ScalableContainer.cs @@ -1,99 +1,99 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using OpenTK; - -namespace osu.Game.Rulesets.UI -{ - /// - /// A which can have its internal coordinate system scaled to a specific size. - /// - public class ScalableContainer : Container - { - /// - /// A function that converts coordinates from gamefield to screen space. - /// - public Func GamefieldToScreenSpace => scaledContent.GamefieldToScreenSpace; - - /// - /// The scaled content. - /// - private readonly ScaledContainer scaledContent; - protected override Container Content => scaledContent; - - /// - /// A which can have its internal coordinate system scaled to a specific size. - /// - /// The width to scale the internal coordinate space to. - /// May be null if scaling based on is desired. If is also null, no scaling will occur. - /// - /// The height to scale the internal coordinate space to. - /// May be null if scaling based on is desired. If is also null, no scaling will occur. - /// - public ScalableContainer(float? customWidth = null, float? customHeight = null) - { - AddInternal(scaledContent = new ScaledContainer - { - CustomWidth = customWidth, - CustomHeight = customHeight, - RelativeSizeAxes = Axes.Both, - }); - } - - private class ScaledContainer : Container - { - /// - /// A function that converts coordinates from gamefield to screen space. - /// - public Func GamefieldToScreenSpace => content.ToScreenSpace; - - /// - /// The value to scale the width of the content to match. - /// If null, is used. - /// - public float? CustomWidth; - - /// - /// The value to scale the height of the content to match. - /// if null, is used. - /// - public float? CustomHeight; - - private readonly Container content; - protected override Container Content => content; - - public ScaledContainer() - { - AddInternal(content = new Container { RelativeSizeAxes = Axes.Both }); - } - - protected override void Update() - { - base.Update(); - - content.Scale = sizeScale; - content.Size = Vector2.Divide(Vector2.One, sizeScale); - } - - /// - /// The scale that is required for the size of the content to match and . - /// - private Vector2 sizeScale - { - get - { - if (CustomWidth.HasValue && CustomHeight.HasValue) - return Vector2.Divide(DrawSize, new Vector2(CustomWidth.Value, CustomHeight.Value)); - if (CustomWidth.HasValue) - return new Vector2(DrawSize.X / CustomWidth.Value); - if (CustomHeight.HasValue) - return new Vector2(DrawSize.Y / CustomHeight.Value); - return Vector2.One; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A which can have its internal coordinate system scaled to a specific size. + /// + public class ScalableContainer : Container + { + /// + /// A function that converts coordinates from gamefield to screen space. + /// + public Func GamefieldToScreenSpace => scaledContent.GamefieldToScreenSpace; + + /// + /// The scaled content. + /// + private readonly ScaledContainer scaledContent; + protected override Container Content => scaledContent; + + /// + /// A which can have its internal coordinate system scaled to a specific size. + /// + /// The width to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + /// The height to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + public ScalableContainer(float? customWidth = null, float? customHeight = null) + { + AddInternal(scaledContent = new ScaledContainer + { + CustomWidth = customWidth, + CustomHeight = customHeight, + RelativeSizeAxes = Axes.Both, + }); + } + + private class ScaledContainer : Container + { + /// + /// A function that converts coordinates from gamefield to screen space. + /// + public Func GamefieldToScreenSpace => content.ToScreenSpace; + + /// + /// The value to scale the width of the content to match. + /// If null, is used. + /// + public float? CustomWidth; + + /// + /// The value to scale the height of the content to match. + /// if null, is used. + /// + public float? CustomHeight; + + private readonly Container content; + protected override Container Content => content; + + public ScaledContainer() + { + AddInternal(content = new Container { RelativeSizeAxes = Axes.Both }); + } + + protected override void Update() + { + base.Update(); + + content.Scale = sizeScale; + content.Size = Vector2.Divide(Vector2.One, sizeScale); + } + + /// + /// The scale that is required for the size of the content to match and . + /// + private Vector2 sizeScale + { + get + { + if (CustomWidth.HasValue && CustomHeight.HasValue) + return Vector2.Divide(DrawSize, new Vector2(CustomWidth.Value, CustomHeight.Value)); + if (CustomWidth.HasValue) + return new Vector2(DrawSize.X / CustomWidth.Value); + if (CustomHeight.HasValue) + return new Vector2(DrawSize.Y / CustomHeight.Value); + return Vector2.One; + } + } + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs index 372bdb1030..4fe727cb84 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs @@ -1,25 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.UI.Scrolling -{ - public enum ScrollingDirection - { - /// - /// Hitobjects will scroll vertically from the bottom of the hitobject container. - /// - Up, - /// - /// Hitobjects will scroll vertically from the top of the hitobject container. - /// - Down, - /// - /// Hitobjects will scroll horizontally from the right of the hitobject container. - /// - Left, - /// - /// Hitobjects will scroll horizontally from the left of the hitobject container. - /// - Right - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.UI.Scrolling +{ + public enum ScrollingDirection + { + /// + /// Hitobjects will scroll vertically from the bottom of the hitobject container. + /// + Up, + /// + /// Hitobjects will scroll vertically from the top of the hitobject container. + /// + Down, + /// + /// Hitobjects will scroll horizontally from the right of the hitobject container. + /// + Left, + /// + /// Hitobjects will scroll horizontally from the left of the hitobject container. + /// + Right + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 530ed653aa..edfea57e94 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -1,116 +1,116 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Caching; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Lists; -using osu.Game.Configuration; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Timing; -using osu.Game.Rulesets.UI.Scrolling.Visualisers; - -namespace osu.Game.Rulesets.UI.Scrolling -{ - public class ScrollingHitObjectContainer : HitObjectContainer - { - /// - /// The duration required to scroll through one length of the before any control point adjustments. - /// - public readonly BindableDouble TimeRange = new BindableDouble - { - MinValue = 0, - MaxValue = double.MaxValue - }; - - /// - /// The control points that adjust the scrolling speed. - /// - protected readonly SortedList ControlPoints = new SortedList(); - - private readonly ScrollingDirection direction; - - private Cached initialStateCache = new Cached(); - - public ScrollingHitObjectContainer(ScrollingDirection direction) - { - this.direction = direction; - - RelativeSizeAxes = Axes.Both; - - TimeRange.ValueChanged += v => initialStateCache.Invalidate(); - } - - private ISpeedChangeVisualiser speedChangeVisualiser; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - switch (config.Get(OsuSetting.SpeedChangeVisualisation)) - { - case SpeedChangeVisualisationMethod.Sequential: - speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints); - break; - case SpeedChangeVisualisationMethod.Overlapping: - speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); - break; - } - } - - public override void Add(DrawableHitObject hitObject) - { - initialStateCache.Invalidate(); - base.Add(hitObject); - } - - public override bool Remove(DrawableHitObject hitObject) - { - var result = base.Remove(hitObject); - if (result) - initialStateCache.Invalidate(); - return result; - } - - public void AddControlPoint(MultiplierControlPoint controlPoint) - { - ControlPoints.Add(controlPoint); - initialStateCache.Invalidate(); - } - - public bool RemoveControlPoint(MultiplierControlPoint controlPoint) - { - var result = ControlPoints.Remove(controlPoint); - if (result) - initialStateCache.Invalidate(); - return result; - } - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) - initialStateCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - - protected override void Update() - { - base.Update(); - - if (!initialStateCache.IsValid) - { - speedChangeVisualiser.ComputeInitialStates(Objects, direction, TimeRange, DrawSize); - initialStateCache.Validate(); - } - } - - protected override void UpdateAfterChildrenLife() - { - base.UpdateAfterChildrenLife(); - - // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions - speedChangeVisualiser.ComputePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Caching; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Lists; +using osu.Game.Configuration; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI.Scrolling.Visualisers; + +namespace osu.Game.Rulesets.UI.Scrolling +{ + public class ScrollingHitObjectContainer : HitObjectContainer + { + /// + /// The duration required to scroll through one length of the before any control point adjustments. + /// + public readonly BindableDouble TimeRange = new BindableDouble + { + MinValue = 0, + MaxValue = double.MaxValue + }; + + /// + /// The control points that adjust the scrolling speed. + /// + protected readonly SortedList ControlPoints = new SortedList(); + + private readonly ScrollingDirection direction; + + private Cached initialStateCache = new Cached(); + + public ScrollingHitObjectContainer(ScrollingDirection direction) + { + this.direction = direction; + + RelativeSizeAxes = Axes.Both; + + TimeRange.ValueChanged += v => initialStateCache.Invalidate(); + } + + private ISpeedChangeVisualiser speedChangeVisualiser; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + switch (config.Get(OsuSetting.SpeedChangeVisualisation)) + { + case SpeedChangeVisualisationMethod.Sequential: + speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints); + break; + case SpeedChangeVisualisationMethod.Overlapping: + speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); + break; + } + } + + public override void Add(DrawableHitObject hitObject) + { + initialStateCache.Invalidate(); + base.Add(hitObject); + } + + public override bool Remove(DrawableHitObject hitObject) + { + var result = base.Remove(hitObject); + if (result) + initialStateCache.Invalidate(); + return result; + } + + public void AddControlPoint(MultiplierControlPoint controlPoint) + { + ControlPoints.Add(controlPoint); + initialStateCache.Invalidate(); + } + + public bool RemoveControlPoint(MultiplierControlPoint controlPoint) + { + var result = ControlPoints.Remove(controlPoint); + if (result) + initialStateCache.Invalidate(); + return result; + } + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) + initialStateCache.Invalidate(); + + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + base.Update(); + + if (!initialStateCache.IsValid) + { + speedChangeVisualiser.ComputeInitialStates(Objects, direction, TimeRange, DrawSize); + initialStateCache.Validate(); + } + } + + protected override void UpdateAfterChildrenLife() + { + base.UpdateAfterChildrenLife(); + + // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions + speedChangeVisualiser.ComputePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize); + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 1c1c8f7f61..1e7f561aba 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -1,127 +1,127 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK.Input; - -namespace osu.Game.Rulesets.UI.Scrolling -{ - /// - /// A type of specialized towards scrolling s. - /// - public abstract class ScrollingPlayfield : Playfield - { - /// - /// The default span of time visible by the length of the scrolling axes. - /// This is clamped between and . - /// - private const double time_span_default = 1500; - /// - /// The minimum span of time that may be visible by the length of the scrolling axes. - /// - private const double time_span_min = 50; - /// - /// The maximum span of time that may be visible by the length of the scrolling axes. - /// - private const double time_span_max = 10000; - /// - /// The step increase/decrease of the span of time visible by the length of the scrolling axes. - /// - private const double time_span_step = 50; - - /// - /// The span of time that is visible by the length of the scrolling axes. - /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. - /// - public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default) - { - Default = time_span_default, - MinValue = time_span_min, - MaxValue = time_span_max - }; - - /// - /// Whether the player can change . - /// - protected virtual bool UserScrollSpeedAdjustment => true; - - /// - /// The container that contains the s. - /// - public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects; - - private readonly ScrollingDirection direction; - - /// - /// Creates a new . - /// - /// The direction in which s in this container should scroll. - /// The width to scale the internal coordinate space to. - /// May be null if scaling based on is desired. If is also null, no scaling will occur. - /// - /// The height to scale the internal coordinate space to. - /// May be null if scaling based on is desired. If is also null, no scaling will occur. - /// - protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null, float? customHeight = null) - : base(customWidth, customHeight) - { - this.direction = direction; - } - - [BackgroundDependencyLoader] - private void load() - { - HitObjects.TimeRange.BindTo(VisibleTimeRange); - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (!UserScrollSpeedAdjustment) - return false; - - if (state.Keyboard.ControlPressed) - { - switch (args.Key) - { - case Key.Minus: - transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint); - break; - case Key.Plus: - transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint); - break; - } - } - - return false; - } - - private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None) - { - this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing)); - } - - protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction); - - private class TransformVisibleTimeRange : Transform - { - private double valueAt(double time) - { - if (time < StartTime) return StartValue; - if (time >= EndTime) return EndValue; - - return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); - } - - public override string TargetMember => "VisibleTimeRange.Value"; - - protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time); - protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK.Input; + +namespace osu.Game.Rulesets.UI.Scrolling +{ + /// + /// A type of specialized towards scrolling s. + /// + public abstract class ScrollingPlayfield : Playfield + { + /// + /// The default span of time visible by the length of the scrolling axes. + /// This is clamped between and . + /// + private const double time_span_default = 1500; + /// + /// The minimum span of time that may be visible by the length of the scrolling axes. + /// + private const double time_span_min = 50; + /// + /// The maximum span of time that may be visible by the length of the scrolling axes. + /// + private const double time_span_max = 10000; + /// + /// The step increase/decrease of the span of time visible by the length of the scrolling axes. + /// + private const double time_span_step = 50; + + /// + /// The span of time that is visible by the length of the scrolling axes. + /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. + /// + public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default) + { + Default = time_span_default, + MinValue = time_span_min, + MaxValue = time_span_max + }; + + /// + /// Whether the player can change . + /// + protected virtual bool UserScrollSpeedAdjustment => true; + + /// + /// The container that contains the s. + /// + public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects; + + private readonly ScrollingDirection direction; + + /// + /// Creates a new . + /// + /// The direction in which s in this container should scroll. + /// The width to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + /// The height to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null, float? customHeight = null) + : base(customWidth, customHeight) + { + this.direction = direction; + } + + [BackgroundDependencyLoader] + private void load() + { + HitObjects.TimeRange.BindTo(VisibleTimeRange); + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!UserScrollSpeedAdjustment) + return false; + + if (state.Keyboard.ControlPressed) + { + switch (args.Key) + { + case Key.Minus: + transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint); + break; + case Key.Plus: + transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint); + break; + } + } + + return false; + } + + private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None) + { + this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing)); + } + + protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction); + + private class TransformVisibleTimeRange : Transform + { + private double valueAt(double time) + { + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + + public override string TargetMember => "VisibleTimeRange.Value"; + + protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time); + protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value; + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs index 5f6b6801ce..79f9eaa9e9 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs @@ -1,110 +1,110 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Lists; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.IO.Serialization; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Timing; - -namespace osu.Game.Rulesets.UI.Scrolling -{ - /// - /// A type of that supports a . - /// s inside this will scroll within the playfield. - /// - public abstract class ScrollingRulesetContainer : RulesetContainer - where TObject : HitObject - where TPlayfield : ScrollingPlayfield - { - /// - /// Provides the default s that adjust the scrolling rate of s - /// inside this . - /// - /// - protected readonly SortedList DefaultControlPoints = new SortedList(Comparer.Default); - - protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(ruleset, beatmap, isForCurrentRuleset) - { - } - - [BackgroundDependencyLoader] - private void load() - { - // Calculate default multiplier control points - var lastTimingPoint = new TimingControlPoint(); - var lastDifficultyPoint = new DifficultyControlPoint(); - - // Merge timing + difficulty points - var allPoints = new SortedList(Comparer.Default); - allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); - allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); - - // Generate the timing points, making non-timing changes use the previous timing change - var timingChanges = allPoints.Select(c => - { - var timingPoint = c as TimingControlPoint; - var difficultyPoint = c as DifficultyControlPoint; - - if (timingPoint != null) - lastTimingPoint = timingPoint; - - if (difficultyPoint != null) - lastDifficultyPoint = difficultyPoint; - - return new MultiplierControlPoint(c.Time) - { - TimingPoint = lastTimingPoint, - DifficultyPoint = lastDifficultyPoint - }; - }); - - double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - - // Perform some post processing of the timing changes - timingChanges = timingChanges - // Collapse sections after the last hit object - .Where(s => s.StartTime <= lastObjectTime) - // Collapse sections with the same start time - .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime); - - DefaultControlPoints.AddRange(timingChanges); - - // If we have no control points, add a default one - if (DefaultControlPoints.Count == 0) - DefaultControlPoints.Add(new MultiplierControlPoint()); - - DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield)); - } - - private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield playfield) - { - playfield.HitObjects.AddControlPoint(controlPoint); - playfield.NestedPlayfields?.OfType().ForEach(p => applySpeedAdjustment(controlPoint, p)); - } - - /// - /// Generates a with the default timing change/difficulty change from the beatmap at a time. - /// - /// The time to create the control point at. - /// The default at . - public MultiplierControlPoint CreateControlPointAt(double time) - { - if (DefaultControlPoints.Count == 0) - return new MultiplierControlPoint(time); - - int index = DefaultControlPoints.BinarySearch(new MultiplierControlPoint(time)); - if (index < 0) - return new MultiplierControlPoint(time); - - return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone()); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Lists; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.IO.Serialization; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Timing; + +namespace osu.Game.Rulesets.UI.Scrolling +{ + /// + /// A type of that supports a . + /// s inside this will scroll within the playfield. + /// + public abstract class ScrollingRulesetContainer : RulesetContainer + where TObject : HitObject + where TPlayfield : ScrollingPlayfield + { + /// + /// Provides the default s that adjust the scrolling rate of s + /// inside this . + /// + /// + protected readonly SortedList DefaultControlPoints = new SortedList(Comparer.Default); + + protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(ruleset, beatmap, isForCurrentRuleset) + { + } + + [BackgroundDependencyLoader] + private void load() + { + // Calculate default multiplier control points + var lastTimingPoint = new TimingControlPoint(); + var lastDifficultyPoint = new DifficultyControlPoint(); + + // Merge timing + difficulty points + var allPoints = new SortedList(Comparer.Default); + allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); + allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); + + // Generate the timing points, making non-timing changes use the previous timing change + var timingChanges = allPoints.Select(c => + { + var timingPoint = c as TimingControlPoint; + var difficultyPoint = c as DifficultyControlPoint; + + if (timingPoint != null) + lastTimingPoint = timingPoint; + + if (difficultyPoint != null) + lastDifficultyPoint = difficultyPoint; + + return new MultiplierControlPoint(c.Time) + { + TimingPoint = lastTimingPoint, + DifficultyPoint = lastDifficultyPoint + }; + }); + + double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; + + // Perform some post processing of the timing changes + timingChanges = timingChanges + // Collapse sections after the last hit object + .Where(s => s.StartTime <= lastObjectTime) + // Collapse sections with the same start time + .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime); + + DefaultControlPoints.AddRange(timingChanges); + + // If we have no control points, add a default one + if (DefaultControlPoints.Count == 0) + DefaultControlPoints.Add(new MultiplierControlPoint()); + + DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield)); + } + + private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield playfield) + { + playfield.HitObjects.AddControlPoint(controlPoint); + playfield.NestedPlayfields?.OfType().ForEach(p => applySpeedAdjustment(controlPoint, p)); + } + + /// + /// Generates a with the default timing change/difficulty change from the beatmap at a time. + /// + /// The time to create the control point at. + /// The default at . + public MultiplierControlPoint CreateControlPointAt(double time) + { + if (DefaultControlPoints.Count == 0) + return new MultiplierControlPoint(time); + + int index = DefaultControlPoints.BinarySearch(new MultiplierControlPoint(time)); + if (index < 0) + return new MultiplierControlPoint(time); + + return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone()); + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs index 46d71e1602..02791e0517 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.UI.Scrolling.Visualisers -{ - public interface ISpeedChangeVisualiser - { - /// - /// Computes the states of s that are constant, such as lifetime and spatial length. - /// This is invoked once whenever or changes. - /// - /// The s whose states should be computed. - /// The scrolling direction. - /// The duration required to scroll through one length of the screen before any control point adjustments. - /// The length of the screen that is scrolled through. - void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length); - - /// - /// Computes the states of s that change depending on , such as position. - /// This is invoked once per frame. - /// - /// The s whose states should be computed. - /// The scrolling direction. - /// The current time. - /// The duration required to scroll through one length of the screen before any control point adjustments. - /// The length of the screen that is scrolled through. - void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Visualisers +{ + public interface ISpeedChangeVisualiser + { + /// + /// Computes the states of s that are constant, such as lifetime and spatial length. + /// This is invoked once whenever or changes. + /// + /// The s whose states should be computed. + /// The scrolling direction. + /// The duration required to scroll through one length of the screen before any control point adjustments. + /// The length of the screen that is scrolled through. + void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length); + + /// + /// Computes the states of s that change depending on , such as position. + /// This is invoked once per frame. + /// + /// The s whose states should be computed. + /// The scrolling direction. + /// The current time. + /// The duration required to scroll through one length of the screen before any control point adjustments. + /// The length of the screen that is scrolled through. + void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs index 48c212efa7..2365582645 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs @@ -1,80 +1,80 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Lists; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Timing; -using OpenTK; - -namespace osu.Game.Rulesets.UI.Scrolling.Visualisers -{ - public class OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser - { - private readonly SortedList controlPoints; - - public OverlappingSpeedChangeVisualiser(SortedList controlPoints) - { - this.controlPoints = controlPoints; - } - - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - var controlPoint = controlPointAt(obj.HitObject.StartTime); - obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier; - - if (obj.HasNestedHitObjects) - { - ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); - } - } - } - - public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - var controlPoint = controlPointAt(obj.HitObject.StartTime); - - var position = (obj.HitObject.StartTime - currentTime) * controlPoint.Multiplier / timeRange; - - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = (float)(position * length.Y); - break; - case ScrollingDirection.Down: - obj.Y = (float)(-position * length.Y); - break; - case ScrollingDirection.Left: - obj.X = (float)(position * length.X); - break; - case ScrollingDirection.Right: - obj.X = (float)(-position * length.X); - break; - } - } - } - - private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint(); - private MultiplierControlPoint controlPointAt(double time) - { - if (controlPoints.Count == 0) - return new MultiplierControlPoint(double.NegativeInfinity); - - if (time < controlPoints[0].StartTime) - return controlPoints[0]; - - searchPoint.StartTime = time; - int index = controlPoints.BinarySearch(searchPoint); - - if (index < 0) - index = ~index - 1; - - return controlPoints[index]; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Lists; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Timing; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Visualisers +{ + public class OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser + { + private readonly SortedList controlPoints; + + public OverlappingSpeedChangeVisualiser(SortedList controlPoints) + { + this.controlPoints = controlPoints; + } + + public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var controlPoint = controlPointAt(obj.HitObject.StartTime); + obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier; + + if (obj.HasNestedHitObjects) + { + ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); + ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); + } + } + } + + public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var controlPoint = controlPointAt(obj.HitObject.StartTime); + + var position = (obj.HitObject.StartTime - currentTime) * controlPoint.Multiplier / timeRange; + + switch (direction) + { + case ScrollingDirection.Up: + obj.Y = (float)(position * length.Y); + break; + case ScrollingDirection.Down: + obj.Y = (float)(-position * length.Y); + break; + case ScrollingDirection.Left: + obj.X = (float)(position * length.X); + break; + case ScrollingDirection.Right: + obj.X = (float)(-position * length.X); + break; + } + } + } + + private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint(); + private MultiplierControlPoint controlPointAt(double time) + { + if (controlPoints.Count == 0) + return new MultiplierControlPoint(double.NegativeInfinity); + + if (time < controlPoints[0].StartTime) + return controlPoints[0]; + + searchPoint.StartTime = time; + int index = controlPoints.BinarySearch(searchPoint); + + if (index < 0) + index = ~index - 1; + + return controlPoints[index]; + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs index 1b7c3714d6..708a2f173b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs @@ -1,103 +1,103 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Timing; -using OpenTK; - -namespace osu.Game.Rulesets.UI.Scrolling.Visualisers -{ - public class SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser - { - private readonly Dictionary hitObjectPositions = new Dictionary(); - - private readonly IReadOnlyList controlPoints; - - public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints) - { - this.controlPoints = controlPoints; - } - - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange); - - obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000; - - if (obj.HitObject is IHasEndTime endTime) - { - var diff = positionAt(endTime.EndTime, timeRange) - startPosition; - - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = (float)(diff * length.Y); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Width = (float)(diff * length.X); - break; - } - } - - if (obj.HasNestedHitObjects) - { - ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); - } - } - } - - public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) - { - var timelinePosition = positionAt(currentTime, timeRange); - - foreach (var obj in hitObjects) - { - var finalPosition = hitObjectPositions[obj] - timelinePosition; - - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = (float)(finalPosition * length.Y); - break; - case ScrollingDirection.Down: - obj.Y = (float)(-finalPosition * length.Y); - break; - case ScrollingDirection.Left: - obj.X = (float)(finalPosition * length.X); - break; - case ScrollingDirection.Right: - obj.X = (float)(-finalPosition * length.X); - break; - } - } - } - - private double positionAt(double time, double timeRange) - { - double length = 0; - for (int i = 0; i < controlPoints.Count; i++) - { - var current = controlPoints[i]; - var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; - - if (i > 0 && current.StartTime > time) - continue; - - // Duration of the current control point - var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; - - length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / timeRange; - } - - return length; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Timing; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Visualisers +{ + public class SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser + { + private readonly Dictionary hitObjectPositions = new Dictionary(); + + private readonly IReadOnlyList controlPoints; + + public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints) + { + this.controlPoints = controlPoints; + } + + public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange); + + obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000; + + if (obj.HitObject is IHasEndTime endTime) + { + var diff = positionAt(endTime.EndTime, timeRange) - startPosition; + + switch (direction) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + obj.Height = (float)(diff * length.Y); + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + obj.Width = (float)(diff * length.X); + break; + } + } + + if (obj.HasNestedHitObjects) + { + ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); + ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); + } + } + } + + public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + { + var timelinePosition = positionAt(currentTime, timeRange); + + foreach (var obj in hitObjects) + { + var finalPosition = hitObjectPositions[obj] - timelinePosition; + + switch (direction) + { + case ScrollingDirection.Up: + obj.Y = (float)(finalPosition * length.Y); + break; + case ScrollingDirection.Down: + obj.Y = (float)(-finalPosition * length.Y); + break; + case ScrollingDirection.Left: + obj.X = (float)(finalPosition * length.X); + break; + case ScrollingDirection.Right: + obj.X = (float)(-finalPosition * length.X); + break; + } + } + } + + private double positionAt(double time, double timeRange) + { + double length = 0; + for (int i = 0; i < controlPoints.Count; i++) + { + var current = controlPoints[i]; + var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + + if (i > 0 && current.StartTime > time) + continue; + + // Duration of the current control point + var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; + + length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / timeRange; + } + + return length; + } + } +} diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index c5e5883b99..e7d3690f59 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -1,85 +1,85 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Threading; -using osu.Framework.Screens; -using osu.Framework.Graphics; -using osu.Framework.Input; -using OpenTK; - -namespace osu.Game.Screens -{ - public abstract class BackgroundScreen : Screen, IEquatable - { - public virtual bool Equals(BackgroundScreen other) - { - return other?.GetType() == GetType(); - } - - private const float transition_length = 500; - private const float x_movement_amount = 50; - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - //we don't want to handle escape key. - return false; - } - - public override bool Push(Screen screen) - { - // When trying to push a non-loaded screen, load it asynchronously and re-invoke Push - // once it's done. - if (screen.LoadState == LoadState.NotLoaded) - { - LoadComponentAsync(screen, d => Push((BackgroundScreen)d)); - return true; - } - - // Make sure the in-progress loading is complete before pushing the screen. - while (screen.LoadState < LoadState.Ready) - Thread.Sleep(1); - - base.Push(screen); - - return true; - } - - protected override void Update() - { - base.Update(); - Content.Scale = new Vector2(1 + x_movement_amount / DrawSize.X * 2); - } - - protected override void OnEntering(Screen last) - { - Content.FadeOut(); - Content.MoveToX(x_movement_amount); - - Content.FadeIn(transition_length, Easing.InOutQuart); - Content.MoveToX(0, transition_length, Easing.InOutQuart); - - base.OnEntering(last); - } - - protected override void OnSuspending(Screen next) - { - Content.MoveToX(-x_movement_amount, transition_length, Easing.InOutQuart); - base.OnSuspending(next); - } - - protected override bool OnExiting(Screen next) - { - Content.FadeOut(transition_length, Easing.OutExpo); - Content.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); - - return base.OnExiting(next); - } - - protected override void OnResuming(Screen last) - { - Content.MoveToX(0, transition_length, Easing.OutExpo); - base.OnResuming(last); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Threading; +using osu.Framework.Screens; +using osu.Framework.Graphics; +using osu.Framework.Input; +using OpenTK; + +namespace osu.Game.Screens +{ + public abstract class BackgroundScreen : Screen, IEquatable + { + public virtual bool Equals(BackgroundScreen other) + { + return other?.GetType() == GetType(); + } + + private const float transition_length = 500; + private const float x_movement_amount = 50; + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + //we don't want to handle escape key. + return false; + } + + public override bool Push(Screen screen) + { + // When trying to push a non-loaded screen, load it asynchronously and re-invoke Push + // once it's done. + if (screen.LoadState == LoadState.NotLoaded) + { + LoadComponentAsync(screen, d => Push((BackgroundScreen)d)); + return true; + } + + // Make sure the in-progress loading is complete before pushing the screen. + while (screen.LoadState < LoadState.Ready) + Thread.Sleep(1); + + base.Push(screen); + + return true; + } + + protected override void Update() + { + base.Update(); + Content.Scale = new Vector2(1 + x_movement_amount / DrawSize.X * 2); + } + + protected override void OnEntering(Screen last) + { + Content.FadeOut(); + Content.MoveToX(x_movement_amount); + + Content.FadeIn(transition_length, Easing.InOutQuart); + Content.MoveToX(0, transition_length, Easing.InOutQuart); + + base.OnEntering(last); + } + + protected override void OnSuspending(Screen next) + { + Content.MoveToX(-x_movement_amount, transition_length, Easing.InOutQuart); + base.OnSuspending(next); + } + + protected override bool OnExiting(Screen next) + { + Content.FadeOut(transition_length, Easing.OutExpo); + Content.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); + + return base.OnExiting(next); + } + + protected override void OnResuming(Screen last) + { + Content.MoveToX(0, transition_length, Easing.OutExpo); + base.OnResuming(last); + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 1ce84289b2..78561cecbf 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -1,84 +1,84 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Transforms; -using OpenTK; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Backgrounds; - -namespace osu.Game.Screens.Backgrounds -{ - public class BackgroundScreenBeatmap : BackgroundScreen - { - private Background background; - - private WorkingBeatmap beatmap; - private Vector2 blurTarget; - - public WorkingBeatmap Beatmap - { - get { return beatmap; } - set - { - if (beatmap == value && beatmap != null) - return; - - beatmap = value; - - Schedule(() => - { - LoadComponentAsync(new BeatmapBackground(beatmap), b => - { - float newDepth = 0; - if (background != null) - { - newDepth = background.Depth + 1; - background.FinishTransforms(); - background.FadeOut(250); - background.Expire(); - } - - b.Depth = newDepth; - Add(background = b); - background.BlurSigma = blurTarget; - }); - }); - } - } - - public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) - { - Beatmap = beatmap; - } - - public TransformSequence BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None) - => background?.BlurTo(blurTarget = sigma, duration, easing); - - public override bool Equals(BackgroundScreen other) - { - var otherBeatmapBackground = other as BackgroundScreenBeatmap; - if (otherBeatmapBackground == null) return false; - - return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap; - } - - private class BeatmapBackground : Background - { - private readonly WorkingBeatmap beatmap; - - public BeatmapBackground(WorkingBeatmap beatmap) - { - this.beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Transforms; +using OpenTK; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Screens.Backgrounds +{ + public class BackgroundScreenBeatmap : BackgroundScreen + { + private Background background; + + private WorkingBeatmap beatmap; + private Vector2 blurTarget; + + public WorkingBeatmap Beatmap + { + get { return beatmap; } + set + { + if (beatmap == value && beatmap != null) + return; + + beatmap = value; + + Schedule(() => + { + LoadComponentAsync(new BeatmapBackground(beatmap), b => + { + float newDepth = 0; + if (background != null) + { + newDepth = background.Depth + 1; + background.FinishTransforms(); + background.FadeOut(250); + background.Expire(); + } + + b.Depth = newDepth; + Add(background = b); + background.BlurSigma = blurTarget; + }); + }); + } + } + + public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) + { + Beatmap = beatmap; + } + + public TransformSequence BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None) + => background?.BlurTo(blurTarget = sigma, duration, easing); + + public override bool Equals(BackgroundScreen other) + { + var otherBeatmapBackground = other as BackgroundScreenBeatmap; + if (otherBeatmapBackground == null) return false; + + return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap; + } + + private class BeatmapBackground : Background + { + private readonly WorkingBeatmap beatmap; + + public BeatmapBackground(WorkingBeatmap beatmap) + { + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); + } + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs index 629f5ea3db..041391db1e 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs @@ -1,26 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Graphics.Backgrounds; - -namespace osu.Game.Screens.Backgrounds -{ - public class BackgroundScreenCustom : BackgroundScreen - { - private readonly string textureName; - - public BackgroundScreenCustom(string textureName) - { - this.textureName = textureName; - Add(new Background(textureName)); - } - - public override bool Equals(BackgroundScreen other) - { - var backgroundScreenCustom = other as BackgroundScreenCustom; - if (backgroundScreenCustom == null) return false; - - return base.Equals(other) && textureName == backgroundScreenCustom.textureName; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Screens.Backgrounds +{ + public class BackgroundScreenCustom : BackgroundScreen + { + private readonly string textureName; + + public BackgroundScreenCustom(string textureName) + { + this.textureName = textureName; + Add(new Background(textureName)); + } + + public override bool Equals(BackgroundScreen other) + { + var backgroundScreenCustom = other as BackgroundScreenCustom; + if (backgroundScreenCustom == null) return false; + + return base.Equals(other) && textureName == backgroundScreenCustom.textureName; + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 24e744d234..38df9b13ef 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -1,46 +1,46 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Threading; -using osu.Game.Graphics.Backgrounds; - -namespace osu.Game.Screens.Backgrounds -{ - public class BackgroundScreenDefault : BackgroundScreen - { - private int currentDisplay; - private const int background_count = 5; - - private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}"; - - private Background current; - - [BackgroundDependencyLoader] - private void load() - { - display(new Background(backgroundName)); - } - - private void display(Background newBackground) - { - current?.FadeOut(800, Easing.InOutSine); - current?.Expire(); - - Add(current = newBackground); - currentDisplay++; - } - - private ScheduledDelegate nextTask; - - public void Next() - { - nextTask?.Cancel(); - nextTask = Scheduler.AddDelayed(() => - { - LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display); - }, 100); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Threading; +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Screens.Backgrounds +{ + public class BackgroundScreenDefault : BackgroundScreen + { + private int currentDisplay; + private const int background_count = 5; + + private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}"; + + private Background current; + + [BackgroundDependencyLoader] + private void load() + { + display(new Background(backgroundName)); + } + + private void display(Background newBackground) + { + current?.FadeOut(800, Easing.InOutSine); + current?.Expire(); + + Add(current = newBackground); + currentDisplay++; + } + + private ScheduledDelegate nextTask; + + public void Next() + { + nextTask?.Cancel(); + nextTask = Scheduler.AddDelayed(() => + { + LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display); + }, 100); + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs index 758032a711..5e08db8907 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Backgrounds -{ - public class BackgroundScreenEmpty : BackgroundScreen - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Screens.Backgrounds +{ + public class BackgroundScreenEmpty : BackgroundScreen + { + } +} diff --git a/osu.Game/Screens/Charts/ChartInfo.cs b/osu.Game/Screens/Charts/ChartInfo.cs index 70ca3b8b67..35021709d6 100644 --- a/osu.Game/Screens/Charts/ChartInfo.cs +++ b/osu.Game/Screens/Charts/ChartInfo.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Charts -{ - public class ChartInfo : ScreenWhiteBox - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Screens.Charts +{ + public class ChartInfo : ScreenWhiteBox + { + } +} diff --git a/osu.Game/Screens/Charts/ChartListing.cs b/osu.Game/Screens/Charts/ChartListing.cs index 376cc76ef5..a618bba8c4 100644 --- a/osu.Game/Screens/Charts/ChartListing.cs +++ b/osu.Game/Screens/Charts/ChartListing.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; - -namespace osu.Game.Screens.Charts -{ - public class ChartListing : ScreenWhiteBox - { - protected override IEnumerable PossibleChildren => new[] { - typeof(ChartInfo) - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; + +namespace osu.Game.Screens.Charts +{ + public class ChartListing : ScreenWhiteBox + { + protected override IEnumerable PossibleChildren => new[] { + typeof(ChartInfo) + }; + } +} diff --git a/osu.Game/Screens/Direct/OnlineListing.cs b/osu.Game/Screens/Direct/OnlineListing.cs index 38f4d6a771..9d2d09a695 100644 --- a/osu.Game/Screens/Direct/OnlineListing.cs +++ b/osu.Game/Screens/Direct/OnlineListing.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Direct -{ - public class OnlineListing : ScreenWhiteBox - { - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Screens.Direct +{ + public class OnlineListing : ScreenWhiteBox + { + } +} diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index 69a1296701..399f9274a6 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -1,50 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Graphics; - -namespace osu.Game.Screens.Edit.Components -{ - public class BottomBarContainer : Container - { - private const float corner_radius = 5; - private const float contents_padding = 15; - - public readonly Bindable Beatmap = new Bindable(); - protected Track Track => Beatmap.Value.Track; - - private readonly Drawable background; - private readonly Container content; - - protected override Container Content => content; - - public BottomBarContainer() - { - Masking = true; - CornerRadius = corner_radius; - - InternalChildren = new[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = contents_padding }, - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray1; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Components +{ + public class BottomBarContainer : Container + { + private const float corner_radius = 5; + private const float contents_padding = 15; + + public readonly Bindable Beatmap = new Bindable(); + protected Track Track => Beatmap.Value.Track; + + private readonly Drawable background; + private readonly Container content; + + protected override Container Content => content; + + public BottomBarContainer() + { + Masking = true; + CornerRadius = corner_radius; + + InternalChildren = new[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = contents_padding }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray1; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index fe2549d300..5f62843faf 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -1,160 +1,160 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Framework.Timing; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Components -{ - public class PlaybackControl : BottomBarContainer - { - private IconButton playButton; - - private IAdjustableClock adjustableClock; - - [BackgroundDependencyLoader] - private void load(IAdjustableClock adjustableClock) - { - this.adjustableClock = adjustableClock; - - PlaybackTabControl tabs; - - Children = new Drawable[] - { - playButton = new IconButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - Scale = new Vector2(1.4f), - IconScale = new Vector2(1.4f), - Icon = FontAwesome.fa_play_circle_o, - Action = togglePause, - Padding = new MarginPadding { Left = 20 } - }, - new OsuSpriteText - { - Origin = Anchor.BottomLeft, - Text = "Playback Speed", - RelativePositionAxes = Axes.Y, - Y = 0.5f, - Padding = new MarginPadding { Left = 45 } - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Padding = new MarginPadding { Left = 45 }, - Child = tabs = new PlaybackTabControl(), - } - }; - - tabs.Current.ValueChanged += newValue => Beatmap.Value.Track.Tempo.Value = newValue; - } - - private void togglePause() - { - if (adjustableClock.IsRunning) - adjustableClock.Stop(); - else - adjustableClock.Start(); - } - - protected override void Update() - { - base.Update(); - - playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; - } - - private class PlaybackTabControl : OsuTabControl - { - private static readonly double[] tempo_values = { 0.5, 0.75, 1 }; - - protected override TabItem CreateTabItem(double value) => new PlaybackTabItem(value); - - protected override Dropdown CreateDropdown() => null; - - public PlaybackTabControl() - { - RelativeSizeAxes = Axes.Both; - TabContainer.Spacing = Vector2.Zero; - - tempo_values.ForEach(AddItem); - } - - public class PlaybackTabItem : TabItem - { - private const float fade_duration = 200; - - private readonly OsuSpriteText text; - private readonly OsuSpriteText textBold; - - public PlaybackTabItem(double value) : base(value) - { - RelativeSizeAxes = Axes.Both; - - Width = 1f / tempo_values.Length; - - Children = new Drawable[] - { - text = new OsuSpriteText - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Text = $"{value:0%}", - TextSize = 14, - }, - textBold = new OsuSpriteText - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Text = $"{value:0%}", - TextSize = 14, - Font = @"Exo2.0-Bold", - Alpha = 0, - }, - }; - } - - private Color4 hoveredColour; - private Color4 normalColour; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - text.Colour = normalColour = colours.YellowDarker; - textBold.Colour = hoveredColour = colours.Yellow; - } - - protected override bool OnHover(InputState state) - { - updateState(); - return true; - } - - protected override void OnHoverLost(InputState state) => updateState(); - protected override void OnActivated() => updateState(); - protected override void OnDeactivated() => updateState(); - - private void updateState() - { - text.FadeColour(Active || IsHovered ? hoveredColour : normalColour, fade_duration, Easing.OutQuint); - text.FadeTo(Active ? 0 : 1, fade_duration, Easing.OutQuint); - textBold.FadeTo(Active ? 1 : 0, fade_duration, Easing.OutQuint); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Timing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Components +{ + public class PlaybackControl : BottomBarContainer + { + private IconButton playButton; + + private IAdjustableClock adjustableClock; + + [BackgroundDependencyLoader] + private void load(IAdjustableClock adjustableClock) + { + this.adjustableClock = adjustableClock; + + PlaybackTabControl tabs; + + Children = new Drawable[] + { + playButton = new IconButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Scale = new Vector2(1.4f), + IconScale = new Vector2(1.4f), + Icon = FontAwesome.fa_play_circle_o, + Action = togglePause, + Padding = new MarginPadding { Left = 20 } + }, + new OsuSpriteText + { + Origin = Anchor.BottomLeft, + Text = "Playback Speed", + RelativePositionAxes = Axes.Y, + Y = 0.5f, + Padding = new MarginPadding { Left = 45 } + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Padding = new MarginPadding { Left = 45 }, + Child = tabs = new PlaybackTabControl(), + } + }; + + tabs.Current.ValueChanged += newValue => Beatmap.Value.Track.Tempo.Value = newValue; + } + + private void togglePause() + { + if (adjustableClock.IsRunning) + adjustableClock.Stop(); + else + adjustableClock.Start(); + } + + protected override void Update() + { + base.Update(); + + playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + } + + private class PlaybackTabControl : OsuTabControl + { + private static readonly double[] tempo_values = { 0.5, 0.75, 1 }; + + protected override TabItem CreateTabItem(double value) => new PlaybackTabItem(value); + + protected override Dropdown CreateDropdown() => null; + + public PlaybackTabControl() + { + RelativeSizeAxes = Axes.Both; + TabContainer.Spacing = Vector2.Zero; + + tempo_values.ForEach(AddItem); + } + + public class PlaybackTabItem : TabItem + { + private const float fade_duration = 200; + + private readonly OsuSpriteText text; + private readonly OsuSpriteText textBold; + + public PlaybackTabItem(double value) : base(value) + { + RelativeSizeAxes = Axes.Both; + + Width = 1f / tempo_values.Length; + + Children = new Drawable[] + { + text = new OsuSpriteText + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Text = $"{value:0%}", + TextSize = 14, + }, + textBold = new OsuSpriteText + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Text = $"{value:0%}", + TextSize = 14, + Font = @"Exo2.0-Bold", + Alpha = 0, + }, + }; + } + + private Color4 hoveredColour; + private Color4 normalColour; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = normalColour = colours.YellowDarker; + textBold.Colour = hoveredColour = colours.Yellow; + } + + protected override bool OnHover(InputState state) + { + updateState(); + return true; + } + + protected override void OnHoverLost(InputState state) => updateState(); + protected override void OnActivated() => updateState(); + protected override void OnDeactivated() => updateState(); + + private void updateState() + { + text.FadeColour(Active || IsHovered ? hoveredColour : normalColour, fade_duration, Easing.OutQuint); + text.FadeTo(Active ? 0 : 1, fade_duration, Easing.OutQuint); + textBold.FadeTo(Active ? 1 : 0, fade_duration, Easing.OutQuint); + } + } + } + } +} diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 5a3b6c652b..129ea2bf7d 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -1,47 +1,47 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Graphics.Sprites; -using System; -using osu.Framework.Allocation; -using osu.Framework.Timing; - -namespace osu.Game.Screens.Edit.Components -{ - public class TimeInfoContainer : BottomBarContainer - { - private readonly OsuSpriteText trackTimer; - - private IAdjustableClock adjustableClock; - - public TimeInfoContainer() - { - - Children = new Drawable[] - { - trackTimer = new OsuSpriteText - { - Origin = Anchor.BottomLeft, - RelativePositionAxes = Axes.Y, - TextSize = 22, - FixedWidth = true, - Y = 0.5f, - } - }; - } - - [BackgroundDependencyLoader] - private void load(IAdjustableClock adjustableClock) - { - this.adjustableClock = adjustableClock; - } - - protected override void Update() - { - base.Update(); - - trackTimer.Text = TimeSpan.FromMilliseconds(adjustableClock.CurrentTime).ToString(@"mm\:ss\:fff"); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using System; +using osu.Framework.Allocation; +using osu.Framework.Timing; + +namespace osu.Game.Screens.Edit.Components +{ + public class TimeInfoContainer : BottomBarContainer + { + private readonly OsuSpriteText trackTimer; + + private IAdjustableClock adjustableClock; + + public TimeInfoContainer() + { + + Children = new Drawable[] + { + trackTimer = new OsuSpriteText + { + Origin = Anchor.BottomLeft, + RelativePositionAxes = Axes.Y, + TextSize = 22, + FixedWidth = true, + Y = 0.5f, + } + }; + } + + [BackgroundDependencyLoader] + private void load(IAdjustableClock adjustableClock) + { + this.adjustableClock = adjustableClock; + } + + protected override void Update() + { + base.Update(); + + trackTimer.Text = TimeSpan.FromMilliseconds(adjustableClock.CurrentTime).ToString(@"mm\:ss\:fff"); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index dfb67e4228..3af3fcbdd7 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -1,34 +1,34 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts -{ - /// - /// The part of the timeline that displays bookmarks. - /// - public class BookmarkPart : TimelinePart - { - protected override void LoadBeatmap(WorkingBeatmap beatmap) - { - base.LoadBeatmap(beatmap); - foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks) - Add(new BookmarkVisualisation(bookmark)); - } - - private class BookmarkVisualisation : PointVisualisation - { - public BookmarkVisualisation(double startTime) - : base(startTime) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Blue; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays bookmarks. + /// + public class BookmarkPart : TimelinePart + { + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks) + Add(new BookmarkVisualisation(bookmark)); + } + + private class BookmarkVisualisation : PointVisualisation + { + public BookmarkVisualisation(double startTime) + : base(startTime) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Blue; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 29cee20a9b..1146037004 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -1,35 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts -{ - /// - /// The part of the timeline that displays breaks in the song. - /// - public class BreakPart : TimelinePart - { - protected override void LoadBeatmap(WorkingBeatmap beatmap) - { - base.LoadBeatmap(beatmap); - foreach (var breakPeriod in beatmap.Beatmap.Breaks) - Add(new BreakVisualisation(breakPeriod)); - } - - private class BreakVisualisation : DurationVisualisation - { - public BreakVisualisation(BreakPeriod breakPeriod) - : base(breakPeriod.StartTime, breakPeriod.EndTime) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Yellow; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays breaks in the song. + /// + public class BreakPart : TimelinePart + { + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + foreach (var breakPeriod in beatmap.Beatmap.Breaks) + Add(new BreakVisualisation(breakPeriod)); + } + + private class BreakVisualisation : DurationVisualisation + { + public BreakVisualisation(BreakPeriod breakPeriod) + : base(breakPeriod.StartTime, breakPeriod.EndTime) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Yellow; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index a8e62d77ad..4bef22463e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -1,70 +1,70 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts -{ - /// - /// The part of the timeline that displays the control points. - /// - public class ControlPointPart : TimelinePart - { - protected override void LoadBeatmap(WorkingBeatmap beatmap) - { - base.LoadBeatmap(beatmap); - - ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo; - - cpi.TimingPoints.ForEach(addTimingPoint); - - // Consider all non-timing points as the same type - cpi.SamplePoints.Select(c => (ControlPoint)c) - .Concat(cpi.EffectPoints) - .Concat(cpi.DifficultyPoints) - .Distinct() - // Non-timing points should not be added where there are timing points - .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time) - .ForEach(addNonTimingPoint); - } - - private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint)); - private void addNonTimingPoint(ControlPoint controlPoint) => Add(new NonTimingPointVisualisation(controlPoint)); - - private class TimingPointVisualisation : ControlPointVisualisation - { - public TimingPointVisualisation(ControlPoint controlPoint) - : base(controlPoint) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.YellowDark; - } - - private class NonTimingPointVisualisation : ControlPointVisualisation - { - public NonTimingPointVisualisation(ControlPoint controlPoint) - : base(controlPoint) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Green; - } - - private abstract class ControlPointVisualisation : PointVisualisation - { - protected ControlPointVisualisation(ControlPoint controlPoint) - : base(controlPoint.Time) - { - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays the control points. + /// + public class ControlPointPart : TimelinePart + { + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + + ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo; + + cpi.TimingPoints.ForEach(addTimingPoint); + + // Consider all non-timing points as the same type + cpi.SamplePoints.Select(c => (ControlPoint)c) + .Concat(cpi.EffectPoints) + .Concat(cpi.DifficultyPoints) + .Distinct() + // Non-timing points should not be added where there are timing points + .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time) + .ForEach(addNonTimingPoint); + } + + private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint)); + private void addNonTimingPoint(ControlPoint controlPoint) => Add(new NonTimingPointVisualisation(controlPoint)); + + private class TimingPointVisualisation : ControlPointVisualisation + { + public TimingPointVisualisation(ControlPoint controlPoint) + : base(controlPoint) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.YellowDark; + } + + private class NonTimingPointVisualisation : ControlPointVisualisation + { + public NonTimingPointVisualisation(ControlPoint controlPoint) + : base(controlPoint) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Green; + } + + private abstract class ControlPointVisualisation : PointVisualisation + { + protected ControlPointVisualisation(ControlPoint controlPoint) + : base(controlPoint.Time) + { + } + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index b249713581..d1fc8be005 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -1,113 +1,111 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Graphics; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts -{ - /// - /// The part of the timeline that displays the current position of the song. - /// - public class MarkerPart : TimelinePart - { - private readonly Drawable marker; - - private readonly IAdjustableClock adjustableClock; - - public MarkerPart(IAdjustableClock adjustableClock) - { - this.adjustableClock = adjustableClock; - - Add(marker = new MarkerVisualisation()); - } - - protected override bool OnDragStart(InputState state) => true; - protected override bool OnDragEnd(InputState state) => true; - protected override bool OnDrag(InputState state) - { - seekToPosition(state.Mouse.NativeState.Position); - return true; - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - seekToPosition(state.Mouse.NativeState.Position); - return true; - } - - /// - /// Seeks the to the time closest to a position on the screen relative to the . - /// - /// The position in screen coordinates. - private void seekToPosition(Vector2 screenPosition) - { - if (Beatmap.Value == null) - return; - - if (Beatmap.Value.Track.Length == double.PositiveInfinity) return; - - float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); - seekTo(markerPos / DrawWidth * Beatmap.Value.Track.Length); - } - - private void seekTo(double time) => adjustableClock.Seek(time); - - protected override void Update() - { - base.Update(); - marker.X = (float)adjustableClock.CurrentTime; - } - - protected override void LoadBeatmap(WorkingBeatmap beatmap) - { - // block base call so we don't clear our marker (can be reused on beatmap change). - } - - private class MarkerVisualisation : CompositeDrawable - { - public MarkerVisualisation() - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; - RelativePositionAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - InternalChildren = new Drawable[] - { - new Triangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Scale = new Vector2(1, -1), - Size = new Vector2(10, 5), - }, - new Triangle - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(10, 5) - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = 2, - EdgeSmoothness = new Vector2(1, 0) - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Red; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays the current position of the song. + /// + public class MarkerPart : TimelinePart + { + private readonly Drawable marker; + + private readonly IAdjustableClock adjustableClock; + + public MarkerPart(IAdjustableClock adjustableClock) + { + this.adjustableClock = adjustableClock; + + Add(marker = new MarkerVisualisation()); + } + + protected override bool OnDragStart(InputState state) => true; + protected override bool OnDragEnd(InputState state) => true; + protected override bool OnDrag(InputState state) + { + seekToPosition(state.Mouse.NativeState.Position); + return true; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + seekToPosition(state.Mouse.NativeState.Position); + return true; + } + + /// + /// Seeks the to the time closest to a position on the screen relative to the . + /// + /// The position in screen coordinates. + private void seekToPosition(Vector2 screenPosition) + { + if (Beatmap.Value == null) + return; + + if (Beatmap.Value.Track.Length == double.PositiveInfinity) return; + + float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); + adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); + } + + protected override void Update() + { + base.Update(); + marker.X = (float)adjustableClock.CurrentTime; + } + + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + // block base call so we don't clear our marker (can be reused on beatmap change). + } + + private class MarkerVisualisation : CompositeDrawable + { + public MarkerVisualisation() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + InternalChildren = new Drawable[] + { + new Triangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + Scale = new Vector2(1, -1), + Size = new Vector2(10, 5), + }, + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(10, 5) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 2, + EdgeSmoothness = new Vector2(1, 0) + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Red; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index c3c8dd9de0..c00e9ac4d5 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -1,54 +1,54 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts -{ - /// - /// Represents a part of the summary timeline.. - /// - public abstract class TimelinePart : CompositeDrawable - { - public Bindable Beatmap = new Bindable(); - - private readonly Container timeline; - - protected TimelinePart() - { - AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); - - Beatmap.ValueChanged += b => - { - updateRelativeChildSize(); - LoadBeatmap(b); - }; - } - - private void updateRelativeChildSize() - { - // the track may not be loaded completely (only has a length once it is). - if (!Beatmap.Value.Track.IsLoaded) - { - timeline.RelativeChildSize = Vector2.One; - Schedule(updateRelativeChildSize); - return; - } - - // Todo: This should be handled more gracefully - timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); - } - - protected void Add(Drawable visualisation) => timeline.Add(visualisation); - - protected virtual void LoadBeatmap(WorkingBeatmap beatmap) - { - timeline.Clear(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// Represents a part of the summary timeline.. + /// + public abstract class TimelinePart : CompositeDrawable + { + public Bindable Beatmap = new Bindable(); + + private readonly Container timeline; + + protected TimelinePart() + { + AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); + + Beatmap.ValueChanged += b => + { + updateRelativeChildSize(); + LoadBeatmap(b); + }; + } + + private void updateRelativeChildSize() + { + // the track may not be loaded completely (only has a length once it is). + if (!Beatmap.Value.Track.IsLoaded) + { + timeline.RelativeChildSize = Vector2.One; + Schedule(updateRelativeChildSize); + return; + } + + // Todo: This should be handled more gracefully + timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); + } + + protected void Add(Drawable visualisation) => timeline.Add(visualisation); + + protected virtual void LoadBeatmap(WorkingBeatmap beatmap) + { + timeline.Clear(); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 0e80c13257..0301870588 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -1,85 +1,85 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary -{ - /// - /// The timeline that sits at the bottom of the editor. - /// - public class SummaryTimeline : BottomBarContainer - { - [BackgroundDependencyLoader] - private void load(OsuColour colours, IAdjustableClock adjustableClock) - { - TimelinePart markerPart, controlPointPart, bookmarkPart, breakPart; - - Children = new Drawable[] - { - markerPart = new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, - controlPointPart = new ControlPointPart - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, - bookmarkPart = new BookmarkPart - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray5, - Children = new Drawable[] - { - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Size = new Vector2(5) - }, - new Box - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = 1, - EdgeSmoothness = new Vector2(0, 1), - }, - new Circle - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, - Size = new Vector2(5) - }, - } - }, - breakPart = new BreakPart - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Height = 0.25f - } - }; - - markerPart.Beatmap.BindTo(Beatmap); - controlPointPart.Beatmap.BindTo(Beatmap); - bookmarkPart.Beatmap.BindTo(Beatmap); - breakPart.Beatmap.BindTo(Beatmap); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary +{ + /// + /// The timeline that sits at the bottom of the editor. + /// + public class SummaryTimeline : BottomBarContainer + { + [BackgroundDependencyLoader] + private void load(OsuColour colours, IAdjustableClock adjustableClock) + { + TimelinePart markerPart, controlPointPart, bookmarkPart, breakPart; + + Children = new Drawable[] + { + markerPart = new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, + controlPointPart = new ControlPointPart + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.35f + }, + bookmarkPart = new BookmarkPart + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.35f + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray5, + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(5) + }, + new Box + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + EdgeSmoothness = new Vector2(0, 1), + }, + new Circle + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, + Size = new Vector2(5) + }, + } + }, + breakPart = new BreakPart + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Height = 0.25f + } + }; + + markerPart.Beatmap.BindTo(Beatmap); + controlPointPart.Beatmap.BindTo(Beatmap); + bookmarkPart.Beatmap.BindTo(Beatmap); + breakPart.Beatmap.BindTo(Beatmap); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs index f8d3133ae9..0b5c6018e8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations -{ - /// - /// Represents a spanning point on a timeline part. - /// - public class DurationVisualisation : Container - { - protected DurationVisualisation(double startTime, double endTime) - { - Masking = true; - CornerRadius = 5; - - RelativePositionAxes = Axes.X; - RelativeSizeAxes = Axes.Both; - X = (float)startTime; - Width = (float)(endTime - startTime); - - AddInternal(new Box { RelativeSizeAxes = Axes.Both }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations +{ + /// + /// Represents a spanning point on a timeline part. + /// + public class DurationVisualisation : Container + { + protected DurationVisualisation(double startTime, double endTime) + { + Masking = true; + CornerRadius = 5; + + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Both; + X = (float)startTime; + Width = (float)(endTime - startTime); + + AddInternal(new Box { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 4271375740..003d969ab8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations -{ - /// - /// Represents a singular point on a timeline part. - /// - public class PointVisualisation : Box - { - protected PointVisualisation(double startTime) - { - Origin = Anchor.TopCentre; - - RelativeSizeAxes = Axes.Y; - Width = 1; - EdgeSmoothness = new Vector2(1, 0); - - RelativePositionAxes = Axes.X; - X = (float)startTime; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations +{ + /// + /// Represents a singular point on a timeline part. + /// + public class PointVisualisation : Box + { + protected PointVisualisation(double startTime) + { + Origin = Anchor.TopCentre; + + RelativeSizeAxes = Axes.Y; + Width = 1; + EdgeSmoothness = new Vector2(1, 0); + + RelativePositionAxes = Axes.X; + X = (float)startTime; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8b651000fd..ea1d85bb5b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,203 +1,217 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Screens; -using osu.Game.Screens.Backgrounds; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Menus; -using osu.Game.Screens.Edit.Components.Timelines.Summary; -using osu.Framework.Allocation; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Timing; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Screens; -using osu.Game.Screens.Edit.Screens.Compose; -using osu.Game.Screens.Edit.Screens.Design; -using osu.Game.Screens.Edit.Components; - -namespace osu.Game.Screens.Edit -{ - public class Editor : OsuScreen - { - protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); - - public override bool ShowOverlaysOnEnter => false; - - private Box bottomBackground; - private Container screenContainer; - - private EditorScreen currentScreen; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - // TODO: should probably be done at a RulesetContainer level to share logic with Player. - var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); - var adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - adjustableClock.ChangeSource(sourceClock); - - dependencies.CacheAs(adjustableClock); - dependencies.CacheAs(adjustableClock); - - EditorMenuBar menuBar; - TimeInfoContainer timeInfo; - SummaryTimeline timeline; - PlaybackControl playback; - - Children = new[] - { - new Container - { - Name = "Screen container", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 60 }, - Child = screenContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true - } - }, - new Container - { - Name = "Top bar", - RelativeSizeAxes = Axes.X, - Height = 40, - Child = menuBar = new EditorMenuBar - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new MenuItem("File") - { - Items = new[] - { - new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, Exit) - } - } - } - } - }, - new Container - { - Name = "Bottom bar", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 60, - Children = new Drawable[] - { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Vertical = 5, Horizontal = 10 }, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 220), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 220) - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 10 }, - Child = timeInfo = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, - }, - timeline = new SummaryTimeline - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10 }, - Child = playback = new PlaybackControl { RelativeSizeAxes = Axes.Both }, - } - }, - } - }, - } - } - }, - }; - - timeInfo.Beatmap.BindTo(Beatmap); - timeline.Beatmap.BindTo(Beatmap); - playback.Beatmap.BindTo(Beatmap); - menuBar.Mode.ValueChanged += onModeChanged; - - bottomBackground.Colour = colours.Gray2; - - } - - private void exportBeatmap() - { - Beatmap.Value.Save(); - } - - private void onModeChanged(EditorScreenMode mode) - { - currentScreen?.Exit(); - - switch (mode) - { - case EditorScreenMode.Compose: - currentScreen = new Compose(); - break; - case EditorScreenMode.Design: - currentScreen = new Design(); - break; - default: - currentScreen = new EditorScreen(); - break; - } - - currentScreen.Beatmap.BindTo(Beatmap); - screenContainer.Add(currentScreen); - } - - protected override void OnResuming(Screen last) - { - Beatmap.Value.Track?.Stop(); - base.OnResuming(last); - } - - protected override void OnEntering(Screen last) - { - base.OnEntering(last); - Background.FadeColour(Color4.DarkGray, 500); - Beatmap.Value.Track?.Stop(); - } - - protected override bool OnExiting(Screen next) - { - Background.FadeColour(Color4.White, 500); - if (Beatmap.Value.Track != null) - { - Beatmap.Value.Track.Tempo.Value = 1; - Beatmap.Value.Track.Start(); - } - return base.OnExiting(next); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Screens; +using osu.Game.Screens.Backgrounds; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Menus; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Framework.Allocation; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Timing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Screens; +using osu.Game.Screens.Edit.Screens.Compose; +using osu.Game.Screens.Edit.Screens.Design; +using osu.Game.Screens.Edit.Components; + +namespace osu.Game.Screens.Edit +{ + public class Editor : OsuScreen + { + protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); + + public override bool ShowOverlaysOnEnter => false; + + private Box bottomBackground; + private Container screenContainer; + + private EditorScreen currentScreen; + + private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + + private EditorClock clock; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(parent); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + // TODO: should probably be done at a RulesetContainer level to share logic with Player. + var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); + clock = new EditorClock(Beatmap.Value.Beatmap.ControlPointInfo, beatDivisor) { IsCoupled = false }; + clock.ChangeSource(sourceClock); + + dependencies.CacheAs(clock); + dependencies.CacheAs(clock); + dependencies.Cache(beatDivisor); + + EditorMenuBar menuBar; + TimeInfoContainer timeInfo; + SummaryTimeline timeline; + PlaybackControl playback; + + Children = new[] + { + new Container + { + Name = "Screen container", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 40, Bottom = 60 }, + Child = screenContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true + } + }, + new Container + { + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Height = 40, + Child = menuBar = new EditorMenuBar + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] + { + new MenuItem("File") + { + Items = new[] + { + new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit", MenuItemType.Standard, Exit) + } + } + } + } + }, + new Container + { + Name = "Bottom bar", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 60, + Children = new Drawable[] + { + bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Vertical = 5, Horizontal = 10 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 220) + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 10 }, + Child = timeInfo = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, + }, + timeline = new SummaryTimeline + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10 }, + Child = playback = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + } + }, + } + }, + } + } + }, + }; + + timeInfo.Beatmap.BindTo(Beatmap); + timeline.Beatmap.BindTo(Beatmap); + playback.Beatmap.BindTo(Beatmap); + menuBar.Mode.ValueChanged += onModeChanged; + + bottomBackground.Colour = colours.Gray2; + } + + private void exportBeatmap() + { + Beatmap.Value.Save(); + } + + private void onModeChanged(EditorScreenMode mode) + { + currentScreen?.Exit(); + + switch (mode) + { + case EditorScreenMode.Compose: + currentScreen = new Compose(); + break; + case EditorScreenMode.Design: + currentScreen = new Design(); + break; + default: + currentScreen = new EditorScreen(); + break; + } + + currentScreen.Beatmap.BindTo(Beatmap); + screenContainer.Add(currentScreen); + } + + protected override bool OnWheel(InputState state) + { + if (state.Mouse.WheelDelta > 0) + clock.SeekBackward(true); + else + clock.SeekForward(true); + return true; + } + + protected override void OnResuming(Screen last) + { + Beatmap.Value.Track?.Stop(); + base.OnResuming(last); + } + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + Background.FadeColour(Color4.DarkGray, 500); + Beatmap.Value.Track?.Stop(); + } + + protected override bool OnExiting(Screen next) + { + Background.FadeColour(Color4.White, 500); + if (Beatmap.Value.Track != null) + { + Beatmap.Value.Track.Tempo.Value = 1; + Beatmap.Value.Track.Start(); + } + return base.OnExiting(next); + } + } +} diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs new file mode 100644 index 0000000000..67025f0620 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -0,0 +1,117 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit.Screens.Compose; + +namespace osu.Game.Screens.Edit +{ + /// + /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. + /// + public class EditorClock : DecoupleableInterpolatingFramedClock + { + public ControlPointInfo ControlPointInfo; + + private readonly BindableBeatDivisor beatDivisor; + + public EditorClock(ControlPointInfo controlPointInfo, BindableBeatDivisor beatDivisor) + { + this.beatDivisor = beatDivisor; + + ControlPointInfo = controlPointInfo; + } + + /// + /// Seek to the closest snappable beat from a time. + /// + /// The raw position which should be seeked around. + /// Whether the seek could be performed. + public bool SeekSnapped(double position) + { + var timingPoint = ControlPointInfo.TimingPointAt(position); + double beatSnapLength = timingPoint.BeatLength / beatDivisor; + + // We will be snapping to beats within the timing point + position -= timingPoint.Time; + + // Determine the index from the current timing point of the closest beat to position + int closestBeat = (int)Math.Round(position / beatSnapLength); + position = timingPoint.Time + closestBeat * beatSnapLength; + + // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to + // the next timing point's start time + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + if (position > nextTimingPoint?.Time) + position = nextTimingPoint.Time; + + return Seek(position); + } + + /// + /// Seeks backwards by one beat length. + /// + /// Whether to snap to the closest beat after seeking. + public void SeekBackward(bool snapped = false) => seek(-1, snapped); + + /// + /// Seeks forwards by one beat length. + /// + /// Whether to snap to the closest beat after seeking. + public void SeekForward(bool snapped = false) => seek(1, snapped); + + private void seek(int direction, bool snapped) + { + var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); + if (direction < 0 && timingPoint.Time == CurrentTime) + { + // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into + int activeIndex = ControlPointInfo.TimingPoints.IndexOf(timingPoint); + while (activeIndex > 0 && CurrentTime == timingPoint.Time) + timingPoint = ControlPointInfo.TimingPoints[--activeIndex]; + } + + double seekAmount = timingPoint.BeatLength / beatDivisor; + double seekTime = CurrentTime + seekAmount * direction; + + if (!snapped || ControlPointInfo.TimingPoints.Count == 0) + { + Seek(seekTime); + return; + } + + // We will be snapping to beats within timingPoint + seekTime -= timingPoint.Time; + + // Determine the index from timingPoint of the closest beat to seekTime, accounting for scrolling direction + int closestBeat; + if (direction > 0) + closestBeat = (int)Math.Floor(seekTime / seekAmount); + else + closestBeat = (int)Math.Ceiling(seekTime / seekAmount); + + seekTime = timingPoint.Time + closestBeat * seekAmount; + + // Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this. + // Instead, we'll go to the next beat in the direction when this is the case + if (Precision.AlmostEquals(CurrentTime, seekTime)) + { + closestBeat += direction > 0 ? 1 : -1; + seekTime = timingPoint.Time + closestBeat * seekAmount; + } + + if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) + seekTime = timingPoint.Time; + + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + if (seekTime > nextTimingPoint?.Time) + seekTime = nextTimingPoint.Time; + + Seek(seekTime); + } + } +} diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index ab699d1832..cb7c0fa803 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -1,190 +1,190 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Game.Screens.Edit.Screens; - -namespace osu.Game.Screens.Edit.Menus -{ - public class EditorMenuBar : OsuMenu - { - public readonly Bindable Mode = new Bindable(); - - public EditorMenuBar() - : base(Direction.Horizontal, true) - { - RelativeSizeAxes = Axes.X; - - MaskingContainer.CornerRadius = 0; - ItemsContainer.Padding = new MarginPadding { Left = 100 }; - BackgroundColour = OsuColour.FromHex("111"); - - ScreenSelectionTabControl tabControl; - AddRangeInternal(new Drawable[] - { - tabControl = new ScreenSelectionTabControl - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -15 - } - }); - - Mode.BindTo(tabControl.Current); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Mode.TriggerChange(); - } - - protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); - - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableEditorBarMenuItem(item); - - private class DrawableEditorBarMenuItem : DrawableOsuMenuItem - { - private BackgroundBox background; - - public DrawableEditorBarMenuItem(MenuItem item) - : base(item) - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - StateChanged += stateChanged; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - ForegroundColour = colours.BlueLight; - BackgroundColour = Color4.Transparent; - ForegroundColourHover = Color4.White; - BackgroundColourHover = colours.Gray3; - } - - public override void SetFlowDirection(Direction direction) - { - AutoSizeAxes = Axes.Both; - } - - protected override void UpdateBackgroundColour() - { - if (State == MenuItemState.Selected) - Background.FadeColour(BackgroundColourHover); - else - base.UpdateBackgroundColour(); - } - - protected override void UpdateForegroundColour() - { - if (State == MenuItemState.Selected) - Foreground.FadeColour(ForegroundColourHover); - else - base.UpdateForegroundColour(); - } - - private void stateChanged(MenuItemState newState) - { - if (newState == MenuItemState.Selected) - background.Expand(); - else - background.Contract(); - } - - protected override Drawable CreateBackground() => background = new BackgroundBox(); - protected override DrawableOsuMenuItem.TextContainer CreateTextContainer() => new TextContainer(); - - private new class TextContainer : DrawableOsuMenuItem.TextContainer - { - public TextContainer() - { - NormalText.TextSize = BoldText.TextSize = 14; - NormalText.Margin = BoldText.Margin = new MarginPadding { Horizontal = 10, Vertical = MARGIN_VERTICAL }; - } - } - - private class BackgroundBox : CompositeDrawable - { - private readonly Container innerBackground; - - public BackgroundBox() - { - RelativeSizeAxes = Axes.Both; - Masking = true; - InternalChild = innerBackground = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - /// - /// Expands the background such that it doesn't show the bottom corners. - /// - public void Expand() => innerBackground.Height = 2; - - /// - /// Contracts the background such that it shows the bottom corners. - /// - public void Contract() => innerBackground.Height = 1; - } - } - - private class SubMenu : OsuMenu - { - public SubMenu() - : base(Direction.Vertical) - { - OriginPosition = new Vector2(5, 1); - ItemsContainer.Padding = new MarginPadding { Top = 5, Bottom = 5 }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Gray3; - } - - protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); - - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableSubMenuItem(item); - - private class DrawableSubMenuItem : DrawableOsuMenuItem - { - public DrawableSubMenuItem(MenuItem item) - : base(item) - { - } - - protected override bool OnHover(InputState state) - { - if (Item is EditorMenuItemSpacer) - return true; - return base.OnHover(state); - } - - protected override bool OnClick(InputState state) - { - if (Item is EditorMenuItemSpacer) - return true; - return base.OnClick(state); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Game.Screens.Edit.Screens; + +namespace osu.Game.Screens.Edit.Menus +{ + public class EditorMenuBar : OsuMenu + { + public readonly Bindable Mode = new Bindable(); + + public EditorMenuBar() + : base(Direction.Horizontal, true) + { + RelativeSizeAxes = Axes.X; + + MaskingContainer.CornerRadius = 0; + ItemsContainer.Padding = new MarginPadding { Left = 100 }; + BackgroundColour = OsuColour.FromHex("111"); + + ScreenSelectionTabControl tabControl; + AddRangeInternal(new Drawable[] + { + tabControl = new ScreenSelectionTabControl + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -15 + } + }); + + Mode.BindTo(tabControl.Current); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Mode.TriggerChange(); + } + + protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableEditorBarMenuItem(item); + + private class DrawableEditorBarMenuItem : DrawableOsuMenuItem + { + private BackgroundBox background; + + public DrawableEditorBarMenuItem(MenuItem item) + : base(item) + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + StateChanged += stateChanged; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ForegroundColour = colours.BlueLight; + BackgroundColour = Color4.Transparent; + ForegroundColourHover = Color4.White; + BackgroundColourHover = colours.Gray3; + } + + public override void SetFlowDirection(Direction direction) + { + AutoSizeAxes = Axes.Both; + } + + protected override void UpdateBackgroundColour() + { + if (State == MenuItemState.Selected) + Background.FadeColour(BackgroundColourHover); + else + base.UpdateBackgroundColour(); + } + + protected override void UpdateForegroundColour() + { + if (State == MenuItemState.Selected) + Foreground.FadeColour(ForegroundColourHover); + else + base.UpdateForegroundColour(); + } + + private void stateChanged(MenuItemState newState) + { + if (newState == MenuItemState.Selected) + background.Expand(); + else + background.Contract(); + } + + protected override Drawable CreateBackground() => background = new BackgroundBox(); + protected override DrawableOsuMenuItem.TextContainer CreateTextContainer() => new TextContainer(); + + private new class TextContainer : DrawableOsuMenuItem.TextContainer + { + public TextContainer() + { + NormalText.TextSize = BoldText.TextSize = 14; + NormalText.Margin = BoldText.Margin = new MarginPadding { Horizontal = 10, Vertical = MARGIN_VERTICAL }; + } + } + + private class BackgroundBox : CompositeDrawable + { + private readonly Container innerBackground; + + public BackgroundBox() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + InternalChild = innerBackground = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + /// + /// Expands the background such that it doesn't show the bottom corners. + /// + public void Expand() => innerBackground.Height = 2; + + /// + /// Contracts the background such that it shows the bottom corners. + /// + public void Contract() => innerBackground.Height = 1; + } + } + + private class SubMenu : OsuMenu + { + public SubMenu() + : base(Direction.Vertical) + { + OriginPosition = new Vector2(5, 1); + ItemsContainer.Padding = new MarginPadding { Top = 5, Bottom = 5 }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray3; + } + + protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); + + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableSubMenuItem(item); + + private class DrawableSubMenuItem : DrawableOsuMenuItem + { + public DrawableSubMenuItem(MenuItem item) + : base(item) + { + } + + protected override bool OnHover(InputState state) + { + if (Item is EditorMenuItemSpacer) + return true; + return base.OnHover(state); + } + + protected override bool OnClick(InputState state) + { + if (Item is EditorMenuItemSpacer) + return true; + return base.OnClick(state); + } + } + } + } +} diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs b/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs index ec936fb023..0ef1ad8c6b 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Menus -{ - public class EditorMenuItem : OsuMenuItem - { - private const int min_text_length = 40; - - public EditorMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : base(text.PadRight(min_text_length), type) - { - } - - public EditorMenuItem(string text, MenuItemType type, Action action) - : base(text.PadRight(min_text_length), type, action) - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Menus +{ + public class EditorMenuItem : OsuMenuItem + { + private const int min_text_length = 40; + + public EditorMenuItem(string text, MenuItemType type = MenuItemType.Standard) + : base(text.PadRight(min_text_length), type) + { + } + + public EditorMenuItem(string text, MenuItemType type, Action action) + : base(text.PadRight(min_text_length), type, action) + { + } + } +} diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs b/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs index 1b16b886b0..91b40a2cfb 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Edit.Menus -{ - public class EditorMenuItemSpacer : EditorMenuItem - { - public EditorMenuItemSpacer() - : base(" ") - { - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Screens.Edit.Menus +{ + public class EditorMenuItemSpacer : EditorMenuItem + { + public EditorMenuItemSpacer() + : base(" ") + { + } + } +} diff --git a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs index a7569330bd..1471a37a29 100644 --- a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs @@ -1,75 +1,75 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Screens; -using OpenTK; - -namespace osu.Game.Screens.Edit.Menus -{ - public class ScreenSelectionTabControl : OsuTabControl - { - public ScreenSelectionTabControl() - { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - - TabContainer.RelativeSizeAxes &= ~Axes.X; - TabContainer.AutoSizeAxes = Axes.X; - TabContainer.Padding = new MarginPadding(); - - Add(new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = Color4.White.Opacity(0.2f), - }); - - Current.Value = EditorScreenMode.Compose; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = colours.Yellow; - } - - protected override Dropdown CreateDropdown() => null; - - protected override TabItem CreateTabItem(EditorScreenMode value) => new TabItem(value); - - private class TabItem : OsuTabItem - { - private const float transition_length = 250; - - public TabItem(EditorScreenMode value) - : base(value) - { - Text.Margin = new MarginPadding(); - Text.Anchor = Anchor.CentreLeft; - Text.Origin = Anchor.CentreLeft; - } - - protected override void OnActivated() - { - base.OnActivated(); - Bar.ScaleTo(new Vector2(1, 5), transition_length, Easing.OutQuint); - } - - protected override void OnDeactivated() - { - base.OnDeactivated(); - Bar.ScaleTo(Vector2.One, transition_length, Easing.OutQuint); - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Screens; +using OpenTK; + +namespace osu.Game.Screens.Edit.Menus +{ + public class ScreenSelectionTabControl : OsuTabControl + { + public ScreenSelectionTabControl() + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + TabContainer.RelativeSizeAxes &= ~Axes.X; + TabContainer.AutoSizeAxes = Axes.X; + TabContainer.Padding = new MarginPadding(); + + Add(new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = Color4.White.Opacity(0.2f), + }); + + Current.Value = EditorScreenMode.Compose; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Yellow; + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(EditorScreenMode value) => new TabItem(value); + + private class TabItem : OsuTabItem + { + private const float transition_length = 250; + + public TabItem(EditorScreenMode value) + : base(value) + { + Text.Margin = new MarginPadding(); + Text.Anchor = Anchor.CentreLeft; + Text.Origin = Anchor.CentreLeft; + } + + protected override void OnActivated() + { + base.OnActivated(); + Bar.ScaleTo(new Vector2(1, 5), transition_length, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + base.OnDeactivated(); + Bar.ScaleTo(Vector2.One, transition_length, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs index 0b30aeef8d..21e48d8b0a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs @@ -1,397 +1,397 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; - -namespace osu.Game.Screens.Edit.Screens.Compose -{ - public class BeatDivisorControl : CompositeDrawable - { - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - - public BeatDivisorControl(BindableBeatDivisor beatDivisor) - { - this.beatDivisor.BindTo(beatDivisor); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - new Box - { - Name = "Gray Background", - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4 - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Name = "Black Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - new TickSliderBar(beatDivisor, BindableBeatDivisor.VALID_DIVISORS) - { - RelativeSizeAxes = Axes.Both, - } - } - } - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4 - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5 }, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new DivisorButton - { - Icon = FontAwesome.fa_chevron_left, - Action = beatDivisor.Previous - }, - new DivisorText(beatDivisor), - new DivisorButton - { - Icon = FontAwesome.fa_chevron_right, - Action = beatDivisor.Next - } - }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 20), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 20) - } - } - } - } - } - }, - new Drawable[] - { - new TextFlowContainer(s => s.TextSize = 14) - { - Padding = new MarginPadding { Horizontal = 15 }, - Text = "beat snap divisor", - RelativeSizeAxes = Axes.X, - TextAnchor = Anchor.TopCentre - }, - } - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 30), - new Dimension(GridSizeMode.Absolute, 25), - } - } - }; - } - - private class DivisorText : SpriteText - { - private readonly Bindable beatDivisor = new Bindable(); - - public DivisorText(BindableBeatDivisor beatDivisor) - { - this.beatDivisor.BindTo(beatDivisor); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.BlueLighter; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - beatDivisor.ValueChanged += v => updateText(); - updateText(); - } - - private void updateText() => Text = $"1/{beatDivisor.Value}"; - } - - private class DivisorButton : IconButton - { - public DivisorButton() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - // Small offset to look a bit better centered along with the divisor text - Y = 1; - - ButtonSize = new Vector2(20); - IconScale = new Vector2(0.6f); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IconColour = Color4.Black; - HoverColour = colours.Gray7; - FlashColour = colours.Gray9; - } - } - - private class TickSliderBar : SliderBar - { - private Marker marker; - - private readonly BindableBeatDivisor beatDivisor; - private readonly int[] availableDivisors; - - public TickSliderBar(BindableBeatDivisor beatDivisor, params int[] divisors) - { - CurrentNumber.BindTo(this.beatDivisor = beatDivisor); - availableDivisors = divisors; - - Padding = new MarginPadding { Horizontal = 5 }; - } - - [BackgroundDependencyLoader] - private void load() - { - foreach (var t in availableDivisors) - { - AddInternal(new Tick(t) - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopCentre, - RelativePositionAxes = Axes.X, - X = getMappedPosition(t) - }); - } - - AddInternal(marker = new Marker()); - - CurrentNumber.ValueChanged += v => - { - marker.MoveToX(getMappedPosition(v), 100, Easing.OutQuint); - marker.Flash(); - }; - } - - protected override void UpdateValue(float value) - { - } - - public override bool HandleKeyboardInput => IsHovered && !CurrentNumber.Disabled; - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - switch (args.Key) - { - case Key.Right: - beatDivisor.Next(); - OnUserChange(); - return true; - case Key.Left: - beatDivisor.Previous(); - OnUserChange(); - return true; - default: - return false; - } - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - marker.Active = true; - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - marker.Active = false; - return base.OnMouseUp(state, args); - } - - protected override bool OnClick(InputState state) - { - handleMouseInput(state); - return true; - } - - protected override bool OnDrag(InputState state) - { - handleMouseInput(state); - return true; - } - - private void handleMouseInput(InputState state) - { - // copied from SliderBar so we can do custom spacing logic. - var xPosition = (ToLocalSpace(state?.Mouse.NativeState.Position ?? Vector2.Zero).X - RangePadding) / UsableWidth; - - CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); - OnUserChange(); - } - - private float getMappedPosition(float divisor) => (float)Math.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f); - - private class Tick : CompositeDrawable - { - private readonly int divisor; - - public Tick(int divisor) - { - this.divisor = divisor; - Size = new Vector2(2.5f, 10); - - InternalChild = new Box { RelativeSizeAxes = Axes.Both }; - - CornerRadius = 0.5f; - Masking = true; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = getColourForDivisor(divisor, colours); - } - - private ColourInfo getColourForDivisor(int divisor, OsuColour colours) - { - switch (divisor) - { - case 2: - return colours.BlueLight; - case 4: - return colours.Blue; - case 8: - return colours.BlueDarker; - case 16: - return colours.PurpleDark; - case 3: - return colours.YellowLight; - case 6: - return colours.Yellow; - case 12: - return colours.YellowDarker; - default: - return Color4.White; - } - } - } - - private class Marker : CompositeDrawable - { - private Color4 defaultColour; - - private const float size = 7; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = defaultColour = colours.Gray4; - Anchor = Anchor.TopLeft; - Origin = Anchor.TopCentre; - - Width = size; - RelativeSizeAxes = Axes.Y; - RelativePositionAxes = Axes.X; - - InternalChildren = new Drawable[] - { - new Box - { - Width = 2, - RelativeSizeAxes = Axes.Y, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.2f), Color4.White), - Blending = BlendingMode.Additive, - }, - new EquilateralTriangle - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Height = size, - EdgeSmoothness = new Vector2(1), - Colour = Color4.White, - } - }; - } - - private bool active; - - public bool Active - { - get => active; - set - { - this.FadeColour(value ? Color4.White : defaultColour, 500, Easing.OutQuint); - active = value; - } - } - - public void Flash() - { - bool wasActive = active; - - Active = true; - - if (wasActive) return; - - using (BeginDelayedSequence(50)) - Active = false; - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace osu.Game.Screens.Edit.Screens.Compose +{ + public class BeatDivisorControl : CompositeDrawable + { + private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + + public BeatDivisorControl(BindableBeatDivisor beatDivisor) + { + this.beatDivisor.BindTo(beatDivisor); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Name = "Gray Background", + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray4 + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Name = "Black Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + new TickSliderBar(beatDivisor, BindableBeatDivisor.VALID_DIVISORS) + { + RelativeSizeAxes = Axes.Both, + } + } + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray4 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new DivisorButton + { + Icon = FontAwesome.fa_chevron_left, + Action = beatDivisor.Previous + }, + new DivisorText(beatDivisor), + new DivisorButton + { + Icon = FontAwesome.fa_chevron_right, + Action = beatDivisor.Next + } + }, + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 20), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 20) + } + } + } + } + } + }, + new Drawable[] + { + new TextFlowContainer(s => s.TextSize = 14) + { + Padding = new MarginPadding { Horizontal = 15 }, + Text = "beat snap divisor", + RelativeSizeAxes = Axes.X, + TextAnchor = Anchor.TopCentre + }, + } + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 25), + } + } + }; + } + + private class DivisorText : SpriteText + { + private readonly Bindable beatDivisor = new Bindable(); + + public DivisorText(BindableBeatDivisor beatDivisor) + { + this.beatDivisor.BindTo(beatDivisor); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatDivisor.ValueChanged += v => updateText(); + updateText(); + } + + private void updateText() => Text = $"1/{beatDivisor.Value}"; + } + + private class DivisorButton : IconButton + { + public DivisorButton() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + // Small offset to look a bit better centered along with the divisor text + Y = 1; + + ButtonSize = new Vector2(20); + IconScale = new Vector2(0.6f); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IconColour = Color4.Black; + HoverColour = colours.Gray7; + FlashColour = colours.Gray9; + } + } + + private class TickSliderBar : SliderBar + { + private Marker marker; + + private readonly BindableBeatDivisor beatDivisor; + private readonly int[] availableDivisors; + + public TickSliderBar(BindableBeatDivisor beatDivisor, params int[] divisors) + { + CurrentNumber.BindTo(this.beatDivisor = beatDivisor); + availableDivisors = divisors; + + Padding = new MarginPadding { Horizontal = 5 }; + } + + [BackgroundDependencyLoader] + private void load() + { + foreach (var t in availableDivisors) + { + AddInternal(new Tick(t) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopCentre, + RelativePositionAxes = Axes.X, + X = getMappedPosition(t) + }); + } + + AddInternal(marker = new Marker()); + + CurrentNumber.ValueChanged += v => + { + marker.MoveToX(getMappedPosition(v), 100, Easing.OutQuint); + marker.Flash(); + }; + } + + protected override void UpdateValue(float value) + { + } + + public override bool HandleKeyboardInput => IsHovered && !CurrentNumber.Disabled; + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + switch (args.Key) + { + case Key.Right: + beatDivisor.Next(); + OnUserChange(); + return true; + case Key.Left: + beatDivisor.Previous(); + OnUserChange(); + return true; + default: + return false; + } + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + marker.Active = true; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + marker.Active = false; + return base.OnMouseUp(state, args); + } + + protected override bool OnClick(InputState state) + { + handleMouseInput(state); + return true; + } + + protected override bool OnDrag(InputState state) + { + handleMouseInput(state); + return true; + } + + private void handleMouseInput(InputState state) + { + // copied from SliderBar so we can do custom spacing logic. + var xPosition = (ToLocalSpace(state?.Mouse.NativeState.Position ?? Vector2.Zero).X - RangePadding) / UsableWidth; + + CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); + OnUserChange(); + } + + private float getMappedPosition(float divisor) => (float)Math.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f); + + private class Tick : CompositeDrawable + { + private readonly int divisor; + + public Tick(int divisor) + { + this.divisor = divisor; + Size = new Vector2(2.5f, 10); + + InternalChild = new Box { RelativeSizeAxes = Axes.Both }; + + CornerRadius = 0.5f; + Masking = true; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = getColourForDivisor(divisor, colours); + } + + private ColourInfo getColourForDivisor(int divisor, OsuColour colours) + { + switch (divisor) + { + case 2: + return colours.BlueLight; + case 4: + return colours.Blue; + case 8: + return colours.BlueDarker; + case 16: + return colours.PurpleDark; + case 3: + return colours.YellowLight; + case 6: + return colours.Yellow; + case 12: + return colours.YellowDarker; + default: + return Color4.White; + } + } + } + + private class Marker : CompositeDrawable + { + private Color4 defaultColour; + + private const float size = 7; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = defaultColour = colours.Gray4; + Anchor = Anchor.TopLeft; + Origin = Anchor.TopCentre; + + Width = size; + RelativeSizeAxes = Axes.Y; + RelativePositionAxes = Axes.X; + + InternalChildren = new Drawable[] + { + new Box + { + Width = 2, + RelativeSizeAxes = Axes.Y, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.2f), Color4.White), + Blending = BlendingMode.Additive, + }, + new EquilateralTriangle + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Height = size, + EdgeSmoothness = new Vector2(1), + Colour = Color4.White, + } + }; + } + + private bool active; + + public bool Active + { + get => active; + set + { + this.FadeColour(value ? Color4.White : defaultColour, 500, Easing.OutQuint); + active = value; + } + } + + public void Flash() + { + bool wasActive = active; + + Active = true; + + if (wasActive) return; + + using (BeginDelayedSequence(50)) + Active = false; + } + } + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs index 8eb3f1347e..b7dce8c96e 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs @@ -1,39 +1,39 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Configuration; - -namespace osu.Game.Screens.Edit.Screens.Compose -{ - public class BindableBeatDivisor : BindableNumber - { - public static readonly int[] VALID_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; - - public BindableBeatDivisor(int value = 1) - : base(value) - { - } - - public void Next() => Value = VALID_DIVISORS[Math.Min(VALID_DIVISORS.Length - 1, Array.IndexOf(VALID_DIVISORS, Value) + 1)]; - - public void Previous() => Value = VALID_DIVISORS[Math.Max(0, Array.IndexOf(VALID_DIVISORS, Value) - 1)]; - - public override int Value - { - get { return base.Value; } - set - { - if (!VALID_DIVISORS.Contains(value)) - throw new ArgumentOutOfRangeException($"Provided divisor is not in {nameof(VALID_DIVISORS)}"); - - base.Value = value; - } - } - - protected override int DefaultMinValue => VALID_DIVISORS.First(); - protected override int DefaultMaxValue => VALID_DIVISORS.Last(); - protected override int DefaultPrecision => 1; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Configuration; + +namespace osu.Game.Screens.Edit.Screens.Compose +{ + public class BindableBeatDivisor : BindableNumber + { + public static readonly int[] VALID_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; + + public BindableBeatDivisor(int value = 1) + : base(value) + { + } + + public void Next() => Value = VALID_DIVISORS[Math.Min(VALID_DIVISORS.Length - 1, Array.IndexOf(VALID_DIVISORS, Value) + 1)]; + + public void Previous() => Value = VALID_DIVISORS[Math.Max(0, Array.IndexOf(VALID_DIVISORS, Value) - 1)]; + + public override int Value + { + get { return base.Value; } + set + { + if (!VALID_DIVISORS.Contains(value)) + throw new ArgumentOutOfRangeException($"Provided divisor is not in {nameof(VALID_DIVISORS)}"); + + base.Value = value; + } + } + + protected override int DefaultMinValue => VALID_DIVISORS.First(); + protected override int DefaultMaxValue => VALID_DIVISORS.Last(); + protected override int DefaultPrecision => 1; + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index 91adc8324a..fea4883144 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -1,121 +1,118 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; - -namespace osu.Game.Screens.Edit.Screens.Compose -{ - public class Compose : EditorScreen - { - private const float vertical_margins = 10; - private const float horizontal_margins = 20; - - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - - private Container composerContainer; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - [BackgroundDependencyLoader] - private void load() - { - dependencies.Cache(beatDivisor); - - ScrollableTimeline timeline; - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new Container - { - Name = "Timeline", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, - new Container - { - Name = "Timeline content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } - }, - new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } - }, - }, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 90), - } - }, - } - } - } - }, - new Drawable[] - { - composerContainer = new Container - { - Name = "Composer content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, - } - } - }, - RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) } - }, - }; - - timeline.Beatmap.BindTo(Beatmap); - - var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); - if (ruleset == null) - { - Logger.Log("Beatmap doesn't have a ruleset assigned."); - // ExitRequested?.Invoke(); - return; - } - - var composer = ruleset.CreateHitObjectComposer(); - if (composer == null) - { - Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); - // ExitRequested?.Invoke(); - return; - } - - composerContainer.Child = composer; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Game.Screens.Edit.Screens.Compose.Timeline; + +namespace osu.Game.Screens.Edit.Screens.Compose +{ + public class Compose : EditorScreen + { + private const float vertical_margins = 10; + private const float horizontal_margins = 20; + + private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + + private Container composerContainer; + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] BindableBeatDivisor beatDivisor) + { + if (beatDivisor != null) + this.beatDivisor.BindTo(beatDivisor); + + ScrollableTimeline timeline; + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + Name = "Timeline", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new Container + { + Name = "Timeline content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + }, + new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } + }, + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 90), + } + }, + } + } + } + }, + new Drawable[] + { + composerContainer = new Container + { + Name = "Composer content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + } + } + }, + RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) } + }, + }; + + timeline.Beatmap.BindTo(Beatmap); + + var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); + if (ruleset == null) + { + Logger.Log("Beatmap doesn't have a ruleset assigned."); + // ExitRequested?.Invoke(); + return; + } + + var composer = ruleset.CreateHitObjectComposer(); + if (composer == null) + { + Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); + // ExitRequested?.Invoke(); + return; + } + + composerContainer.Child = composer; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs index 49cf078d36..c46f9a1b7f 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs @@ -1,38 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using OpenTK.Graphics; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class BorderLayer : Container - { - protected override Container Content => content; - private readonly Container content; - - public BorderLayer() - { - InternalChildren = new Drawable[] - { - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - }, - content = new Container { RelativeSizeAxes = Axes.Both } - }; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + public class BorderLayer : Container + { + protected override Container Content => content; + private readonly Container content; + + public BorderLayer() + { + InternalChildren = new Drawable[] + { + new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + }, + content = new Container { RelativeSizeAxes = Axes.Both } + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 51bb61b607..d8200d6c37 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -1,92 +1,92 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Rulesets.Edit; -using OpenTK.Graphics; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - /// - /// A layer that handles and displays drag selection for a collection of s. - /// - public class DragLayer : CompositeDrawable - { - private readonly Action performSelection; - - /// - /// Invoked when the drag selection has finished. - /// - public event Action DragEnd; - - private Drawable box; - - /// - /// Creates a new . - /// - /// The selectable s. - public DragLayer(Action performSelection) - { - this.performSelection = performSelection; - - RelativeSizeAxes = Axes.Both; - AlwaysPresent = true; - Alpha = 0; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = box = new Container - { - Masking = true, - BorderColour = Color4.White, - BorderThickness = MaskSelection.BORDER_RADIUS, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - } - }; - } - - protected override bool OnDragStart(InputState state) - { - this.FadeIn(250, Easing.OutQuint); - return true; - } - - protected override bool OnDrag(InputState state) - { - var dragPosition = state.Mouse.NativeState.Position; - var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; - - var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); - - // We use AABBFloat instead of RectangleF since it handles negative sizes for us - var dragRectangle = dragQuad.AABBFloat; - - var topLeft = ToLocalSpace(dragRectangle.TopLeft); - var bottomRight = ToLocalSpace(dragRectangle.BottomRight); - - box.Position = topLeft; - box.Size = bottomRight - topLeft; - - performSelection?.Invoke(dragRectangle); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - this.FadeOut(250, Easing.OutQuint); - DragEnd?.Invoke(); - return true; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Rulesets.Edit; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + /// + /// A layer that handles and displays drag selection for a collection of s. + /// + public class DragLayer : CompositeDrawable + { + private readonly Action performSelection; + + /// + /// Invoked when the drag selection has finished. + /// + public event Action DragEnd; + + private Drawable box; + + /// + /// Creates a new . + /// + /// The selectable s. + public DragLayer(Action performSelection) + { + this.performSelection = performSelection; + + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = box = new Container + { + Masking = true, + BorderColour = Color4.White, + BorderThickness = MaskSelection.BORDER_RADIUS, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f + } + }; + } + + protected override bool OnDragStart(InputState state) + { + this.FadeIn(250, Easing.OutQuint); + return true; + } + + protected override bool OnDrag(InputState state) + { + var dragPosition = state.Mouse.NativeState.Position; + var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; + + var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); + + // We use AABBFloat instead of RectangleF since it handles negative sizes for us + var dragRectangle = dragQuad.AABBFloat; + + var topLeft = ToLocalSpace(dragRectangle.TopLeft); + var bottomRight = ToLocalSpace(dragRectangle.BottomRight); + + box.Position = topLeft; + box.Size = bottomRight - topLeft; + + performSelection?.Invoke(dragRectangle); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + this.FadeOut(250, Easing.OutQuint); + DragEnd?.Invoke(); + return true; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 423cf0ed29..ede98d986a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -1,93 +1,93 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class HitObjectMaskLayer : CompositeDrawable - { - private readonly Playfield playfield; - private readonly HitObjectComposer composer; - - private MaskContainer maskContainer; - - public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) - { - // we need the playfield as HitObjects may not be initialised until its BDL. - this.playfield = playfield; - - this.composer = composer; - - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - maskContainer = new MaskContainer(); - - var maskSelection = composer.CreateMaskSelection(); - - maskContainer.MaskSelected += maskSelection.HandleSelected; - maskContainer.MaskDeselected += maskSelection.HandleDeselected; - maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested; - maskContainer.MaskDragRequested += maskSelection.HandleDrag; - - maskSelection.DeselectAll = maskContainer.DeselectAll; - - var dragLayer = new DragLayer(maskContainer.Select); - dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); - - InternalChildren = new Drawable[] - { - dragLayer, - maskSelection, - maskContainer, - dragLayer.CreateProxy() - }; - - foreach (var obj in playfield.HitObjects.Objects) - addMask(obj); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - maskContainer.DeselectAll(); - return true; - } - - /// - /// Adds a mask for a which adds movement support. - /// - /// The to create a mask for. - private void addMask(DrawableHitObject hitObject) - { - var mask = composer.CreateMaskFor(hitObject); - if (mask == null) - return; - - maskContainer.Add(mask); - } - - /// - /// Removes the mask for a . - /// - /// The to remove the mask for. - private void removeMask(DrawableHitObject hitObject) - { - var mask = maskContainer.FirstOrDefault(h => h.HitObject == hitObject); - if (mask == null) - return; - - maskContainer.Remove(mask); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + public class HitObjectMaskLayer : CompositeDrawable + { + private readonly Playfield playfield; + private readonly HitObjectComposer composer; + + private MaskContainer maskContainer; + + public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) + { + // we need the playfield as HitObjects may not be initialised until its BDL. + this.playfield = playfield; + + this.composer = composer; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + maskContainer = new MaskContainer(); + + var maskSelection = composer.CreateMaskSelection(); + + maskContainer.MaskSelected += maskSelection.HandleSelected; + maskContainer.MaskDeselected += maskSelection.HandleDeselected; + maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested; + maskContainer.MaskDragRequested += maskSelection.HandleDrag; + + maskSelection.DeselectAll = maskContainer.DeselectAll; + + var dragLayer = new DragLayer(maskContainer.Select); + dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); + + InternalChildren = new Drawable[] + { + dragLayer, + maskSelection, + maskContainer, + dragLayer.CreateProxy() + }; + + foreach (var obj in playfield.HitObjects.Objects) + addMask(obj); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + maskContainer.DeselectAll(); + return true; + } + + /// + /// Adds a mask for a which adds movement support. + /// + /// The to create a mask for. + private void addMask(DrawableHitObject hitObject) + { + var mask = composer.CreateMaskFor(hitObject); + if (mask == null) + return; + + maskContainer.Add(mask); + } + + /// + /// Removes the mask for a . + /// + /// The to remove the mask for. + private void removeMask(DrawableHitObject hitObject) + { + var mask = maskContainer.FirstOrDefault(h => h.HitObject == hitObject); + if (mask == null) + return; + + maskContainer.Remove(mask); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index b631628c9e..6d75b8dc15 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -1,123 +1,123 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Rulesets.Edit; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class MaskContainer : Container - { - /// - /// Invoked when any is selected. - /// - public event Action MaskSelected; - - /// - /// Invoked when any is deselected. - /// - public event Action MaskDeselected; - - /// - /// Invoked when any requests selection. - /// - public event Action MaskSelectionRequested; - - /// - /// Invoked when any requests drag. - /// - public event Action MaskDragRequested; - - private IEnumerable aliveMasks => AliveInternalChildren.Cast(); - - public MaskContainer() - { - RelativeSizeAxes = Axes.Both; - } - - public override void Add(HitObjectMask drawable) - { - base.Add(drawable); - - drawable.Selected += onMaskSelected; - drawable.Deselected += onMaskDeselected; - drawable.SelectionRequested += onSelectionRequested; - drawable.DragRequested += onDragRequested; - } - - public override bool Remove(HitObjectMask drawable) - { - var result = base.Remove(drawable); - - if (result) - { - drawable.Selected -= onMaskSelected; - drawable.Deselected -= onMaskDeselected; - drawable.SelectionRequested -= onSelectionRequested; - drawable.DragRequested -= onDragRequested; - } - - return result; - } - - /// - /// Select all masks in a given rectangle selection area. - /// - /// The rectangle to perform a selection on in screen-space coordinates. - public void Select(RectangleF rect) - { - foreach (var mask in aliveMasks.ToList()) - { - if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) - mask.Select(); - else - mask.Deselect(); - } - } - - /// - /// Deselects all selected s. - /// - public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect()); - - private void onMaskSelected(HitObjectMask mask) - { - MaskSelected?.Invoke(mask); - ChangeChildDepth(mask, 1); - } - - private void onMaskDeselected(HitObjectMask mask) - { - MaskDeselected?.Invoke(mask); - ChangeChildDepth(mask, 0); - } - - private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); - private void onDragRequested(HitObjectMask mask, InputState state) => MaskDragRequested?.Invoke(mask, state); - - protected override int Compare(Drawable x, Drawable y) - { - if (!(x is HitObjectMask xMask) || !(y is HitObjectMask yMask)) - return base.Compare(x, y); - return Compare(xMask, yMask); - } - - public int Compare(HitObjectMask x, HitObjectMask y) - { - // dpeth is used to denote selected status (we always want selected masks to handle input first). - int d = x.Depth.CompareTo(y.Depth); - if (d != 0) - return d; - - // Put earlier hitobjects towards the end of the list, so they handle input first - int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); - return i == 0 ? CompareReverseChildID(x, y) : i; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Rulesets.Edit; +using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + public class MaskContainer : Container + { + /// + /// Invoked when any is selected. + /// + public event Action MaskSelected; + + /// + /// Invoked when any is deselected. + /// + public event Action MaskDeselected; + + /// + /// Invoked when any requests selection. + /// + public event Action MaskSelectionRequested; + + /// + /// Invoked when any requests drag. + /// + public event Action MaskDragRequested; + + private IEnumerable aliveMasks => AliveInternalChildren.Cast(); + + public MaskContainer() + { + RelativeSizeAxes = Axes.Both; + } + + public override void Add(HitObjectMask drawable) + { + base.Add(drawable); + + drawable.Selected += onMaskSelected; + drawable.Deselected += onMaskDeselected; + drawable.SelectionRequested += onSelectionRequested; + drawable.DragRequested += onDragRequested; + } + + public override bool Remove(HitObjectMask drawable) + { + var result = base.Remove(drawable); + + if (result) + { + drawable.Selected -= onMaskSelected; + drawable.Deselected -= onMaskDeselected; + drawable.SelectionRequested -= onSelectionRequested; + drawable.DragRequested -= onDragRequested; + } + + return result; + } + + /// + /// Select all masks in a given rectangle selection area. + /// + /// The rectangle to perform a selection on in screen-space coordinates. + public void Select(RectangleF rect) + { + foreach (var mask in aliveMasks.ToList()) + { + if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) + mask.Select(); + else + mask.Deselect(); + } + } + + /// + /// Deselects all selected s. + /// + public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect()); + + private void onMaskSelected(HitObjectMask mask) + { + MaskSelected?.Invoke(mask); + ChangeChildDepth(mask, 1); + } + + private void onMaskDeselected(HitObjectMask mask) + { + MaskDeselected?.Invoke(mask); + ChangeChildDepth(mask, 0); + } + + private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); + private void onDragRequested(HitObjectMask mask, InputState state) => MaskDragRequested?.Invoke(mask, state); + + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is HitObjectMask xMask) || !(y is HitObjectMask yMask)) + return base.Compare(x, y); + return Compare(xMask, yMask); + } + + public int Compare(HitObjectMask x, HitObjectMask y) + { + // dpeth is used to denote selected status (we always want selected masks to handle input first). + int d = x.Depth.CompareTo(y.Depth); + if (d != 0) + return d; + + // Put earlier hitobjects towards the end of the list, so they handle input first + int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); + return i == 0 ? CompareReverseChildID(x, y) : i; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 76b8027b07..54697bad77 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -1,164 +1,164 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Types; -using OpenTK; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - /// - /// A box which surrounds s and provides interactive handles, context menus etc. - /// - public class MaskSelection : CompositeDrawable - { - public const float BORDER_RADIUS = 2; - - private readonly List selectedMasks; - - private Drawable outline; - - public MaskSelection() - { - selectedMasks = new List(); - - RelativeSizeAxes = Axes.Both; - AlwaysPresent = true; - Alpha = 0; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - InternalChild = outline = new Container - { - Masking = true, - BorderThickness = BORDER_RADIUS, - BorderColour = colours.Yellow, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - } - }; - } - - #region User Input Handling - - public void HandleDrag(HitObjectMask m, InputState state) - { - // Todo: Various forms of snapping - - foreach (var mask in selectedMasks) - { - switch (mask.HitObject.HitObject) - { - case IHasEditablePosition editablePosition: - editablePosition.OffsetPosition(state.Mouse.Delta); - break; - } - } - } - - #endregion - - #region Selection Handling - - /// - /// Bind an action to deselect all selected masks. - /// - public Action DeselectAll { private get; set; } - - /// - /// Handle a mask becoming selected. - /// - /// The mask. - public void HandleSelected(HitObjectMask mask) => selectedMasks.Add(mask); - - /// - /// Handle a mask becoming deselected. - /// - /// The mask. - public void HandleDeselected(HitObjectMask mask) - { - selectedMasks.Remove(mask); - - // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection - if (selectedMasks.Count == 0) - UpdateVisibility(); - } - - /// - /// Handle a mask requesting selection. - /// - /// The mask. - public void HandleSelectionRequested(HitObjectMask mask, InputState state) - { - if (state.Keyboard.ControlPressed) - { - if (mask.IsSelected) - mask.Deselect(); - else - mask.Select(); - } - else - { - if (mask.IsSelected) - return; - - DeselectAll?.Invoke(); - mask.Select(); - } - - UpdateVisibility(); - } - - #endregion - - /// - /// Updates whether this is visible. - /// - internal void UpdateVisibility() - { - if (selectedMasks.Count > 0) - Show(); - else - Hide(); - } - - protected override void Update() - { - base.Update(); - - if (selectedMasks.Count == 0) - return; - - // Move the rectangle to cover the hitobjects - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); - - bool hasSelection = false; - - foreach (var mask in selectedMasks) - { - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight)); - } - - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); - - outline.Size = bottomRight - topLeft; - outline.Position = topLeft; - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Types; +using OpenTK; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + /// + /// A box which surrounds s and provides interactive handles, context menus etc. + /// + public class MaskSelection : CompositeDrawable + { + public const float BORDER_RADIUS = 2; + + private readonly List selectedMasks; + + private Drawable outline; + + public MaskSelection() + { + selectedMasks = new List(); + + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = outline = new Container + { + Masking = true, + BorderThickness = BORDER_RADIUS, + BorderColour = colours.Yellow, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + } + }; + } + + #region User Input Handling + + public void HandleDrag(HitObjectMask m, InputState state) + { + // Todo: Various forms of snapping + + foreach (var mask in selectedMasks) + { + switch (mask.HitObject.HitObject) + { + case IHasEditablePosition editablePosition: + editablePosition.OffsetPosition(state.Mouse.Delta); + break; + } + } + } + + #endregion + + #region Selection Handling + + /// + /// Bind an action to deselect all selected masks. + /// + public Action DeselectAll { private get; set; } + + /// + /// Handle a mask becoming selected. + /// + /// The mask. + public void HandleSelected(HitObjectMask mask) => selectedMasks.Add(mask); + + /// + /// Handle a mask becoming deselected. + /// + /// The mask. + public void HandleDeselected(HitObjectMask mask) + { + selectedMasks.Remove(mask); + + // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection + if (selectedMasks.Count == 0) + UpdateVisibility(); + } + + /// + /// Handle a mask requesting selection. + /// + /// The mask. + public void HandleSelectionRequested(HitObjectMask mask, InputState state) + { + if (state.Keyboard.ControlPressed) + { + if (mask.IsSelected) + mask.Deselect(); + else + mask.Select(); + } + else + { + if (mask.IsSelected) + return; + + DeselectAll?.Invoke(); + mask.Select(); + } + + UpdateVisibility(); + } + + #endregion + + /// + /// Updates whether this is visible. + /// + internal void UpdateVisibility() + { + if (selectedMasks.Count > 0) + Show(); + else + Hide(); + } + + protected override void Update() + { + base.Update(); + + if (selectedMasks.Count == 0) + return; + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + bool hasSelection = false; + + foreach (var mask in selectedMasks) + { + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight)); + } + + topLeft -= new Vector2(5); + bottomRight += new Vector2(5); + + outline.Size = bottomRight - topLeft; + outline.Position = topLeft; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs index e2510ec016..0a009d9958 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs @@ -1,123 +1,123 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons -{ - public class DrawableRadioButton : TriangleButton - { - /// - /// Invoked when this has been selected. - /// - public Action Selected; - - private Color4 defaultBackgroundColour; - private Color4 defaultBubbleColour; - private Color4 selectedBackgroundColour; - private Color4 selectedBubbleColour; - - private readonly Drawable bubble; - private readonly RadioButton button; - - public DrawableRadioButton(RadioButton button) - { - this.button = button; - - Text = button.Text; - Action = button.Action; - - RelativeSizeAxes = Axes.X; - - bubble = new CircularContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.5f), - X = 10, - Masking = true, - Blending = BlendingMode.Additive, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - defaultBackgroundColour = colours.Gray3; - defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); - selectedBackgroundColour = colours.BlueDark; - selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); - - Triangles.Alpha = 0; - - Content.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 2, - Offset = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f) - }; - - Add(bubble); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - button.Selected.ValueChanged += v => - { - updateSelectionState(); - if (v) - Selected?.Invoke(button); - }; - - updateSelectionState(); - } - - private void updateSelectionState() - { - if (!IsLoaded) - return; - - BackgroundColour = button.Selected ? selectedBackgroundColour : defaultBackgroundColour; - bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour; - } - - protected override bool OnClick(InputState state) - { - if (button.Selected) - return true; - - if (!Enabled) - return true; - - button.Selected.Value = true; - - return base.OnClick(state); - } - - protected override SpriteText CreateText() => new OsuSpriteText - { - Depth = -1, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - X = 40f - }; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class DrawableRadioButton : TriangleButton + { + /// + /// Invoked when this has been selected. + /// + public Action Selected; + + private Color4 defaultBackgroundColour; + private Color4 defaultBubbleColour; + private Color4 selectedBackgroundColour; + private Color4 selectedBubbleColour; + + private readonly Drawable bubble; + private readonly RadioButton button; + + public DrawableRadioButton(RadioButton button) + { + this.button = button; + + Text = button.Text; + Action = button.Action; + + RelativeSizeAxes = Axes.X; + + bubble = new CircularContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.5f), + X = 10, + Masking = true, + Blending = BlendingMode.Additive, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + defaultBackgroundColour = colours.Gray3; + defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); + selectedBackgroundColour = colours.BlueDark; + selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); + + Triangles.Alpha = 0; + + Content.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 2, + Offset = new Vector2(0, 1), + Colour = Color4.Black.Opacity(0.5f) + }; + + Add(bubble); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + button.Selected.ValueChanged += v => + { + updateSelectionState(); + if (v) + Selected?.Invoke(button); + }; + + updateSelectionState(); + } + + private void updateSelectionState() + { + if (!IsLoaded) + return; + + BackgroundColour = button.Selected ? selectedBackgroundColour : defaultBackgroundColour; + bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour; + } + + protected override bool OnClick(InputState state) + { + if (button.Selected) + return true; + + if (!Enabled) + return true; + + button.Selected.Value = true; + + return base.OnClick(state); + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 40f + }; + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs index 5c28cc106a..09fe34bedc 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs @@ -1,51 +1,51 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Configuration; - -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons -{ - public class RadioButton - { - /// - /// Whether this is selected. - /// - /// - public readonly BindableBool Selected; - - /// - /// The text that should be displayed in this button. - /// - public string Text; - - /// - /// The that should be invoked when this button is selected. - /// - public Action Action; - - public RadioButton(string text, Action action) - { - Text = text; - Action = action; - Selected = new BindableBool(); - } - - public RadioButton(string text) - : this(text, null) - { - Text = text; - Action = null; - } - - /// - /// Selects this . - /// - public void Select() => Selected.Value = true; - - /// - /// Deselects this . - /// - public void Deselect() => Selected.Value = false; - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Configuration; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class RadioButton + { + /// + /// Whether this is selected. + /// + /// + public readonly BindableBool Selected; + + /// + /// The text that should be displayed in this button. + /// + public string Text; + + /// + /// The that should be invoked when this button is selected. + /// + public Action Action; + + public RadioButton(string text, Action action) + { + Text = text; + Action = action; + Selected = new BindableBool(); + } + + public RadioButton(string text) + : this(text, null) + { + Text = text; + Action = null; + } + + /// + /// Selects this . + /// + public void Select() => Selected.Value = true; + + /// + /// Deselects this . + /// + public void Deselect() => Selected.Value = false; + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs index f178f739dd..7ba16b3d3e 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs @@ -1,61 +1,61 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons -{ - public class RadioButtonCollection : CompositeDrawable - { - private IReadOnlyList items; - public IReadOnlyList Items - { - get { return items; } - set - { - if (ReferenceEquals(items, value)) - return; - items = value; - - buttonContainer.Clear(); - items.ForEach(addButton); - } - } - - private readonly FlowContainer buttonContainer; - - public RadioButtonCollection() - { - AutoSizeAxes = Axes.Y; - - InternalChild = buttonContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5) - }; - } - - private RadioButton currentlySelected; - private void addButton(RadioButton button) - { - button.Selected.ValueChanged += v => - { - if (v) - { - currentlySelected?.Deselect(); - currentlySelected = button; - } - else - currentlySelected = null; - }; - - buttonContainer.Add(new DrawableRadioButton(button)); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +{ + public class RadioButtonCollection : CompositeDrawable + { + private IReadOnlyList items; + public IReadOnlyList Items + { + get { return items; } + set + { + if (ReferenceEquals(items, value)) + return; + items = value; + + buttonContainer.Clear(); + items.ForEach(addButton); + } + } + + private readonly FlowContainer buttonContainer; + + public RadioButtonCollection() + { + AutoSizeAxes = Axes.Y; + + InternalChild = buttonContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + }; + } + + private RadioButton currentlySelected; + private void addButton(RadioButton button) + { + button.Selected.ValueChanged += v => + { + if (v) + { + currentlySelected?.Deselect(); + currentlySelected = button; + } + else + currentlySelected = null; + }; + + buttonContainer.Add(new DrawableRadioButton(button)); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs index f82ff5f286..72dda24b62 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs @@ -1,33 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; - -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline -{ - public class BeatmapWaveformGraph : CompositeDrawable - { - public readonly Bindable Beatmap = new Bindable(); - - private readonly WaveformGraph graph; - - public BeatmapWaveformGraph() - { - InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both }; - Beatmap.ValueChanged += b => graph.Waveform = b.Waveform; - } - - /// - /// Gets or sets the . - /// - public float Resolution - { - get { return graph.Resolution; } - set { graph.Resolution = value; } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class BeatmapWaveformGraph : CompositeDrawable + { + public readonly Bindable Beatmap = new Bindable(); + + private readonly WaveformGraph graph; + + public BeatmapWaveformGraph() + { + InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both }; + Beatmap.ValueChanged += b => graph.Waveform = b.Waveform; + } + + /// + /// Gets or sets the . + /// + public float Resolution + { + get { return graph.Resolution; } + set { graph.Resolution = value; } + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs index c308b2b9f8..3223c08c1f 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs @@ -1,131 +1,131 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline -{ - public class ScrollableTimeline : CompositeDrawable - { - public readonly Bindable Beatmap = new Bindable(); - - private readonly ScrollingTimelineContainer timelineContainer; - - public ScrollableTimeline() - { - Masking = true; - CornerRadius = 5; - - OsuCheckbox hitObjectsCheckbox; - OsuCheckbox hitSoundsCheckbox; - OsuCheckbox waveformCheckbox; - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("111") - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("222") - }, - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - Width = 160, - Padding = new MarginPadding { Horizontal = 15 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 4), - Children = new[] - { - hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" }, - hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" }, - waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } - } - } - } - }, - new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("333") - }, - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new[] - { - new TimelineButton - { - RelativeSizeAxes = Axes.Y, - Height = 0.5f, - Icon = FontAwesome.fa_search_plus, - Action = () => timelineContainer.Zoom++ - }, - new TimelineButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Y, - Height = 0.5f, - Icon = FontAwesome.fa_search_minus, - Action = () => timelineContainer.Zoom-- - }, - } - } - } - }, - timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y } - } - } - }; - - hitObjectsCheckbox.Current.Value = true; - hitSoundsCheckbox.Current.Value = true; - waveformCheckbox.Current.Value = true; - - timelineContainer.Beatmap.BindTo(Beatmap); - timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current); - } - - protected override void Update() - { - base.Update(); - - timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class ScrollableTimeline : CompositeDrawable + { + public readonly Bindable Beatmap = new Bindable(); + + private readonly ScrollingTimelineContainer timelineContainer; + + public ScrollableTimeline() + { + Masking = true; + CornerRadius = 5; + + OsuCheckbox hitObjectsCheckbox; + OsuCheckbox hitSoundsCheckbox; + OsuCheckbox waveformCheckbox; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("111") + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("222") + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + Width = 160, + Padding = new MarginPadding { Horizontal = 15 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 4), + Children = new[] + { + hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" }, + hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" }, + waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } + } + } + } + }, + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("333") + }, + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new[] + { + new TimelineButton + { + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Icon = FontAwesome.fa_search_plus, + Action = () => timelineContainer.Zoom++ + }, + new TimelineButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Icon = FontAwesome.fa_search_minus, + Action = () => timelineContainer.Zoom-- + }, + } + } + } + }, + timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y } + } + } + }; + + hitObjectsCheckbox.Current.Value = true; + hitSoundsCheckbox.Current.Value = true; + waveformCheckbox.Current.Value = true; + + timelineContainer.Beatmap.BindTo(Beatmap); + timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current); + } + + protected override void Update() + { + base.Update(); + + timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs index f71607a6cf..83aa86ba61 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs @@ -1,141 +1,141 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Graphics; - -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline -{ - public class ScrollingTimelineContainer : ScrollContainer - { - public readonly Bindable HitObjectsVisible = new Bindable(); - public readonly Bindable HitSoundsVisible = new Bindable(); - public readonly Bindable WaveformVisible = new Bindable(); - public readonly Bindable Beatmap = new Bindable(); - - private readonly BeatmapWaveformGraph waveform; - - public ScrollingTimelineContainer() - : base(Direction.Horizontal) - { - Masking = true; - - Add(waveform = new BeatmapWaveformGraph - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("222"), - Depth = float.MaxValue - }); - - Content.AutoSizeAxes = Axes.None; - Content.RelativeSizeAxes = Axes.Both; - - waveform.Beatmap.BindTo(Beatmap); - WaveformVisible.ValueChanged += waveformVisibilityChanged; - - Zoom = 10; - } - - private float minZoom = 1; - /// - /// The minimum zoom level allowed. - /// - public float MinZoom - { - get { return minZoom; } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException(nameof(value)); - if (minZoom == value) - return; - minZoom = value; - - // Update the zoom level - Zoom = Zoom; - } - } - - private float maxZoom = 30; - /// - /// The maximum zoom level allowed. - /// - public float MaxZoom - { - get { return maxZoom; } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException(nameof(value)); - if (maxZoom == value) - return; - maxZoom = value; - - // Update the zoom level - Zoom = Zoom; - } - } - - private float zoom = 1; - /// - /// The current zoom level. - /// - public float Zoom - { - get { return zoom; } - set - { - value = MathHelper.Clamp(value, MinZoom, MaxZoom); - if (zoom == value) - return; - zoom = value; - - // Make the zoom target default to the center of the graph if it hasn't been set - if (relativeContentZoomTarget == null) - relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X; - if (localZoomTarget == null) - localZoomTarget = DrawSize.X / 2; - - Content.ResizeWidthTo(Zoom); - - // Update the scroll position to focus on the zoom target - float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value; - ScrollTo(scrollPos, false); - - relativeContentZoomTarget = null; - localZoomTarget = null; - } - } - - /// - /// Zoom target as a relative position in the space. - /// - private float? relativeContentZoomTarget; - - /// - /// Zoom target as a position in our local space. - /// - private float? localZoomTarget; - - protected override bool OnWheel(InputState state) - { - if (!state.Keyboard.ControlPressed) - return base.OnWheel(state); - - relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X; - localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X; - - Zoom += state.Mouse.WheelDelta; - - return true; - } - - private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class ScrollingTimelineContainer : ScrollContainer + { + public readonly Bindable HitObjectsVisible = new Bindable(); + public readonly Bindable HitSoundsVisible = new Bindable(); + public readonly Bindable WaveformVisible = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); + + private readonly BeatmapWaveformGraph waveform; + + public ScrollingTimelineContainer() + : base(Direction.Horizontal) + { + Masking = true; + + Add(waveform = new BeatmapWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("222"), + Depth = float.MaxValue + }); + + Content.AutoSizeAxes = Axes.None; + Content.RelativeSizeAxes = Axes.Both; + + waveform.Beatmap.BindTo(Beatmap); + WaveformVisible.ValueChanged += waveformVisibilityChanged; + + Zoom = 10; + } + + private float minZoom = 1; + /// + /// The minimum zoom level allowed. + /// + public float MinZoom + { + get { return minZoom; } + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException(nameof(value)); + if (minZoom == value) + return; + minZoom = value; + + // Update the zoom level + Zoom = Zoom; + } + } + + private float maxZoom = 30; + /// + /// The maximum zoom level allowed. + /// + public float MaxZoom + { + get { return maxZoom; } + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException(nameof(value)); + if (maxZoom == value) + return; + maxZoom = value; + + // Update the zoom level + Zoom = Zoom; + } + } + + private float zoom = 1; + /// + /// The current zoom level. + /// + public float Zoom + { + get { return zoom; } + set + { + value = MathHelper.Clamp(value, MinZoom, MaxZoom); + if (zoom == value) + return; + zoom = value; + + // Make the zoom target default to the center of the graph if it hasn't been set + if (relativeContentZoomTarget == null) + relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X; + if (localZoomTarget == null) + localZoomTarget = DrawSize.X / 2; + + Content.ResizeWidthTo(Zoom); + + // Update the scroll position to focus on the zoom target + float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value; + ScrollTo(scrollPos, false); + + relativeContentZoomTarget = null; + localZoomTarget = null; + } + } + + /// + /// Zoom target as a relative position in the space. + /// + private float? relativeContentZoomTarget; + + /// + /// Zoom target as a position in our local space. + /// + private float? localZoomTarget; + + protected override bool OnWheel(InputState state) + { + if (!state.Keyboard.ControlPressed) + return base.OnWheel(state); + + relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X; + localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X; + + Zoom += state.Mouse.WheelDelta; + + return true; + } + + private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs index a0802f0d7d..f46cc7ef9d 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs @@ -1,52 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline -{ - public class TimelineButton : CompositeDrawable - { - public Action Action; - public readonly BindableBool Enabled = new BindableBool(true); - - public FontAwesome Icon - { - get { return button.Icon; } - set { button.Icon = value; } - } - - private readonly IconButton button; - - public TimelineButton() - { - InternalChild = button = new IconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - IconColour = OsuColour.Gray(0.35f), - IconHoverColour = Color4.White, - HoverColour = OsuColour.Gray(0.25f), - FlashColour = OsuColour.Gray(0.5f), - Action = () => Action?.Invoke() - }; - - button.Enabled.BindTo(Enabled); - Width = button.ButtonSize.X; - } - - protected override void Update() - { - base.Update(); - - button.ButtonSize = new Vector2(button.ButtonSize.X, DrawHeight); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class TimelineButton : CompositeDrawable + { + public Action Action; + public readonly BindableBool Enabled = new BindableBool(true); + + public FontAwesome Icon + { + get { return button.Icon; } + set { button.Icon = value; } + } + + private readonly IconButton button; + + public TimelineButton() + { + InternalChild = button = new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + IconColour = OsuColour.Gray(0.35f), + IconHoverColour = Color4.White, + HoverColour = OsuColour.Gray(0.25f), + FlashColour = OsuColour.Gray(0.5f), + Action = () => Action?.Invoke() + }; + + button.Enabled.BindTo(Enabled); + Width = button.ButtonSize.X; + } + + protected override void Update() + { + base.Update(); + + button.ButtonSize = new Vector2(button.ButtonSize.X, DrawHeight); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Design/Design.cs b/osu.Game/Screens/Edit/Screens/Design/Design.cs index 16a71846ac..052a1c1d90 100644 --- a/osu.Game/Screens/Edit/Screens/Design/Design.cs +++ b/osu.Game/Screens/Edit/Screens/Design/Design.cs @@ -1,52 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; - -namespace osu.Game.Screens.Edit.Screens.Design -{ - public class Design : EditorScreen - { - public Design() - { - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.35f - }, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.5f - }, - new Container - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding(20), - Child = new OsuSpriteText { Text = "Design screen" } - } - } - } - } - }); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Design +{ + public class Design : EditorScreen + { + public Design() + { + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.35f + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f + }, + new Container + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Child = new OsuSpriteText { Text = "Design screen" } + } + } + } + } + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index 009830502e..f70c462cd8 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -1,44 +1,44 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; - -namespace osu.Game.Screens.Edit.Screens -{ - /// - /// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor. - /// - public class EditorScreen : Container - { - public readonly Bindable Beatmap = new Bindable(); - - protected override Container Content => content; - private readonly Container content; - - public EditorScreen() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - - InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - this.FadeTo(0) - .Then() - .FadeTo(1f, 250, Easing.OutQuint); - } - - public void Exit() - { - this.FadeOut(250).Expire(); - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Edit.Screens +{ + /// + /// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor. + /// + public class EditorScreen : Container + { + public readonly Bindable Beatmap = new Bindable(); + + protected override Container Content => content; + private readonly Container content; + + public EditorScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + + InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeTo(0) + .Then() + .FadeTo(1f, 250, Easing.OutQuint); + } + + public void Exit() + { + this.FadeOut(250).Expire(); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs b/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs index 67e50b34ac..be8363680d 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Screens.Edit.Screens -{ - public enum EditorScreenMode - { - [Description("setup")] - SongSetup, - [Description("compose")] - Compose, - [Description("design")] - Design, - [Description("timing")] - Timing, - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Screens.Edit.Screens +{ + public enum EditorScreenMode + { + [Description("setup")] + SongSetup, + [Description("compose")] + Compose, + [Description("design")] + Design, + [Description("timing")] + Timing, + } +} diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 94238285ad..1d152361df 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -1,118 +1,118 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shaders; -using osu.Game.Screens.Menu; -using OpenTK; -using osu.Framework.Screens; - -namespace osu.Game.Screens -{ - public class Loader : OsuScreen - { - private bool showDisclaimer; - - public override bool ShowOverlaysOnEnter => false; - - public Loader() - { - ValidForResume = false; - } - - protected override void LogoArriving(OsuLogo logo, bool resuming) - { - base.LogoArriving(logo, resuming); - - logo.Triangles = false; - logo.Origin = Anchor.BottomRight; - logo.Anchor = Anchor.BottomRight; - logo.Position = new Vector2(-40); - logo.Scale = new Vector2(0.2f); - - logo.FadeInFromZero(5000, Easing.OutQuint); - } - - private OsuScreen loadScreen; - private ShaderPrecompiler precompiler; - - protected override void OnEntering(Screen last) - { - base.OnEntering(last); - - LoadComponentAsync(precompiler = new ShaderPrecompiler(loadIfReady), Add); - LoadComponentAsync(loadScreen = showDisclaimer ? (OsuScreen)new Disclaimer() : new Intro(), s => loadIfReady()); - } - - private void loadIfReady() - { - if (ChildScreen == loadScreen) return; - - if (loadScreen.LoadState != LoadState.Ready) - return; - - if (!precompiler.FinishedCompiling) - return; - - Push(loadScreen); - } - - protected override void LogoSuspending(OsuLogo logo) - { - base.LogoSuspending(logo); - logo.FadeOut(100); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - showDisclaimer = game.IsDeployedBuild; - } - - /// - /// Compiles a set of shaders before continuing. Attempts to draw some frames between compilation by limiting to one compile per draw frame. - /// - public class ShaderPrecompiler : Drawable - { - private readonly Action onLoaded; - private readonly List loadTargets = new List(); - - public bool FinishedCompiling { get; private set; } - - public ShaderPrecompiler(Action onLoaded) - { - this.onLoaded = onLoaded; - } - - [BackgroundDependencyLoader] - private void load(ShaderManager manager) - { - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED)); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR)); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); - - loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); - - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED)); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); - } - - protected override void Update() - { - base.Update(); - - // if our target is null we are done. - if (loadTargets.All(s => s.Loaded)) - { - FinishedCompiling = true; - Expire(); - onLoaded?.Invoke(); - } - } - } - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shaders; +using osu.Game.Screens.Menu; +using OpenTK; +using osu.Framework.Screens; + +namespace osu.Game.Screens +{ + public class Loader : OsuScreen + { + private bool showDisclaimer; + + public override bool ShowOverlaysOnEnter => false; + + public Loader() + { + ValidForResume = false; + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.Triangles = false; + logo.Origin = Anchor.BottomRight; + logo.Anchor = Anchor.BottomRight; + logo.Position = new Vector2(-40); + logo.Scale = new Vector2(0.2f); + + logo.FadeInFromZero(5000, Easing.OutQuint); + } + + private OsuScreen loadScreen; + private ShaderPrecompiler precompiler; + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + + LoadComponentAsync(precompiler = new ShaderPrecompiler(loadIfReady), Add); + LoadComponentAsync(loadScreen = showDisclaimer ? (OsuScreen)new Disclaimer() : new Intro(), s => loadIfReady()); + } + + private void loadIfReady() + { + if (ChildScreen == loadScreen) return; + + if (loadScreen.LoadState != LoadState.Ready) + return; + + if (!precompiler.FinishedCompiling) + return; + + Push(loadScreen); + } + + protected override void LogoSuspending(OsuLogo logo) + { + base.LogoSuspending(logo); + logo.FadeOut(100); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + showDisclaimer = game.IsDeployedBuild; + } + + /// + /// Compiles a set of shaders before continuing. Attempts to draw some frames between compilation by limiting to one compile per draw frame. + /// + public class ShaderPrecompiler : Drawable + { + private readonly Action onLoaded; + private readonly List loadTargets = new List(); + + public bool FinishedCompiling { get; private set; } + + public ShaderPrecompiler(Action onLoaded) + { + this.onLoaded = onLoaded; + } + + [BackgroundDependencyLoader] + private void load(ShaderManager manager) + { + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); + + loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); + + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); + } + + protected override void Update() + { + base.Update(); + + // if our target is null we are done. + if (loadTargets.All(s => s.Loaded)) + { + FinishedCompiling = true; + Expire(); + onLoaded?.Invoke(); + } + } + } + } +} diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 268be51347..33e423a558 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -1,287 +1,287 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; -using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Graphics.Containers; -using osu.Framework.Audio.Track; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Screens.Menu -{ - /// - /// Button designed specifically for the osu!next main menu. - /// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape). - /// - public class Button : BeatSyncedContainer, IStateful - { - public event Action StateChanged; - - private readonly Container iconText; - private readonly Container box; - private readonly Box boxHoverLayer; - private readonly SpriteIcon icon; - private readonly string sampleName; - private readonly Action clickAction; - private readonly Key triggerKey; - private SampleChannel sampleClick; - private SampleChannel sampleHover; - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos); - - public Button(string text, string sampleName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) - { - this.sampleName = sampleName; - this.clickAction = clickAction; - this.triggerKey = triggerKey; - - AutoSizeAxes = Axes.Both; - Alpha = 0; - - Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonSystem.BUTTON_AREA_HEIGHT); - - Children = new Drawable[] - { - box = new Container - { - Masking = true, - MaskingSmoothness = 2, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Roundness = 5, - Radius = 8, - }, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0, 1), - Size = boxSize, - Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / boxSize.Y, 0), - Children = new[] - { - new Box - { - EdgeSmoothness = new Vector2(1.5f, 0), - RelativeSizeAxes = Axes.Both, - Colour = colour, - }, - boxHoverLayer = new Box - { - EdgeSmoothness = new Vector2(1.5f, 0), - RelativeSizeAxes = Axes.Both, - Blending = BlendingMode.Additive, - Colour = Color4.White, - Alpha = 0, - }, - } - }, - iconText = new Container - { - AutoSizeAxes = Axes.Both, - Position = new Vector2(extraWidth / 2, 0), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - icon = new SpriteIcon - { - Shadow = true, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(30), - Position = new Vector2(0, 0), - Icon = symbol - }, - new OsuSpriteText - { - Shadow = true, - AllowMultiline = false, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - TextSize = 16, - Position = new Vector2(0, 35), - Text = text - } - } - } - }; - } - - private bool rightward; - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) - { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - - if (!IsHovered) return; - - double duration = timingPoint.BeatLength / 2; - - icon.RotateTo(rightward ? 10 : -10, duration * 2, Easing.InOutSine); - - icon.Animate( - i => i.MoveToY(-10, duration, Easing.Out), - i => i.ScaleTo(1, duration, Easing.Out) - ).Then( - i => i.MoveToY(0, duration, Easing.In), - i => i.ScaleTo(new Vector2(1, 0.9f), duration, Easing.In) - ); - - rightward = !rightward; - } - - protected override bool OnHover(InputState state) - { - if (State != ButtonState.Expanded) return true; - - sampleHover?.Play(); - - box.ScaleTo(new Vector2(1.5f, 1), 500, Easing.OutElastic); - - double duration = TimeUntilNextBeat; - - icon.ClearTransforms(); - icon.RotateTo(rightward ? -10 : 10, duration, Easing.InOutSine); - icon.ScaleTo(new Vector2(1, 0.9f), duration, Easing.Out); - return true; - } - - protected override void OnHoverLost(InputState state) - { - icon.ClearTransforms(); - icon.RotateTo(0, 500, Easing.Out); - icon.MoveTo(Vector2.Zero, 500, Easing.Out); - icon.ScaleTo(Vector2.One, 200, Easing.Out); - - if (State == ButtonState.Expanded) - box.ScaleTo(new Vector2(1, 1), 500, Easing.OutElastic); - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleHover = audio.Sample.Get(@"Menu/button-hover"); - if (!string.IsNullOrEmpty(sampleName)) - sampleClick = audio.Sample.Get($@"Menu/{sampleName}"); - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - boxHoverLayer.FadeTo(0.1f, 1000, Easing.OutQuint); - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - boxHoverLayer.FadeTo(0, 1000, Easing.OutQuint); - return base.OnMouseUp(state, args); - } - - protected override bool OnClick(InputState state) - { - trigger(); - return true; - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Repeat || state.Keyboard.ControlPressed || state.Keyboard.ShiftPressed || state.Keyboard.AltPressed) - return false; - - if (triggerKey == args.Key && triggerKey != Key.Unknown) - { - trigger(); - return true; - } - - return false; - } - - private void trigger() - { - sampleClick?.Play(); - - clickAction?.Invoke(); - - boxHoverLayer.ClearTransforms(); - boxHoverLayer.Alpha = 0.9f; - boxHoverLayer.FadeOut(800, Easing.OutExpo); - } - - public override bool HandleKeyboardInput => state != ButtonState.Exploded; - public override bool HandleMouseInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; - - protected override void Update() - { - iconText.Alpha = MathHelper.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); - base.Update(); - } - - public int ContractStyle; - - private ButtonState state; - - public ButtonState State - { - get { return state; } - - set - { - if (state == value) - return; - - state = value; - - switch (state) - { - case ButtonState.Contracted: - switch (ContractStyle) - { - default: - box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); - this.FadeOut(500); - break; - case 1: - box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); - this.FadeOut(800); - break; - } - break; - case ButtonState.Expanded: - const int expand_duration = 500; - box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); - this.FadeIn(expand_duration / 6f); - break; - case ButtonState.Exploded: - const int explode_duration = 200; - box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); - this.FadeOut(explode_duration / 4f * 3); - break; - } - - StateChanged?.Invoke(State); - } - } - } - - public enum ButtonState - { - Contracted, - Expanded, - Exploded - } -} +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; +using osu.Framework.Audio.Track; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Screens.Menu +{ + /// + /// Button designed specifically for the osu!next main menu. + /// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape). + /// + public class Button : BeatSyncedContainer, IStateful + { + public event Action StateChanged; + + private readonly Container iconText; + private readonly Container box; + private readonly Box boxHoverLayer; + private readonly SpriteIcon icon; + private readonly string sampleName; + private readonly Action clickAction; + private readonly Key triggerKey; + private SampleChannel sampleClick; + private SampleChannel sampleHover; + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos); + + public Button(string text, string sampleName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) + { + this.sampleName = sampleName; + this.clickAction = clickAction; + this.triggerKey = triggerKey; + + AutoSizeAxes = Axes.Both; + Alpha = 0; + + Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonSystem.BUTTON_AREA_HEIGHT); + + Children = new Drawable[] + { + box = new Container + { + Masking = true, + MaskingSmoothness = 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Roundness = 5, + Radius = 8, + }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0, 1), + Size = boxSize, + Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / boxSize.Y, 0), + Children = new[] + { + new Box + { + EdgeSmoothness = new Vector2(1.5f, 0), + RelativeSizeAxes = Axes.Both, + Colour = colour, + }, + boxHoverLayer = new Box + { + EdgeSmoothness = new Vector2(1.5f, 0), + RelativeSizeAxes = Axes.Both, + Blending = BlendingMode.Additive, + Colour = Color4.White, + Alpha = 0, + }, + } + }, + iconText = new Container + { + AutoSizeAxes = Axes.Both, + Position = new Vector2(extraWidth / 2, 0), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + icon = new SpriteIcon + { + Shadow = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(30), + Position = new Vector2(0, 0), + Icon = symbol + }, + new OsuSpriteText + { + Shadow = true, + AllowMultiline = false, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 16, + Position = new Vector2(0, 35), + Text = text + } + } + } + }; + } + + private bool rightward; + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (!IsHovered) return; + + double duration = timingPoint.BeatLength / 2; + + icon.RotateTo(rightward ? 10 : -10, duration * 2, Easing.InOutSine); + + icon.Animate( + i => i.MoveToY(-10, duration, Easing.Out), + i => i.ScaleTo(1, duration, Easing.Out) + ).Then( + i => i.MoveToY(0, duration, Easing.In), + i => i.ScaleTo(new Vector2(1, 0.9f), duration, Easing.In) + ); + + rightward = !rightward; + } + + protected override bool OnHover(InputState state) + { + if (State != ButtonState.Expanded) return true; + + sampleHover?.Play(); + + box.ScaleTo(new Vector2(1.5f, 1), 500, Easing.OutElastic); + + double duration = TimeUntilNextBeat; + + icon.ClearTransforms(); + icon.RotateTo(rightward ? -10 : 10, duration, Easing.InOutSine); + icon.ScaleTo(new Vector2(1, 0.9f), duration, Easing.Out); + return true; + } + + protected override void OnHoverLost(InputState state) + { + icon.ClearTransforms(); + icon.RotateTo(0, 500, Easing.Out); + icon.MoveTo(Vector2.Zero, 500, Easing.Out); + icon.ScaleTo(Vector2.One, 200, Easing.Out); + + if (State == ButtonState.Expanded) + box.ScaleTo(new Vector2(1, 1), 500, Easing.OutElastic); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleHover = audio.Sample.Get(@"Menu/button-hover"); + if (!string.IsNullOrEmpty(sampleName)) + sampleClick = audio.Sample.Get($@"Menu/{sampleName}"); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + boxHoverLayer.FadeTo(0.1f, 1000, Easing.OutQuint); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + boxHoverLayer.FadeTo(0, 1000, Easing.OutQuint); + return base.OnMouseUp(state, args); + } + + protected override bool OnClick(InputState state) + { + trigger(); + return true; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat || state.Keyboard.ControlPressed || state.Keyboard.ShiftPressed || state.Keyboard.AltPressed) + return false; + + if (triggerKey == args.Key && triggerKey != Key.Unknown) + { + trigger(); + return true; + } + + return false; + } + + private void trigger() + { + sampleClick?.Play(); + + clickAction?.Invoke(); + + boxHoverLayer.ClearTransforms(); + boxHoverLayer.Alpha = 0.9f; + boxHoverLayer.FadeOut(800, Easing.OutExpo); + } + + public override bool HandleKeyboardInput => state != ButtonState.Exploded; + public override bool HandleMouseInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; + + protected override void Update() + { + iconText.Alpha = MathHelper.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); + base.Update(); + } + + public int ContractStyle; + + private ButtonState state; + + public ButtonState State + { + get { return state; } + + set + { + if (state == value) + return; + + state = value; + + switch (state) + { + case ButtonState.Contracted: + switch (ContractStyle) + { + default: + box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); + this.FadeOut(500); + break; + case 1: + box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); + this.FadeOut(800); + break; + } + break; + case ButtonState.Expanded: + const int expand_duration = 500; + box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); + this.FadeIn(expand_duration / 6f); + break; + case ButtonState.Exploded: + const int explode_duration = 200; + box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); + this.FadeOut(explode_duration / 4f * 3); + break; + } + + StateChanged?.Invoke(State); + } + } + } + + public enum ButtonState + { + Contracted, + Expanded, + Exploded + } +} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 72c26af2a6..f48e1925c5 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -1,376 +1,376 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; -using osu.Framework.Audio.Sample; -using osu.Framework.Audio; -using osu.Framework.Configuration; -using osu.Framework.Threading; - -namespace osu.Game.Screens.Menu -{ - public class ButtonSystem : Container, IStateful - { - public event Action StateChanged; - - private readonly BindableBool showOverlays = new BindableBool(); - - public Action OnEdit; - public Action OnExit; - public Action OnDirect; - public Action OnSolo; - public Action OnSettings; - public Action OnMulti; - public Action OnChart; - public Action OnTest; - - private readonly FlowContainerWithOrigin buttonFlow; - - //todo: make these non-internal somehow. - public const float BUTTON_AREA_HEIGHT = 100; - - public const float BUTTON_WIDTH = 140f; - public const float WEDGE_WIDTH = 20; - - private OsuLogo logo; - - public void SetOsuLogo(OsuLogo logo) - { - this.logo = logo; - - if (this.logo != null) - { - this.logo.Action = onOsuLogo; - - // osuLogo.SizeForFlow relies on loading to be complete. - buttonFlow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); - - updateLogoState(); - } - } - - private readonly Drawable iconFacade; - private readonly Container buttonArea; - private readonly Box buttonAreaBackground; - - private readonly Button backButton; - private readonly Button settingsButton; - - private readonly List