diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md new file mode 100644 index 0000000000..8d85c92fec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-issues.md @@ -0,0 +1,14 @@ +--- +name: Bug Report +about: For issues regarding encountered game bugs +--- + + + +**Describe your problem:** + +**Screenshots or videos showing encountered issue:** + +**osu!lazer version:** + +**Logs:** \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md new file mode 100644 index 0000000000..849f042c1f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash-issues.md @@ -0,0 +1,16 @@ +--- +name: Crash Report +about: For issues regarding game crashes or permanent freezes +--- + + + +**Describe your problem:** + +**Screenshots or videos showing encountered issue:** + +**osu!lazer version:** + +**Logs:** + +**Computer Specifications:** \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md new file mode 100644 index 0000000000..73c4f37a3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request-issues.md @@ -0,0 +1,10 @@ +--- +name: Feature Request +about: Let us know what you would like to see in the game! +--- + + + +**Describe the feature:** + +**Proposal designs of the feature:** diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md new file mode 100644 index 0000000000..ae3cf20a8c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md @@ -0,0 +1,10 @@ +--- +name: Missing for Live +about: Let us know the features you need which are available in osu-stable but not lazer +--- + + + +**Describe the feature:** + +**Designs:** diff --git a/.gitignore b/.gitignore index 5138e940ed..f95a04e517 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +### Cake ### +tools/* +!tools/cakebuild.csproj + # Build results bin/[Dd]ebug/ [Dd]ebugPublic/ @@ -98,6 +102,7 @@ $tf/ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user +inspectcode # JustCode is a .NET coding add-in .JustCode @@ -247,7 +252,11 @@ paket-files/ .fake/ # JetBrains Rider -.idea/ +.idea/.idea.osu/.idea/*.xml +.idea/.idea.osu/.idea/codeStyles/*.xml +.idea/.idea.osu/.idea/dataSources/*.xml +.idea/.idea.osu/.idea/dictionaries/*.xml +.idea/.idea.osu/*.iml *.sln.iml # CodeRush @@ -257,3 +266,5 @@ paket-files/ __pycache__/ *.pyc Staging/ + +inspectcodereport.xml diff --git a/README.md b/README.md index dc36145337..baaba22726 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Build and run - Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included) - From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance. +- To run with code analysis, instead use `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternative, you can install resharper or use rider to get inline support in your IDE of choice. Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example diff --git a/appveyor.yml b/appveyor.yml index 8c06b42fd2..1f485485da 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,22 +1,8 @@ clone_depth: 1 version: '{branch}-{build}' image: Visual Studio 2017 -configuration: Debug -cache: - - C:\ProgramData\chocolatey\bin -> appveyor.yml - - C:\ProgramData\chocolatey\lib -> appveyor.yml +test: off install: - cmd: git submodule update --init --recursive --depth=5 - - cmd: choco install resharper-clt -y - - cmd: choco install nvika -y - - cmd: dotnet tool install CodeFileSanity --version 0.0.16 --global -before_build: - - cmd: CodeFileSanity - - cmd: nuget restore -verbosity quiet -build: - project: osu.sln - parallel: true - verbosity: minimal -after_build: - - cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL - - cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors +build_script: + - cmd: PowerShell -Version 2.0 .\build.ps1 diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml deleted file mode 100644 index 6d8d95e773..0000000000 --- a/appveyor_deploy.yml +++ /dev/null @@ -1,30 +0,0 @@ -clone_depth: 1 -version: '{build}' -skip_non_tags: true -image: Visual Studio 2017 -install: - - git clone https://github.com/ppy/osu-deploy -before_build: - - ps: if($env:appveyor_repo_tag -eq 'True') { Update-AppveyorBuild -Version $env:appveyor_repo_tag_name } - - cmd: git submodule update --init --recursive --depth=5 - - cmd: nuget restore -verbosity quiet -build_script: - - ps: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/secure-file/master/install.ps1')) - - appveyor DownloadFile https://puu.sh/A6g5K/4d08705438.enc # signing certificate - - cmd: appveyor-tools\secure-file -decrypt 4d08705438.enc -secret %decode_secret% -out %HOMEPATH%\deanherbert.pfx - - appveyor DownloadFile https://puu.sh/A6g75/fdc6f19b04.enc # deploy configuration - - cd osu-deploy - - nuget restore -verbosity quiet - - msbuild osu.Desktop.Deploy.csproj - - cmd: ..\appveyor-tools\secure-file -decrypt ..\fdc6f19b04.enc -secret %decode_secret% -out bin\Debug\netcoreapp2.1\osu.Desktop.Deploy.dll.config - - dotnet bin/Debug/netcoreapp2.1/osu.Desktop.Deploy.dll %code_signing_password% %APPVEYOR_REPO_TAG_NAME% -environment: - decode_secret: - secure: i67IC2xj6DjjxmA6Oj2jing3+MwzLkq6CbGsjfZ7rdY= - code_signing_password: - secure: 34tLNqvjmmZEi97MLKfrnQ== -artifacts: - - path: 'osu-deploy/releases/*' -deploy: - - provider: Environment - name: github \ No newline at end of file diff --git a/build.cake b/build.cake new file mode 100644 index 0000000000..bc7dfafb8c --- /dev/null +++ b/build.cake @@ -0,0 +1,72 @@ +#addin "nuget:?package=CodeFileSanity&version=0.0.21" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2" +#tool "nuget:?package=NVika.MSBuild&version=1.0.1" + +/////////////////////////////////////////////////////////////////////////////// +// ARGUMENTS +/////////////////////////////////////////////////////////////////////////////// + +var target = Argument("target", "Build"); +var configuration = Argument("configuration", "Release"); + +var osuSolution = new FilePath("./osu.sln"); + +/////////////////////////////////////////////////////////////////////////////// +// TASKS +/////////////////////////////////////////////////////////////////////////////// + +Task("Restore") + .Does(() => { + DotNetCoreRestore(osuSolution.FullPath); + }); + +Task("Compile") + .IsDependentOn("Restore") + .Does(() => { + DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings { + Configuration = configuration, + NoRestore = true, + }); + }); + +Task("Test") + .IsDependentOn("Compile") + .Does(() => { + var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll"); + + DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings { + Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx", + Parallel = true, + ToolTimeout = TimeSpan.FromMinutes(10), + }); + }); + +// windows only because both inspectcore and nvika depend on net45 +Task("InspectCode") + .WithCriteria(IsRunningOnWindows()) + .IsDependentOn("Compile") + .Does(() => { + var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); + + InspectCode(osuSolution, new InspectCodeSettings { + CachesHome = "inspectcode", + OutputFile = "inspectcodereport.xml", + }); + + StartProcess(nVikaToolPath, @"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + }); + +Task("CodeFileSanity") + .Does(() => { + ValidateCodeSanity(new ValidateCodeSanitySettings { + RootDirectory = ".", + IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor + }); + }); + +Task("Build") + .IsDependentOn("CodeFileSanity") + .IsDependentOn("InspectCode") + .IsDependentOn("Test"); + +RunTarget(target); \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000000..9968673c90 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,79 @@ +########################################################################## +# This is a customized Cake bootstrapper script for PowerShell. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script restores NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER ShowDescription +Shows description about tasks. +.PARAMETER DryRun +Performs a dry run. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +https://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +Write-Host "Preparing to run build script..." + +# Determine the script root for resolving other paths. +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +# Resolve the paths for resources used for debugging. +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj" + +# Install the required tools locally. +Write-Host "Restoring cake tools..." +Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null + +# Find the Cake executable +$CAKE_EXECUTABLE = (Get-ChildItem -Path ./tools/cake.coreclr/ -Filter Cake.dll -Recurse).FullName + +# Build Cake arguments +$cakeArguments = @("$Script"); +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +if ($Experimental) { $cakeArguments += "-experimental" } +$cakeArguments += $ScriptArgs + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments" +exit $LASTEXITCODE diff --git a/build.sh b/build.sh new file mode 100755 index 0000000000..caf1702f41 --- /dev/null +++ b/build.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +########################################################################## +# This is a customized Cake bootstrapper script for Shell. +########################################################################## + +echo "Preparing to run build script..." + +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TOOLS_DIR=$SCRIPT_DIR/tools +CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr" + +SCRIPT="build.cake" +CAKE_CSPROJ=$TOOLS_DIR/"cakebuild.csproj" + +# Parse arguments. +CAKE_ARGUMENTS=() +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + --) shift; CAKE_ARGUMENTS+=("$@"); break ;; + *) CAKE_ARGUMENTS+=("$1") ;; + esac + shift +done + +# Install the required tools locally. +echo "Restoring cake tools..." +dotnet restore $CAKE_CSPROJ --packages $TOOLS_DIR > /dev/null 2>&1 + +# Search for the CakeBuild binary. +CAKE_BINARY=$(find $CAKE_BINARY_PATH -name "Cake.dll") + +# Start Cake +echo "Running build script..." + +dotnet "$CAKE_BINARY" $SCRIPT "${CAKE_ARGUMENTS[@]}" diff --git a/cake.config b/cake.config new file mode 100644 index 0000000000..187d825591 --- /dev/null +++ b/cake.config @@ -0,0 +1,5 @@ + +[Nuget] +Source=https://api.nuget.org/v3/index.json +UseInProcessClient=true +LoadDependencies=true diff --git a/osu-resources b/osu-resources index c3848d8b1c..9ee64e369f 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit c3848d8b1c84966abe851d915bcca878415614b4 +Subproject commit 9ee64e369fe6fdafc6aed40f5a35b5f01eb82c53 diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 1129969694..96857d6b4f 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -27,9 +27,6 @@ namespace osu.Desktop.Overlays private NotificationOverlay notificationOverlay; private GameHost host; - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - [BackgroundDependencyLoader] private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host) { diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index e2fc4d14f6..e1e59804e5 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,9 +27,9 @@ - - - + + + diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 34e07170bd..cac1356c81 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -1,7 +1,6 @@ // 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; @@ -38,12 +37,12 @@ namespace osu.Game.Rulesets.Catch.Tests beatmap.HitObjects.Add(new JuiceStream { X = 0.5f - width / 2, - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) }, - CurveType = CurveType.Linear, + PathType = PathType.Linear, Distance = width * CatchPlayfield.BASE_WIDTH, StartTime = i * 2000, NewCombo = i % 8 == 0 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 51343d9e91..b76f591239 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 @@ -2,9 +2,10 @@ - - - + + + + WinExe diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 15d4edc411..fed65c42af 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -35,9 +35,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, + PathType = curveData.PathType, Distance = curveData.Distance, - RepeatSamples = curveData.RepeatSamples, + NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, NewCombo = comboData?.NewCombo ?? false, diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 773bf576dd..0c50dae9fb 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Objects public int IndexInBeatmap { get; set; } - public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4); + public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(ComboIndex % 4); public virtual bool NewCombo { get; set; } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 0344189af5..f1e131932b 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects if (TickDistance == 0) return; - var length = Curve.Distance; + var length = Path.Distance; var tickDistance = Math.Min(TickDistance, length); var spanDuration = length / Velocity; @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { StartTime = t, - X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new Droplet { StartTime = time, - X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, @@ -127,12 +127,12 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = Samples, StartTime = spanStartTime + spanDuration, - X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH }); } } - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; @@ -140,24 +140,24 @@ namespace osu.Game.Rulesets.Catch.Objects public double Distance { - get { return Curve.Distance; } - set { Curve.Distance = value; } + get { return Path.Distance; } + set { Path.Distance = value; } } - public SliderCurve Curve { get; } = new SliderCurve(); + public SliderPath Path { get; } = new SliderPath(); - public List ControlPoints + public Vector2[] ControlPoints { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } + get { return Path.ControlPoints; } + set { Path.ControlPoints = value; } } - public List> RepeatSamples { get; set; } = new List>(); + public List> NodeSamples { get; set; } = new List>(); - public CurveType CurveType + public PathType PathType { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } + get { return Path.PathType; } + set { Path.PathType = value; } } public double? LegacyLastTickOffset { get; set; } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index d49be69856..925e7aaac9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -5,11 +5,13 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Configuration; 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; +using OpenTK; namespace osu.Game.Rulesets.Catch.UI { @@ -17,13 +19,13 @@ namespace osu.Game.Rulesets.Catch.UI { public const float BASE_WIDTH = 512; - protected override Container Content => content; - private readonly Container content; - private readonly CatcherArea catcherArea; + protected override bool UserScrollSpeedAdjustment => false; + + protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Constant; + public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) - : base(BASE_WIDTH) { Direction.Value = ScrollingDirection.Down; @@ -32,27 +34,29 @@ namespace osu.Game.Rulesets.Catch.UI Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; - base.Content.Anchor = Anchor.BottomLeft; - base.Content.Origin = Anchor.BottomLeft; + Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate - base.Content.AddRange(new Drawable[] + InternalChild = new PlayfieldAdjustmentContainer { - explodingFruitContainer = new Container + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - }, - catcherArea = new CatcherArea(difficulty) - { - GetVisualRepresentation = getVisualRepresentation, - ExplodingFruitTarget = explodingFruitContainer, - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }); + explodingFruitContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + catcherArea = new CatcherArea(difficulty) + { + GetVisualRepresentation = getVisualRepresentation, + ExplodingFruitTarget = explodingFruitContainer, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + }, + HitObjectContainer + } + }; + + VisibleTimeRange.Value = BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); } public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj); diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 1ac052de4d..94233bc9f0 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -13,7 +13,6 @@ 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 { @@ -32,9 +31,7 @@ namespace osu.Game.Rulesets.Catch.UI public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); - protected override Vector2 PlayfieldArea => new Vector2(0.86f); // matches stable's vertical offset for catcher plate - - protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h) + public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index be56ccf8c1..06453ac32d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatcherArea : Container { - public const float CATCHER_SIZE = 84; + public const float CATCHER_SIZE = 100; protected readonly Catcher MovableCatcher; diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..ad0073ff12 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs @@ -0,0 +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.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class PlayfieldAdjustmentContainer : Container + { + protected override Container Content => content; + private readonly Container content; + + public PlayfieldAdjustmentContainer() + { + InternalChild = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + FillAspectRatio = 4f / 3, + Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both } + }; + } + + /// + /// A which scales its content relative to a target width. + /// + private class ScalingContainer : Container + { + protected override void Update() + { + base.Update(); + + Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.BASE_WIDTH); + Size = Vector2.Divide(Vector2.One, Scale); + } + } + } +} 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 3165f69a6b..98ad086c66 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 @@ -2,9 +2,10 @@ - - - + + + + WinExe diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index c15b303048..d86ebc9a09 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps TargetColumns = (int)Math.Max(1, roundedCircleSize); else { - float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count(); + float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) TargetColumns = 7; else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.RepeatSamples[index]; + return curveData.NodeSamples[index]; } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 37a8062d75..635004d2f6 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -470,7 +470,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy double segmentTime = (EndTime - HitObject.StartTime) / spanCount; int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.RepeatSamples[index]; + return curveData.NodeSamples[index]; } /// diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 05ca1d4365..7bd39adb45 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy drainTime = 10000; BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; - conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15; + conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)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/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs index 3e9c9feba1..1c9e1e4c73 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0); + Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0); Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down); } diff --git a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs similarity index 82% rename from osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs rename to osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index bfa6bc0a17..3da2538497 100644 --- a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; @@ -13,9 +12,9 @@ using osu.Game.Rulesets.UI.Scrolling; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays +namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteMask : HitObjectMask + public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject; @@ -23,13 +22,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays private readonly BodyPiece body; - public HoldNoteMask(DrawableHoldNote hold) + public HoldNoteSelectionBlueprint(DrawableHoldNote hold) : base(hold) { InternalChildren = new Drawable[] { - new HoldNoteNoteMask(hold.Head), - new HoldNoteNoteMask(hold.Tail), + new HoldNoteNoteSelectionBlueprint(hold.Head), + new HoldNoteNoteSelectionBlueprint(hold.Tail), body = new BodyPiece { AccentColour = Color4.Transparent @@ -59,9 +58,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays Y -= HitObject.Tail.DrawHeight; } - private class HoldNoteNoteMask : NoteMask + private class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint { - public HoldNoteNoteMask(DrawableNote note) + public HoldNoteNoteSelectionBlueprint(DrawableNote note) : base(note) { Select(); @@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays } // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. - public override bool HandleMouseInput => false; + public override bool HandlePositionalInput => false; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs new file mode 100644 index 0000000000..474b8c662e --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -0,0 +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.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class ManiaSelectionBlueprint : SelectionBlueprint + { + public ManiaSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + } + + public override void AdjustPosition(DragEvent dragEvent) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs similarity index 82% rename from osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs rename to osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 78f876cb14..7c0337dc4e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -3,15 +3,14 @@ using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays +namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class NoteMask : HitObjectMask + public class NoteSelectionBlueprint : ManiaSelectionBlueprint { - public NoteMask(DrawableNote note) + public NoteSelectionBlueprint(DrawableNote note) : base(note) { Scale = note.Scale; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs index a01947a60b..138a2c0273 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs @@ -20,8 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Size = Vector2.One }; - - protected override Vector2 PlayfieldArea => Vector2.One; } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index f37d8134ce..06d67821a9 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -1,22 +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.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays; 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 System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaHitObjectComposer : HitObjectComposer + public class ManiaHitObjectComposer : HitObjectComposer { protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; @@ -32,25 +33,22 @@ namespace osu.Game.Rulesets.Mania.Edit return dependencies; } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new ManiaEditRulesetContainer(ruleset, beatmap); + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + => new ManiaEditRulesetContainer(ruleset, beatmap); - protected override IReadOnlyList CompositionTools => new ICompositionTool[] - { - new HitObjectCompositionTool("Note"), - new HitObjectCompositionTool("Hold"), - }; + protected override IReadOnlyList CompositionTools => Array.Empty(); - public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) + public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) { case DrawableNote note: - return new NoteMask(note); + return new NoteSelectionBlueprint(note); case DrawableHoldNote holdNote: - return new HoldNoteMask(holdNote); + return new HoldNoteSelectionBlueprint(holdNote); } - return base.CreateMaskFor(hitObject); + return base.CreateBlueprintFor(hitObject); } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index aecfb50fbe..4790a77cc0 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -5,13 +5,11 @@ 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 class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap { public override string Name => "Dual Stages"; public override string ShortenedName => "DS"; @@ -34,22 +32,21 @@ namespace osu.Game.Rulesets.Mania.Mods mbc.TargetColumns *= 2; } - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToBeatmap(Beatmap beatmap) { - var mrc = (ManiaRulesetContainer)rulesetContainer; - - // Although this can work, for now let's not allow keymods for mania-specific beatmaps if (isForCurrentRuleset) return; + var maniaBeatmap = (ManiaBeatmap)beatmap; + var newDefinitions = new List(); - foreach (var existing in mrc.Beatmap.Stages) + foreach (var existing in maniaBeatmap.Stages) { newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); } - mrc.Beatmap.Stages = newDefinitions; + maniaBeatmap.Stages = newDefinitions; } public PlayfieldType PlayfieldType => PlayfieldType.Dual; diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d489d48fc3..09976e5994 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -30,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly Container explosionContainer; - protected override Container Content => hitObjectArea; - public Column() { RelativeSizeAxes = Axes.Y; @@ -54,7 +52,10 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both }, + hitObjectArea = new ColumnHitObjectArea(HitObjectContainer) + { + RelativeSizeAxes = Axes.Both, + }, explosionContainer = new Container { Name = "Hit explosions", diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index b5dfb0949a..5a4adfd72e 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -8,28 +8,24 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using OpenTK.Graphics; namespace osu.Game.Rulesets.Mania.UI.Components { - public class ColumnHitObjectArea : Container, IHasAccentColour + public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour { private const float hit_target_height = 10; private const float hit_target_bar_height = 2; - private Container content; - protected override Container Content => content; - private readonly IBindable direction = new Bindable(); - private Container hitTargetLine; + private readonly Container hitTargetLine; + private readonly Drawable hitTargetBar; - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) { - Drawable hitTargetBar; - InternalChildren = new[] { hitTargetBar = new Box @@ -45,13 +41,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components Masking = true, Child = new Box { RelativeSizeAxes = Axes.Both } }, - content = new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - }, + hitObjectContainer }; + } + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(direction => { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index dc66249cd9..f78cfefab8 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -30,10 +30,11 @@ namespace osu.Game.Rulesets.Mania.UI if (Result.IsHit) { - this.ScaleTo(0.8f); - this.ScaleTo(1, 250, Easing.OutElastic); + JudgementBody.ScaleTo(0.8f); + JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - this.Delay(50).FadeOut(200).ScaleTo(0.75f, 250); + JudgementBody.Delay(50).ScaleTo(0.75f, 250); + this.Delay(50).FadeOut(200); } Expire(); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 999f84ed8e..5c3a618a19 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; namespace osu.Game.Rulesets.Mania.UI { @@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.UI if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); + Size = new Vector2(1, 0.8f); + GridContainer playfieldGrid; AddInternal(playfieldGrid = new GridContainer { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 09ebde2799..500fb5a631 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -24,7 +24,6 @@ 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 { @@ -97,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.UI public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) + public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) { switch (h) { @@ -110,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.UI } } - protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs index 4d6c5a747a..8ee0fbf7fe 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaScrollingPlayfield : ScrollingPlayfield + public abstract class ManiaScrollingPlayfield : ScrollingPlayfield { private readonly IBindable direction = new Bindable(); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index f292d5ff16..8cf49686b9 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -30,8 +30,7 @@ namespace osu.Game.Rulesets.Mania.UI public IReadOnlyList Columns => columnFlow.Children; private readonly FillFlowContainer columnFlow; - protected override Container Content => barLineContainer; - private readonly Container barLineContainer; + private readonly Container barLineContainer; public Container Judgements => judgements; private readonly JudgementContainer judgements; @@ -105,6 +104,7 @@ namespace osu.Game.Rulesets.Mania.UI Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Y, + Child = HitObjectContainer } }, judgements = new JudgementContainer diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs new file mode 100644 index 0000000000..313438a337 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs @@ -0,0 +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.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseHitCirclePlacementBlueprint : PlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs new file mode 100644 index 0000000000..9662e0018f --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs @@ -0,0 +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.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseHitCircleSelectionBlueprint : SelectionBlueprintTestCase + { + private readonly DrawableHitCircle drawableObject; + + public TestCaseHitCircleSelectionBlueprint() + { + var hitCircle = new HitCircle { Position = new Vector2(256, 192) }; + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + + Add(drawableObject = new DrawableHitCircle(hitCircle)); + } + + protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index 3f9464a98f..0bd6bb5abc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(239, 176), - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(154, 28), @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, Distance = 700, RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats), + NodeSamples = createEmptySamples(repeats), StackHeight = 10 }; @@ -141,14 +141,14 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-(distance / 2), 0), - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(distance, 0), }, Distance = distance, RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats), + NodeSamples = createEmptySamples(repeats), StackHeight = stackHeight }; @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(200, 200), @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, Distance = 600, RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -181,10 +181,10 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Linear, + PathType = PathType.Linear, StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(150, 75), @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, Distance = 793.4417, RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -207,10 +207,10 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Bezier, + PathType = PathType.Bezier, StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(150, 75), @@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, Distance = 480, RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -232,10 +232,10 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Linear, + PathType = PathType.Linear, StartTime = Time.Current + 1000, Position = new Vector2(0, 0), - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(-200, 0), @@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, Distance = 1000, RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -264,8 +264,8 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-100, 0), - CurveType = CurveType.Catmull, - ControlPoints = new List + PathType = PathType.Catmull, + ControlPoints = new[] { Vector2.Zero, new Vector2(50, -50), @@ -274,7 +274,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, Distance = 300, RepeatCount = repeats, - RepeatSamples = repeatSamples + NodeSamples = repeatSamples }; addSlider(slider, 3, 1); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs new file mode 100644 index 0000000000..1f693ad9f4 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs @@ -0,0 +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.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSliderPlacementBlueprint : PlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs new file mode 100644 index 0000000000..78e3d76313 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs @@ -0,0 +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 System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSliderSelectionBlueprint : SelectionBlueprintTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SliderSelectionBlueprint), + typeof(SliderCircleSelectionBlueprint), + typeof(SliderBodyPiece), + typeof(SliderCircle), + typeof(PathControlPointVisualiser), + typeof(PathControlPointPiece) + }; + + private readonly DrawableSlider drawableObject; + + public TestCaseSliderSelectionBlueprint() + { + var slider = new Slider + { + Position = new Vector2(256, 192), + ControlPoints = new[] + { + Vector2.Zero, + new Vector2(150, 150), + new Vector2(300, 0) + }, + PathType = PathType.Bezier, + Distance = 350 + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + + Add(drawableObject = new DrawableSlider(slider)); + } + + protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs new file mode 100644 index 0000000000..9a90be2582 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs @@ -0,0 +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.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSpinnerPlacementBlueprint : PlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); + + protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs new file mode 100644 index 0000000000..a0cfd4487e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs @@ -0,0 +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 System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSpinnerSelectionBlueprint : SelectionBlueprintTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SpinnerSelectionBlueprint), + typeof(SpinnerPiece) + }; + + private readonly DrawableSpinner drawableSpinner; + + public TestCaseSpinnerSelectionBlueprint() + { + var spinner = new Spinner + { + Position = new Vector2(256, 256), + StartTime = -1000, + EndTime = 2000 + }; + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + Child = drawableSpinner = new DrawableSpinner(spinner) + }); + } + + protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }; + } +} 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 247d5e18c1..6117812f45 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 @@ -2,9 +2,10 @@ - - - + + + + WinExe diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 9e0e649eb2..87c81cdd3b 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -36,14 +36,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps StartTime = original.StartTime, Samples = original.Samples, ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, + PathType = curveData.PathType, Distance = curveData.Distance, - RepeatSamples = curveData.RepeatSamples, + NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset + LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset, + // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. + // this results in more (or less) ticks being generated in )Beatmap); + + var osuBeatmap = (Beatmap)Beatmap; + + // Reset stacking + foreach (var h in osuBeatmap.HitObjects) + h.StackHeight = 0; + + if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) + applyStacking(osuBeatmap); + else + applyStackingOld(osuBeatmap); } 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--) @@ -167,5 +173,40 @@ namespace osu.Game.Rulesets.Osu.Beatmaps } } } + + private void applyStackingOld(Beatmap beatmap) + { + for (int i = 0; i < beatmap.HitObjects.Count; i++) + { + OsuHitObject currHitObject = beatmap.HitObjects[i]; + + if (currHitObject.StackHeight != 0 && !(currHitObject is Slider)) + continue; + + double startTime = (currHitObject as IHasEndTime)?.EndTime ?? currHitObject.StartTime; + int sliderStack = 0; + + for (int j = i + 1; j < beatmap.HitObjects.Count; j++) + { + double stackThreshold = beatmap.HitObjects[i].TimePreempt * beatmap.BeatmapInfo.StackLeniency; + + if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime) + break; + + if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance) + { + currHitObject.StackHeight++; + startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime; + } + else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance) + { + //Case for sliders - bump notes down and right, rather than up and left. + sliderStack++; + beatmap.HitObjects[j].StackHeight -= sliderStack; + startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime; + } + } + } + } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 5e91ed7a97..8fc2b69267 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sectionLength = section_length * timeRate; // The first object doesn't generate a strain, so we begin with an incremented section end - double currentSectionEnd = 2 * sectionLength; + double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; foreach (OsuDifficultyHitObject h in difficultyBeatmap) { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; - int maxCombo = beatmap.HitObjects.Count(); + int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d4a60dd52f..b0887ac72b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); - beatmapMaxCombo = Beatmap.HitObjects.Count(); + beatmapMaxCombo = Beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above) beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs index 4443a0e66b..24d4677981 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing @@ -23,8 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { // 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); + difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate); } /// diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 29de23406b..39e3c009da 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -21,15 +21,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public OsuHitObject BaseObject { get; } /// - /// Normalized distance from the of the previous . + /// Normalized distance from the end position of the previous to the start position of this . /// - public double Distance { get; private set; } + public double JumpDistance { get; private set; } + + /// + /// Normalized distance between the start and end position of the previous . + /// + public double TravelDistance { get; private set; } /// /// Milliseconds elapsed since the StartTime of the previous . /// public double DeltaTime { get; private set; } + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms. + /// + public double StrainTime { get; private set; } + private readonly OsuHitObject lastObject; private readonly double timeRate; @@ -51,31 +61,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing 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; + float scalingFactor = normalized_radius / (float)BaseObject.Radius; if (BaseObject.Radius < 30) { - double smallCircleBonus = Math.Min(30 - BaseObject.Radius, 5) / 50; + float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50; scalingFactor *= 1 + smallCircleBonus; } Vector2 lastCursorPosition = lastObject.StackedPosition; - float lastTravelDistance = 0; var lastSlider = lastObject as Slider; if (lastSlider != null) { computeSliderCursorPosition(lastSlider); lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition; - lastTravelDistance = lastSlider.LazyTravelDistance; + + TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; } - Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor; + // Don't need to jump to reach spinners + if (!(BaseObject is Spinner)) + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; } private void setTimingValues() { - // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure. - DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate); + DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate; + + // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure + StrainTime = Math.Max(50, DeltaTime); } private void computeSliderCursorPosition(Slider slider) @@ -87,8 +101,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing float approxFollowCircleRadius = (float)(slider.Radius * 3); var computeVertex = new Action(t => { + double progress = ((int)t - (int)slider.StartTime) / (float)(int)slider.SpanDuration; + if (progress % 2 > 1) + progress = 1 - progress % 1; + else + progress = progress % 1; + // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) - var diff = slider.StackedPositionAt(t) - slider.LazyEndPosition.Value; + var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value; float dist = diff.Length; if (dist > approxFollowCircleRadius) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 0a45c62671..f11b6d66f6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills 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; + protected override double StrainValueOf(OsuDifficultyHitObject current) + => (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index b807f20037..1cde03624b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double StrainValueOf(OsuDifficultyHitObject current) { - double distance = current.Distance; + double distance = current.TravelDistance + current.JumpDistance; double speedValue; if (distance > single_spacing_threshold) @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills else speedValue = 0.95; - return speedValue / current.DeltaTime; + return speedValue / current.StrainTime; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs new file mode 100644 index 0000000000..9c33435285 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -0,0 +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.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components +{ + public class HitCirclePiece : CompositeDrawable + { + private readonly HitCircle hitCircle; + + public HitCirclePiece(HitCircle hitCircle) + { + this.hitCircle = hitCircle; + Origin = Anchor.Centre; + + Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2); + Scale = new Vector2(hitCircle.Scale); + CornerRadius = Size.X / 2; + + InternalChild = new RingPiece(); + + hitCircle.PositionChanged += _ => UpdatePosition(); + hitCircle.StackHeightChanged += _ => UpdatePosition(); + hitCircle.ScaleChanged += _ => Scale = new Vector2(hitCircle.Scale); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + + UpdatePosition(); + } + + protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs new file mode 100644 index 0000000000..eddd399a4a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -0,0 +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.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles +{ + public class HitCirclePlacementBlueprint : PlacementBlueprint + { + public new HitCircle HitObject => (HitCircle)base.HitObject; + + public HitCirclePlacementBlueprint() + : base(new HitCircle()) + { + InternalChild = new HitCirclePiece(HitObject); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Fixes a 1-frame position discrpancy due to the first mouse move event happening in the next frame + HitObject.Position = GetContainingInputManager().CurrentState.Mouse.Position; + } + + protected override bool OnClick(ClickEvent e) + { + HitObject.StartTime = EditorClock.CurrentTime; + EndPlacement(); + return true; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + HitObject.Position = e.MousePosition; + return true; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs new file mode 100644 index 0000000000..a59dac1834 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -0,0 +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.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles +{ + public class HitCircleSelectionBlueprint : OsuSelectionBlueprint + { + public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle) + : base(hitCircle) + { + InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs new file mode 100644 index 0000000000..8431d5d5d0 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -0,0 +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.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints +{ + public class OsuSelectionBlueprint : SelectionBlueprint + { + protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject; + + public OsuSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + } + + public override void AdjustPosition(DragEvent dragEvent) => OsuObject.Position += dragEvent.Delta; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs new file mode 100644 index 0000000000..175e9d79f4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -0,0 +1,113 @@ +// 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.Graphics.Lines; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class PathControlPointPiece : CompositeDrawable + { + private readonly Slider slider; + private readonly int index; + + private readonly Path path; + private readonly CircularContainer marker; + + [Resolved] + private OsuColour colours { get; set; } + + public PathControlPointPiece(Slider slider, int index) + { + this.slider = slider; + this.index = index; + + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + path = new SmoothPath + { + Anchor = Anchor.Centre, + PathWidth = 1 + }, + marker = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(10), + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + } + }; + } + + protected override void Update() + { + base.Update(); + + Position = slider.StackedPosition + slider.ControlPoints[index]; + + marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow; + + path.ClearVertices(); + + if (index != slider.ControlPoints.Length - 1) + { + path.AddVertex(Vector2.Zero); + path.AddVertex(slider.ControlPoints[index + 1] - slider.ControlPoints[index]); + } + + path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override bool OnDrag(DragEvent e) + { + var newControlPoints = slider.ControlPoints.ToArray(); + + if (index == 0) + { + // Special handling for the head - only the position of the slider changes + slider.Position += e.Delta; + + // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta + for (int i = 1; i < newControlPoints.Length; i++) + newControlPoints[i] -= e.Delta; + } + else + newControlPoints[index] += e.Delta; + + if (isSegmentSeparatorWithNext) + newControlPoints[index + 1] = newControlPoints[index]; + + if (isSegmentSeparatorWithPrevious) + newControlPoints[index - 1] = newControlPoints[index]; + + slider.ControlPoints = newControlPoints; + slider.Path.Calculate(true); + + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) => true; + + private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious; + + private bool isSegmentSeparatorWithNext => index < slider.ControlPoints.Length - 1 && slider.ControlPoints[index + 1] == slider.ControlPoints[index]; + + private bool isSegmentSeparatorWithPrevious => index > 0 && slider.ControlPoints[index - 1] == slider.ControlPoints[index]; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs new file mode 100644 index 0000000000..db8e879126 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -0,0 +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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class PathControlPointVisualiser : CompositeDrawable + { + private readonly Slider slider; + + private readonly Container pieces; + + public PathControlPointVisualiser(Slider slider) + { + this.slider = slider; + + InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; + + slider.ControlPointsChanged += _ => updatePathControlPoints(); + updatePathControlPoints(); + } + + private void updatePathControlPoints() + { + while (slider.ControlPoints.Length > pieces.Count) + pieces.Add(new PathControlPointPiece(slider, pieces.Count)); + while (slider.ControlPoints.Length < pieces.Count) + pieces.Remove(pieces[pieces.Count - 1]); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs new file mode 100644 index 0000000000..6fc7d39e6c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -0,0 +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.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class SliderBodyPiece : CompositeDrawable + { + private readonly Slider slider; + private readonly ManualSliderBody body; + + public SliderBodyPiece(Slider slider) + { + this.slider = slider; + + InternalChild = body = new ManualSliderBody + { + AccentColour = Color4.Transparent, + PathWidth = slider.Scale * 64 + }; + + slider.PositionChanged += _ => updatePosition(); + slider.ScaleChanged += _ => body.PathWidth = slider.Scale * 64; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + body.BorderColour = colours.Yellow; + + updatePosition(); + } + + private void updatePosition() => Position = slider.StackedPosition; + + protected override void Update() + { + base.Update(); + + slider.Path.Calculate(); + + var vertices = new List(); + slider.Path.GetPathToProgress(vertices, 0, 1); + + body.SetVertices(vertices); + + Size = body.Size; + OriginPosition = body.PathOffset; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs new file mode 100644 index 0000000000..a91739737f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs @@ -0,0 +1,36 @@ +// 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.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class SliderCirclePiece : HitCirclePiece + { + private readonly Slider slider; + private readonly SliderPosition position; + + public SliderCirclePiece(Slider slider, SliderPosition position) + : base(slider.HeadCircle) + { + this.slider = slider; + this.position = position; + + slider.ControlPointsChanged += _ => UpdatePosition(); + } + + protected override void UpdatePosition() + { + switch (position) + { + case SliderPosition.Start: + Position = slider.StackedPosition + slider.Path.PositionAt(0); + break; + case SliderPosition.End: + Position = slider.StackedPosition + slider.Path.PositionAt(1); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs new file mode 100644 index 0000000000..4bac9d3556 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -0,0 +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.Input.Events; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint + { + private readonly Slider slider; + + public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position) + : base(hitObject) + { + this.slider = slider; + + InternalChild = new SliderCirclePiece(slider, position); + + Select(); + } + + // 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 HandlePositionalInput => false; + + public override void AdjustPosition(DragEvent dragEvent) => slider.Position += dragEvent.Delta; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs new file mode 100644 index 0000000000..add9cb69f3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -0,0 +1,180 @@ +// 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.Input.Events; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using OpenTK; +using OpenTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public class SliderPlacementBlueprint : PlacementBlueprint + { + public new Objects.Slider HitObject => (Objects.Slider)base.HitObject; + + private readonly List segments = new List(); + private Vector2 cursor; + + private PlacementState state; + + public SliderPlacementBlueprint() + : base(new Objects.Slider()) + { + RelativeSizeAxes = Axes.Both; + segments.Add(new Segment(Vector2.Zero)); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new SliderBodyPiece(HitObject), + new SliderCirclePiece(HitObject, SliderPosition.Start), + new SliderCirclePiece(HitObject, SliderPosition.End), + new PathControlPointVisualiser(HitObject), + }; + + setState(PlacementState.Initial); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + switch (state) + { + case PlacementState.Initial: + HitObject.Position = e.MousePosition; + return true; + case PlacementState.Body: + cursor = e.MousePosition - HitObject.Position; + return true; + } + + return false; + } + + protected override bool OnClick(ClickEvent e) + { + switch (state) + { + case PlacementState.Initial: + beginCurve(); + break; + case PlacementState.Body: + switch (e.Button) + { + case MouseButton.Left: + segments.Last().ControlPoints.Add(cursor); + break; + } + + break; + } + + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + if (state == PlacementState.Body && e.Button == MouseButton.Right) + endCurve(); + return base.OnMouseUp(e); + } + + protected override bool OnDoubleClick(DoubleClickEvent e) + { + segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last())); + return true; + } + + private void beginCurve() + { + BeginPlacement(); + + HitObject.StartTime = EditorClock.CurrentTime; + setState(PlacementState.Body); + } + + private void endCurve() + { + updateSlider(); + EndPlacement(); + } + + protected override void Update() + { + base.Update(); + updateSlider(); + } + + private void updateSlider() + { + for (int i = 0; i < segments.Count; i++) + segments[i].Calculate(i == segments.Count - 1 ? (Vector2?)cursor : null); + + HitObject.ControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); + HitObject.PathType = HitObject.ControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear; + HitObject.Distance = segments.Sum(s => s.Distance); + } + + private void setState(PlacementState newState) + { + state = newState; + } + + private enum PlacementState + { + Initial, + Body, + } + + private class Segment + { + public float Distance { get; private set; } + + public readonly List ControlPoints = new List(); + + public Segment(Vector2 offset) + { + ControlPoints.Add(offset); + } + + public void Calculate(Vector2? cursor = null) + { + Span allControlPoints = stackalloc Vector2[ControlPoints.Count + (cursor.HasValue ? 1 : 0)]; + + for (int i = 0; i < ControlPoints.Count; i++) + allControlPoints[i] = ControlPoints[i]; + if (cursor.HasValue) + allControlPoints[allControlPoints.Length - 1] = cursor.Value; + + List result; + + switch (allControlPoints.Length) + { + case 1: + case 2: + result = PathApproximator.ApproximateLinear(allControlPoints); + break; + default: + result = PathApproximator.ApproximateBezier(allControlPoints); + break; + } + + Distance = 0; + for (int i = 0; i < result.Count - 1; i++) + Distance += Vector2.Distance(result[i], result[i + 1]); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs new file mode 100644 index 0000000000..a117a7056e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs @@ -0,0 +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.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public enum SliderPosition + { + Start, + End + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs new file mode 100644 index 0000000000..4810d76bf8 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -0,0 +1,32 @@ +// 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.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public class SliderSelectionBlueprint : OsuSelectionBlueprint + { + private readonly SliderCircleSelectionBlueprint headBlueprint; + + public SliderSelectionBlueprint(DrawableSlider slider) + : base(slider) + { + var sliderObject = (Slider)slider.HitObject; + + InternalChildren = new Drawable[] + { + new SliderBodyPiece(sliderObject), + headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start), + new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End), + new PathControlPointVisualiser(sliderObject), + }; + } + + public override Vector2 SelectionPoint => headBlueprint.SelectionPoint; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs new file mode 100644 index 0000000000..bd63a3e607 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs @@ -0,0 +1,66 @@ +// 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.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components +{ + public class SpinnerPiece : CompositeDrawable + { + private readonly Spinner spinner; + private readonly CircularContainer circle; + + public SpinnerPiece(Spinner spinner) + { + this.spinner = spinner; + + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + Size = new Vector2(1.3f); + + RingPiece ring; + InternalChildren = new Drawable[] + { + circle = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0.5f, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + ring = new RingPiece + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }; + + ring.Scale = new Vector2(spinner.Scale); + + spinner.PositionChanged += _ => updatePosition(); + spinner.StackHeightChanged += _ => updatePosition(); + spinner.ScaleChanged += _ => ring.Scale = new Vector2(spinner.Scale); + + updatePosition(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + + private void updatePosition() => Position = spinner.Position; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs new file mode 100644 index 0000000000..c97adde427 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -0,0 +1,45 @@ +// 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.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners +{ + public class SpinnerPlacementBlueprint : PlacementBlueprint + { + public new Spinner HitObject => (Spinner)base.HitObject; + + private readonly SpinnerPiece piece; + + private bool isPlacingEnd; + + public SpinnerPlacementBlueprint() + : base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 }) + { + InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f }; + } + + protected override bool OnClick(ClickEvent e) + { + if (isPlacingEnd) + { + HitObject.EndTime = EditorClock.CurrentTime; + EndPlacement(); + } + else + { + HitObject.StartTime = EditorClock.CurrentTime; + + isPlacingEnd = true; + piece.FadeTo(1f, 150, Easing.OutQuint); + } + + return true; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs new file mode 100644 index 0000000000..9e9cc87c5e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs @@ -0,0 +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.Input.Events; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners +{ + public class SpinnerSelectionBlueprint : OsuSelectionBlueprint + { + private readonly SpinnerPiece piece; + + public SpinnerSelectionBlueprint(DrawableSpinner spinner) + : base(spinner) + { + InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos); + + public override void AdjustPosition(DragEvent dragEvent) + { + // Spinners don't support position adjustments + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs new file mode 100644 index 0000000000..ddab70d53a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs @@ -0,0 +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.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class HitCircleCompositionTool : HitObjectCompositionTool + { + public HitCircleCompositionTool() + : base(nameof(HitCircle)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs deleted file mode 100644 index a2aa639004..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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 deleted file mode 100644 index adb28289cf..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ /dev/null @@ -1,61 +0,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 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 deleted file mode 100644 index 0f6143a83d..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs +++ /dev/null @@ -1,67 +0,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 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/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs index 6efa16bf56..8571de39f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs @@ -4,6 +4,7 @@ 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 @@ -15,8 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override Vector2 PlayfieldArea => Vector2.One; - protected override CursorContainer CreateCursor() => null; + + protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One }; } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index dce1fc2851..a706e1d4be 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; 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.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; @@ -15,35 +18,38 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuHitObjectComposer : HitObjectComposer + public class OsuHitObjectComposer : HitObjectComposer { public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) { } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap); + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + => new OsuEditRulesetContainer(ruleset, beatmap); - protected override IReadOnlyList CompositionTools => new ICompositionTool[] + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new HitObjectCompositionTool(), - new HitObjectCompositionTool(), - new HitObjectCompositionTool() + new HitCircleCompositionTool(), + new SliderCompositionTool(), + new SpinnerCompositionTool() }; - protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both }; + protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; - public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) + public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) { case DrawableHitCircle circle: - return new HitCircleMask(circle); + return new HitCircleSelectionBlueprint(circle); case DrawableSlider slider: - return new SliderMask(slider); + return new SliderSelectionBlueprint(slider); + case DrawableSpinner spinner: + return new SpinnerSelectionBlueprint(spinner); } - return base.CreateMaskFor(hitObject); + return base.CreateBlueprintFor(hitObject); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs new file mode 100644 index 0000000000..6d4f67c597 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs @@ -0,0 +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.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class SliderCompositionTool : HitObjectCompositionTool + { + public SliderCompositionTool() + : base(nameof(Slider)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs new file mode 100644 index 0000000000..5c19a1bac0 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs @@ -0,0 +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.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class SpinnerCompositionTool : HitObjectCompositionTool + { + public SpinnerCompositionTool() + : base(nameof(Spinner)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 7a30e6b134..e01d71e1f8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -1,7 +1,6 @@ // 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; @@ -33,11 +32,12 @@ namespace osu.Game.Rulesets.Osu.Mods 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))); + var newControlPoints = new Vector2[slider.ControlPoints.Length]; + for (int i = 0; i < slider.ControlPoints.Length; i++) + newControlPoints[i] = new Vector2(slider.ControlPoints[i].X, -slider.ControlPoints[i].Y); slider.ControlPoints = newControlPoints; - slider.Curve?.Calculate(); // Recalculate the slider curve + slider.Path?.Calculate(); // Recalculate the slider curve } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index f3b7d60cf0..8d27502b3c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Input.States; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; @@ -77,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Mods wasLeft = !wasLeft; } - osuInputManager.HandleCustomInput(new InputState(), state); + state.Apply(osuInputManager.CurrentState, osuInputManager); } public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs new file mode 100644 index 0000000000..440b314e5f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -0,0 +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 System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Transform"; + public override string ShortenedName => "TR"; + public override FontAwesome Icon => FontAwesome.fa_arrows; + public override ModType Type => ModType.Fun; + public override string Description => "Everything rotates. EVERYTHING."; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) }; + + private float theta; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + { + var hitObject = (OsuHitObject) drawable.HitObject; + + float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2; + + Vector2 originalPosition = drawable.Position; + Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance; + + //the - 1 and + 1 prevents the hit objects to appear in the wrong position. + double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1; + double moveDuration = hitObject.TimePreempt + 1; + + using (drawable.BeginAbsoluteSequence(appearTime, true)) + { + drawable + .MoveToOffset(appearOffset) + .MoveTo(originalPosition, moveDuration, Easing.InOutSine); + } + + theta += (float) hitObject.TimeFadeIn / 1000; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs new file mode 100644 index 0000000000..e0a93453ce --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -0,0 +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 osu.Framework.Graphics; +using osu.Game.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 OpenTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects + { + public override string Name => "Wiggle"; + public override string ShortenedName => "WG"; + public override FontAwesome Icon => FontAwesome.fa_certificate; + public override ModType Type => ModType.Fun; + public override string Description => "They just won't stay still..."; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; + + private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles + private const int wiggle_strength = 10; // Higher = stronger wiggles + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; + } + + private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) + { + var osuObject = (OsuHitObject)drawable.HitObject; + Vector2 origin = drawable.Position; + + // Wiggle the repeat points with the slider instead of independently. + // Also fixes an issue with repeat points being positioned incorrectly. + if (osuObject is RepeatPoint) + return; + + Random objRand = new Random((int)osuObject.StartTime); + + // Wiggle all objects during TimePreempt + int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration; + + void wiggle() + { + float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); + float nextDist = (float)(objRand.NextDouble() * wiggle_strength); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); + } + + for (int i = 0; i < amountWiggles; i++) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true)) + wiggle(); + + // Keep wiggling sliders and spinners for their duration + if (!(osuObject is IHasEndTime endTime)) + return; + + amountWiggles = (int)(endTime.Duration / wiggle_duration); + + for (int i = 0; i < amountWiggles; i++) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true)) + wiggle(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 4bdddcef11..e663989eeb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -61,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = circle.DrawSize; HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + HitObject.StackHeightChanged += _ => Position = HitObject.StackedPosition; + HitObject.ScaleChanged += s => Scale = new Vector2(s); } public override Color4 AccentColour diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 66f491532d..514ae09064 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public readonly DrawableHitCircle HeadCircle; public readonly DrawableSliderTail TailCircle; - public readonly SliderBody Body; + public readonly SnakingSliderBody Body; public readonly SliderBall Ball; public DrawableSlider(Slider s) @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { - Body = new SliderBody(s) + Body = new SnakingSliderBody(s) { PathWidth = s.Scale * 64, }, @@ -85,6 +85,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + HitObject.ScaleChanged += _ => + { + Body.PathWidth = HitObject.Scale * 64; + Ball.Scale = new Vector2(HitObject.Scale); + }; + + slider.ControlPointsChanged += _ => Body.Refresh(); } public override Color4 AccentColour @@ -119,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables 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 c in components.OfType()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; Size = Body.Size; @@ -184,6 +191,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ApproachCircle; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 6d6cba4936..6a836679a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -16,7 +16,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { this.slider = slider; - Position = HitObject.Position - slider.Position; + h.PositionChanged += _ => updatePosition(); + slider.ControlPointsChanged += _ => updatePosition(); + + updatePosition(); } protected override void Update() @@ -33,5 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Action OnShake; protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); + + private void updatePosition() => Position = HitObject.Position - slider.Position; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 45c925b87a..cc88a6718b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking { + private readonly Slider slider; + /// /// The judgement text is provided by the . /// @@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) : base(hitCircle) { + this.slider = slider; + Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; @@ -25,7 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true; - Position = HitObject.Position - slider.Position; + hitCircle.PositionChanged += _ => updatePosition(); + slider.ControlPointsChanged += _ => updatePosition(); + + updatePosition(); } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -33,5 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered && timeOffset >= 0) ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss); } + + private void updatePosition() => Position = HitObject.Position - slider.Position; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 51b1990a21..f3846bd52f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -112,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Alpha = 0 } }; + + s.PositionChanged += _ => Position = s.Position; } public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); @@ -167,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { - Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); + Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; if (!spmCounter.IsPresent && Disc.Tracking) spmCounter.FadeIn(HitObject.TimeFadeIn); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index bd7a4ad3f6..6bb6991cc0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class CirclePiece : Container, IKeyBindingHandler { + // IsHovered is used + public override bool HandlePositionalInput => true; + public Func Hit; public CirclePiece() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs new file mode 100644 index 0000000000..9d239c15f2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs @@ -0,0 +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 OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + /// + /// A with the ability to set the drawn vertices manually. + /// + public class ManualSliderBody : SliderBody + { + public new void SetVertices(IReadOnlyList vertices) + { + base.SetVertices(vertices); + Size = Path.Size; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 30140484de..acb3ee92ff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -4,7 +4,6 @@ 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; @@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class NumberPiece : Container { - private readonly SpriteText number; + private readonly SkinnableSpriteText number; public string Text { @@ -41,15 +40,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }, Child = new Box() }, s => s.GetTexture("Play/osu/hitcircle") == null), - number = new OsuSpriteText + number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText { - Text = @"1", Font = @"Venera", UseFullGlyphHeight = false, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, TextSize = 40, - Alpha = 1 + }, restrictSize: false) + { + Text = @"1" } }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index b79750a1b3..3081ae49fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -5,11 +5,11 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Skinning; +using OpenTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -102,24 +102,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - private InputState lastState; + private Vector2? lastScreenSpaceMousePosition; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { - lastState = state; - return base.OnMouseDown(state, args); + lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { - lastState = state; - return base.OnMouseUp(state, args); + lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; + return base.OnMouseUp(e); } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { - lastState = state; - return base.OnMouseMove(state); + lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; + return base.OnMouseMove(e); } public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) @@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (Time.Current < slider.EndTime) { - // Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position. + // Make sure to use the base version of ReceivePositionalInputAt so that we correctly check the position. Tracking = canCurrentlyTrack - && lastState != null - && ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) + && lastScreenSpaceMousePosition.HasValue + && ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 6f0197e711..ca2daa3adb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -1,230 +1,139 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Lines; -using osu.Framework.Graphics.Textures; -using OpenTK.Graphics.ES30; -using OpenTK.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Game.Rulesets.Objects.Types; using OpenTK; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; +using OpenTK.Graphics; +using OpenTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SliderBody : Container, ISliderProgress + public abstract class SliderBody : CompositeDrawable { - private readonly Path path; + private readonly SliderPath path; + protected Path Path => path; + private readonly BufferedContainer container; public float PathWidth { - get { return path.PathWidth; } - set { path.PathWidth = value; } + get => 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; + public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]); /// /// Used to colour the path. /// public Color4 AccentColour { - get { return accentColour; } + get => path.AccentColour; set { - if (accentColour == value) + if (path.AccentColour == value) return; - accentColour = value; + path.AccentColour = value; - if (LoadState >= LoadState.Ready) - reloadTexture(); + container.ForceRedraw(); } } - private Color4 borderColour = Color4.White; - /// /// Used to colour the path border. /// public new Color4 BorderColour { - get { return borderColour; } + get => path.BorderColour; set { - if (borderColour == value) + if (path.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; + path.BorderColour = value; container.ForceRedraw(); } } - [BackgroundDependencyLoader] - private void load() + public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; + + protected SliderBody() { - reloadTexture(); - computeSize(); + InternalChild = container = new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + CacheDrawnFrameBuffer = true, + Child = path = new SliderPath { Blending = BlendingMode.None } + }; + + container.Attach(RenderbufferInternalFormat.DepthComponent16); } - private void reloadTexture() + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos); + + /// + /// Sets the vertices of the path which should be drawn by this . + /// + /// The vertices + protected void SetVertices(IReadOnlyList vertices) { - var texture = new Texture(textureWidth, 1); - - //initialise background - var raw = new Image(textureWidth, 1); - - 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) - { - raw[i, 0] = new Rgba32(BorderColour.R, BorderColour.G, BorderColour.B, Math.Min(progress / aa_portion, 1) * BorderColour.A); - } - else - { - progress -= border_portion; - raw[i, 0] = new Rgba32(AccentColour.R, AccentColour.G, AccentColour.B, - (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * AccentColour.A); - } - } - - texture.SetData(new TextureUpload(raw)); - path.Texture = texture; - + path.Vertices = vertices; container.ForceRedraw(); } - private void computeSize() + private class SliderPath : SmoothPath { - // Generate the entire curve - slider.Curve.GetPathToProgress(CurrentCurve, 0, 1); - foreach (Vector2 p in CurrentCurve) - path.AddVertex(p); + private const float border_portion = 0.128f; + private const float gradient_portion = 1 - border_portion; - Size = path.Size; + private const float opacity_at_centre = 0.3f; + private const float opacity_at_edge = 0.8f; - topLeftOffset = path.PositionInBoundingBox(Vector2.Zero); - PathOffset = path.PositionInBoundingBox(CurrentCurve[0]); - } + private Color4 borderColour = Color4.White; - 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) + public Color4 BorderColour { - if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) + get => borderColour; + set { - start = 0; - end = SnakingOut ? spanProgress : 1; - } - else - { - start = SnakingOut ? spanProgress : 0; + if (borderColour == value) + return; + borderColour = value; + + InvalidateTexture(); } } - SetRange(start, end); + private Color4 accentColour = Color4.White; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + accentColour = value; + + InvalidateTexture(); + } + } + + protected override Color4 ColourAt(float position) + { + if (position <= border_portion) + return BorderColour; + + position -= border_portion; + return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A); + } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs new file mode 100644 index 0000000000..0e6f3ad16c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -0,0 +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 osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + /// + /// A which changes its curve depending on the snaking progress. + /// + public class SnakingSliderBody : SliderBody, ISliderProgress + { + 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; } + + public override Vector2 PathOffset => snakedPathOffset; + + /// + /// The top-left position of the path when fully snaked. + /// + private Vector2 snakedPosition; + + /// + /// The offset of the path from when fully snaked. + /// + private Vector2 snakedPathOffset; + + private readonly Slider slider; + + public SnakingSliderBody(Slider slider) + { + this.slider = slider; + } + + [BackgroundDependencyLoader] + private void load() + { + Refresh(); + } + + 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); + } + + public void Refresh() + { + // Generate the entire curve + slider.Path.GetPathToProgress(CurrentCurve, 0, 1); + SetVertices(CurrentCurve); + + // The body is sized to the full path size to avoid excessive autosize computations + Size = Path.Size; + + snakedPosition = Path.PositionInBoundingBox(Vector2.Zero); + snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]); + + var lastSnakedStart = SnakedStart ?? 0; + var lastSnakedEnd = SnakedEnd ?? 0; + + SnakedStart = null; + SnakedEnd = null; + + setRange(lastSnakedStart, lastSnakedEnd); + } + + private void setRange(double p0, double p1) + { + if (p0 > p1) + MathHelper.Swap(ref p0, ref p1); + + if (SnakedStart == p0 && SnakedEnd == p1) return; + + SnakedStart = p0; + SnakedEnd = p1; + + slider.Path.GetPathToProgress(CurrentCurve, p0, p1); + + SetVertices(CurrentCurve); + + // The bounding box of the path expands as it snakes, which in turn shifts the position of the path. + // Depending on the direction of expansion, it may appear as if the path is expanding towards the position of the slider + // rather than expanding out from the position of the slider. + // To remove this effect, the path's position is shifted towards its final snaked position + + Path.Position = snakedPosition - Path.PositionInBoundingBox(Vector2.Zero); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index 1a7455838f..584fd93a70 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -11,9 +11,6 @@ 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 diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 5aba60ba03..4dd1c5f218 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -4,7 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private bool tracking; public bool Tracking @@ -68,10 +68,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { - mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position); - return base.OnMouseMove(state); + mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition); + return base.OnMouseMove(e); } private Vector2 mousePosition; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index fdf5aaffa8..61d199a7dc 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -7,22 +7,23 @@ 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 abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition { public const double OBJECT_RADIUS = 64; public event Action PositionChanged; + public event Action StackHeightChanged; + public event Action ScaleChanged; public double TimePreempt = 600; public double TimeFadeIn = 400; private Vector2 position; - public Vector2 Position + public virtual Vector2 Position { get => position; set @@ -44,13 +45,39 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedEndPosition => EndPosition + StackOffset; - public virtual int StackHeight { get; set; } + private int stackHeight; + + public int StackHeight + { + get => stackHeight; + set + { + if (stackHeight == value) + return; + stackHeight = value; + + StackHeightChanged?.Invoke(value); + } + } public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); public double Radius => OBJECT_RADIUS * Scale; - public float Scale { get; set; } = 1; + private float scale = 1; + + public float Scale + { + get => scale; + set + { + if (scale == value) + return; + scale = value; + + ScaleChanged?.Invoke(value); + } + } public virtual bool NewCombo { get; set; } @@ -72,8 +99,6 @@ namespace osu.Game.Rulesets.Osu.Objects Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } - public virtual void OffsetPosition(Vector2 offset) => Position += offset; - protected override HitWindows CreateHitWindows() => new OsuHitWindows(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 7a0dcc77a6..cff742ca29 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Osu.Objects /// private const float base_scoring_distance = 100; - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + public event Action ControlPointsChanged; + + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); @@ -50,24 +52,49 @@ namespace osu.Game.Rulesets.Osu.Objects } } - public SliderCurve Curve { get; } = new SliderCurve(); + public SliderPath Path { get; } = new SliderPath(); - public List ControlPoints + public Vector2[] ControlPoints { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } + get => Path.ControlPoints; + set + { + if (Path.ControlPoints == value) + return; + Path.ControlPoints = value; + + ControlPointsChanged?.Invoke(value); + + if (TailCircle != null) + TailCircle.Position = EndPosition; + } } - public CurveType CurveType + public PathType PathType { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } + get { return Path.PathType; } + set { Path.PathType = value; } } public double Distance { - get { return Curve.Distance; } - set { Curve.Distance = value; } + get { return Path.Distance; } + set { Path.Distance = value; } + } + + public override Vector2 Position + { + get => base.Position; + set + { + base.Position = value; + + if (HeadCircle != null) + HeadCircle.Position = value; + + if (TailCircle != null) + TailCircle.Position = EndPosition; + } } public double? LegacyLastTickOffset { get; set; } @@ -84,7 +111,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; - public List> RepeatSamples { get; set; } = new List>(); + public List> NodeSamples { get; set; } = new List>(); + public int RepeatCount { get; set; } /// @@ -92,8 +120,21 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double SpanDuration => Duration / this.SpanCount(); - public double Velocity; - public double TickDistance; + /// + /// Velocity of this . + /// + public double Velocity { get; private set; } + + /// + /// Spacing between s of this . + /// + public double TickDistance { get; private set; } + + /// + /// An extra multiplier that affects the number of s generated by this . + /// An increase in this value increases , which reduces the number of ticks generated. + /// + public double TickDistanceMultiplier = 1; public HitCircle HeadCircle; public SliderTailCircle TailCircle; @@ -108,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = scoringDistance / difficulty.SliderTickRate; + TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; } protected override void CreateNestedHitObjects() @@ -125,17 +166,17 @@ namespace osu.Game.Rulesets.Osu.Objects private void createSliderEnds() { - HeadCircle = new SliderCircle(this) + HeadCircle = new SliderCircle { StartTime = StartTime, Position = Position, - Samples = Samples, + Samples = getNodeSamples(0), SampleControlPoint = SampleControlPoint, IndexInCurrentCombo = IndexInCurrentCombo, ComboIndex = ComboIndex, }; - TailCircle = new SliderTailCircle(this) + TailCircle = new SliderTailCircle { StartTime = EndTime, Position = EndPosition, @@ -149,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects private void createTicks() { - var length = Curve.Distance; + var length = Path.Distance; var tickDistance = MathHelper.Clamp(TickDistance, 0, length); if (tickDistance == 0) return; @@ -188,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Objects SpanIndex = span, SpanStartTime = spanStartTime, StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Position + Curve.PositionAt(distanceProgress), + Position = Position + Path.PositionAt(distanceProgress), StackHeight = StackHeight, Scale = Scale, Samples = sampleList @@ -206,14 +247,21 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = repeatIndex, SpanDuration = SpanDuration, StartTime = StartTime + repeat * SpanDuration, - Position = Position + Curve.PositionAt(repeat % 2), + Position = Position + Path.PositionAt(repeat % 2), StackHeight = StackHeight, Scale = Scale, - Samples = new List(RepeatSamples[repeatIndex]) + Samples = getNodeSamples(1 + repeatIndex) }); } } + private List getNodeSamples(int nodeIndex) + { + if (nodeIndex < NodeSamples.Count) + return NodeSamples[nodeIndex]; + return Samples; + } + public override Judgement CreateJudgement() => new OsuJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs index 1bdd16c9df..deda951378 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs @@ -1,19 +1,9 @@ // 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/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 23616ea005..b567bd8423 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -8,11 +8,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderTailCircle : SliderCircle { - public SliderTailCircle(Slider slider) - : base(slider) - { - } - public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index e7bbe755a0..e2f4c43a23 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -4,8 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using osu.Framework.Input.Bindings; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu @@ -36,22 +35,20 @@ namespace osu.Game.Rulesets.Osu { } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => AllowUserPresses && base.OnKeyDown(state, args); - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => AllowUserPresses && base.OnKeyUp(state, args); - protected override bool OnJoystickPress(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickPress(state, args); - protected override bool OnJoystickRelease(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickRelease(state, args); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => AllowUserPresses && base.OnMouseDown(state, args); - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => AllowUserPresses && base.OnMouseUp(state, args); - protected override bool OnScroll(InputState state) => AllowUserPresses && base.OnScroll(state); + protected override bool Handle(UIEvent e) + { + if (!AllowUserPresses) return false; + return base.Handle(e); + } } } public enum OsuAction { - [Description("Left Button")] + [Description("Left button")] LeftButton, - [Description("Right Button")] + [Description("Right button")] RightButton } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index fa6e9a018a..6736d10dab 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -117,6 +117,11 @@ namespace osu.Game.Rulesets.Osu new OsuModRelax(), new OsuModAutopilot(), }; + case ModType.Fun: + return new Mod[] { + new OsuModTransform(), + new OsuModWiggle(), + }; default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 4a6b12d41a..4b5513ff9c 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.Input; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Timing; using OpenTK; using OpenTK.Graphics; @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; [BackgroundDependencyLoader] private void load(ShaderManager shaders, TextureStore textures) @@ -117,15 +117,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor timeOffset = Time.Current; } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { - Vector2 pos = state.Mouse.NativeState.Position; + Vector2 pos = e.ScreenSpaceMousePosition; if (lastPosition == null) { lastPosition = pos; resampler.AddPosition(lastPosition.Value); - return base.OnMouseMove(state); + return base.OnMouseMove(e); } foreach (Vector2 pos2 in resampler.AddPosition(pos)) @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } - return base.OnMouseMove(state); + return base.OnMouseMove(e); } private void addPosition(Vector2 pos) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 4d6722b61b..4a45d4fb31 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return false; } - public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. + public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. protected override void PopIn() { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 61937a535c..398680cb8d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -23,29 +23,35 @@ namespace osu.Game.Rulesets.Osu.UI 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[] + Size = new Vector2(0.75f); + + InternalChild = new PlayfieldAdjustmentContainer { - connectionLayer = new FollowPointRenderer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, - approachCircles = new Container - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, - }); + connectionLayer = new FollowPointRenderer + { + RelativeSizeAxes = Axes.Both, + Depth = 2, + }, + judgementLayer = new JudgementContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1, + }, + HitObjectContainer, + approachCircles = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = -1, + }, + } + }; } public override void Add(DrawableHitObject h) @@ -72,7 +78,8 @@ namespace osu.Game.Rulesets.Osu.UI DrawableOsuJudgement explosion = new DrawableOsuJudgement(result, judgedObject) { Origin = Anchor.Centre, - Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition, + Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale * 1.65f) }; judgementLayer.Add(explosion); diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 4bc6992445..ea5718bed2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -4,7 +4,6 @@ using System.Linq; 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; @@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) + public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { switch (h) { @@ -58,12 +57,6 @@ namespace osu.Game.Rulesets.Osu.UI } } - 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/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..00d5692fda --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs @@ -0,0 +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.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class PlayfieldAdjustmentContainer : Container + { + protected override Container Content => content; + private readonly Container content; + + public PlayfieldAdjustmentContainer() + { + InternalChild = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + FillAspectRatio = 4f / 3, + Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both } + }; + } + + /// + /// A which scales its content relative to a target width. + /// + private class ScalingContainer : Container + { + protected override void Update() + { + base.Update(); + + Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X); + Size = Vector2.Divide(Vector2.One, Scale); + } + } + } +} 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 08a0579561..3ba64398f3 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 @@ -2,9 +2,10 @@ - - - + + + + WinExe diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index c2cde332e8..c4a84f416e 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = curveData != null ? curveData.RepeatSamples : new List>(new[] { samples }); + List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index f59dc8c1ee..6f7264e23b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private bool validActionPressed; + private bool pressHandledThisFrame; + protected DrawableHit(Hit hit) : base(hit) { @@ -51,6 +53,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) { + if (pressHandledThisFrame) + return true; + if (Judged) return false; @@ -62,6 +67,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (IsHit) HitAction = action; + // Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded + // E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note + pressHandledThisFrame = true; + return result; } @@ -76,6 +85,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.Update(); + // The input manager processes all input prior to us updating, so this is the perfect time + // for us to remove the extra press blocking, before input is handled in the next frame + pressHandledThisFrame = false; + Size = BaseSize * Parent.RelativeChildSize; } diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index dc683ae2f5..3b430e7ad1 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -17,13 +17,13 @@ namespace osu.Game.Rulesets.Taiko public enum TaikoAction { - [Description("Left (Rim)")] + [Description("Left (rim)")] LeftRim, - [Description("Left (Centre)")] + [Description("Left (centre)")] LeftCentre, - [Description("Right (Centre)")] + [Description("Right (centre)")] RightCentre, - [Description("Right (Rim)")] + [Description("Right (rim)")] RightRim } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 4d660918b8..c7eba91564 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.UI switch (Result.Type) { case HitResult.Good: - Colour = colours.GreenLight; + JudgementBody.Colour = colours.GreenLight; break; case HitResult.Great: - Colour = colours.BlueLight; + JudgementBody.Colour = colours.BlueLight; break; } } diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..661a4e135c --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs @@ -0,0 +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.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class PlayfieldAdjustmentContainer : Container + { + private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; + private const float default_aspect = 16f / 9f; + + protected override void Update() + { + base.Update(); + + float aspectAdjust = MathHelper.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; + Size = new Vector2(1, default_relative_height * aspectAdjust); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 325beb38a5..40ed659bd6 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -1,23 +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.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.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.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Taiko.UI { @@ -40,13 +41,12 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; + protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Overlapping; + 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; @@ -61,140 +61,147 @@ namespace osu.Game.Rulesets.Taiko.UI { Direction.Value = ScrollingDirection.Left; - AddRangeInternal(new Drawable[] + InternalChild = new PlayfieldAdjustmentContainer { - backgroundContainer = new Container + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters + backgroundContainer = new Container { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, - }, - Children = new Drawable[] - { - background = new Box + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, }, - } - }, - new Container - { - Name = "Right area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] - { - new Container + Children = new Drawable[] { - Name = "Masked elements before hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Children = new Drawable[] + background = new Box { - 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 + 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 } + }, + new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Child = HitObjectContainer + }, + 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, } - }, - topLevelHitContainer = new Container - { - Name = "Top level hit objects", - RelativeSizeAxes = Axes.Both, } - }); + }; - VisibleTimeRange.Value = 6000; + VisibleTimeRange.Value = 7000; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 229ab69ceb..c94ced3390 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -2,7 +2,6 @@ // 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; @@ -13,7 +12,6 @@ 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; @@ -74,29 +72,13 @@ namespace osu.Game.Rulesets.Taiko.UI } } - 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); public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); - protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }; + protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); - protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + public override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) { switch (h) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index d3351f86f8..464cfbf5e9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -8,11 +8,14 @@ using OpenTK.Graphics; using osu.Game.Tests.Resources; using System.Linq; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Skinning; @@ -21,6 +24,25 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class LegacyBeatmapDecoderTest { + [Test] + public void TestDecodeBeatmapVersion() + { + using (var resStream = Resource.OpenResource("beatmap-version.osu")) + using (var stream = new StreamReader(resStream)) + { + var decoder = Decoder.GetDecoder(stream); + + stream.BaseStream.Position = 0; + stream.DiscardBufferedData(); + + var working = new TestWorkingBeatmap(decoder.Decode(stream)); + + Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapVersion); + } + } + [Test] public void TestDecodeBeatmapGeneral() { @@ -165,7 +187,7 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void TestDecodeBeatmapColors() + public void TestDecodeBeatmapColours() { var decoder = new LegacySkinDecoder(); using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) @@ -181,6 +203,7 @@ namespace osu.Game.Tests.Beatmaps.Formats new Color4(128, 255, 128, 255), new Color4(255, 187, 255, 255), new Color4(255, 177, 140, 255), + new Color4(100, 100, 100, 100), }; Assert.AreEqual(expectedColors.Length, comboColors.Count); for (int i = 0; i < expectedColors.Length; i++) @@ -311,5 +334,61 @@ namespace osu.Game.Tests.Beatmaps.Formats SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } + + [Test] + public void TestDecodeSliderSamples() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("slider-samples.osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + var slider1 = (ConvertSlider)hitObjects[0]; + + Assert.AreEqual(1, slider1.NodeSamples[0].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name); + Assert.AreEqual(1, slider1.NodeSamples[1].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name); + Assert.AreEqual(1, slider1.NodeSamples[2].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name); + + var slider2 = (ConvertSlider)hitObjects[1]; + + Assert.AreEqual(2, slider2.NodeSamples[0].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name); + Assert.AreEqual(2, slider2.NodeSamples[1].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name); + Assert.AreEqual(2, slider2.NodeSamples[2].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name); + + var slider3 = (ConvertSlider)hitObjects[2]; + + Assert.AreEqual(2, slider3.NodeSamples[0].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name); + Assert.AreEqual(SampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name); + Assert.AreEqual(1, slider3.NodeSamples[1].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name); + Assert.AreEqual(2, slider3.NodeSamples[2].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name); + } + } + + [Test] + public void TestDecodeHitObjectNullAdditionBank() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("hitobject-no-addition-bank.osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank); + } + } } } diff --git a/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu b/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu index 3e44dc0af8..67570ad21b 100644 --- a/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu +++ b/osu.Game.Tests/Resources/Soleily - Renatus (Gamu) [Insane].osu @@ -101,6 +101,7 @@ Combo3 : 128,255,255 Combo4 : 128,255,128 Combo5 : 255,187,255 Combo6 : 255,177,140 +Combo7 : 100,100,100,100 [HitObjects] 192,168,956,6,0,P|184:128|200:80,1,90,4|0,1:2|0:0,0:0:0:0: diff --git a/osu.Game.Tests/Resources/beatmap-version.osu b/osu.Game.Tests/Resources/beatmap-version.osu new file mode 100644 index 0000000000..5749054ac4 --- /dev/null +++ b/osu.Game.Tests/Resources/beatmap-version.osu @@ -0,0 +1 @@ +osu file format v6 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/hitobject-no-addition-bank.osu b/osu.Game.Tests/Resources/hitobject-no-addition-bank.osu new file mode 100644 index 0000000000..43d0b8cc16 --- /dev/null +++ b/osu.Game.Tests/Resources/hitobject-no-addition-bank.osu @@ -0,0 +1,4 @@ +osu file format v14 + +[HitObjects] +444,320,1000,5,2,3:0:1:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/slider-samples.osu b/osu.Game.Tests/Resources/slider-samples.osu new file mode 100644 index 0000000000..7759a2e35f --- /dev/null +++ b/osu.Game.Tests/Resources/slider-samples.osu @@ -0,0 +1,23 @@ +osu file format v14 + +[General] +SampleSet: Normal + +[Difficulty] + +SliderMultiplier:1.6 +SliderTickRate:2 + +[TimingPoints] +24735,389.61038961039,4,1,1,25,1,0 + +[HitObjects] +// Unified: Normal, Normal, Normal +168,256,30579,6,0,B|248:320|320:248,2,160 + +// Unified: Normal+Clap, Normal+Clap, Normal+Clap +168,256,32137,6,8,B|248:320|320:248,2,160 + +// Nodal: Normal+Whistle, Normal, Normal+Clap +// Nodal sounds should override the unified clap sound +168,256,33696,6,8,B|248:320|320:248,2,160,2|0|8 \ No newline at end of file diff --git a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs index 1effa14e76..6c607acd11 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs @@ -5,7 +5,8 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Screens.Edit.Screens.Compose; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; using OpenTK; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs index 361e255894..1f409f043e 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/TestCaseCursors.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual /// /// The cursor to check. private bool checkAtMouse(CursorContainer cursorContainer) - => Precision.AlmostEquals(InputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition)); + => Precision.AlmostEquals(InputManager.CurrentState.Mouse.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition)); private class CustomCursorBox : Container, IProvideCursor { @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual public CursorContainer Cursor { get; } public bool ProvidingUserCursor { get; } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor; private readonly Box background; @@ -224,16 +224,16 @@ namespace osu.Game.Tests.Visual }; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { background.FadeTo(0.4f, 250, Easing.OutQuint); return false; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { background.FadeTo(0.1f, 250); - base.OnHoverLost(state); + base.OnHoverLost(e); } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index e7bcfbf500..5c53fdfac4 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -6,7 +6,7 @@ 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.Screens.Edit.Compose; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual @@ -14,13 +14,13 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseEditorCompose : EditorClockTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(Compose) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(ComposeScreen) }; [BackgroundDependencyLoader] private void load() { Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - Child = new Compose(); + Child = new ComposeScreen(); } } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs index 09f390ab74..9df36b0bc1 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; +using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs index 9ad8bf7b92..d2c1127f4c 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using OpenTK.Graphics; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs index cb4438b2ba..eab799011d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs @@ -8,7 +8,7 @@ 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; +using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index 45b5ec2c11..417b0f94d7 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -8,14 +8,14 @@ using System.Linq; using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.EventArgs; using osu.Framework.Logging; using osu.Game.Screens.Play; +using OpenTK; namespace osu.Game.Tests.Visual { [Description("player pause/fail screens")] - public class TestCaseGameplayMenuOverlay : OsuTestCase + public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase { public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; @@ -73,12 +73,18 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => failOverlay.Show()); - AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnMouseMove(null)); + AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); } + private void press(Key key) + { + InputManager.PressKey(key); + InputManager.ReleaseKey(key); + } + /// /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. /// @@ -86,7 +92,7 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => pauseOverlay.Show()); - AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter })); + AddStep("Press enter", () => press(Key.Enter)); AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); AddStep("Hide overlay", () => pauseOverlay.Hide()); @@ -99,7 +105,7 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => pauseOverlay.Show()); - AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddStep("Up arrow", () => press(Key.Up)); AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); AddStep("Hide overlay", () => pauseOverlay.Hide()); @@ -112,7 +118,7 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => pauseOverlay.Show()); - AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddStep("Down arrow", () => press(Key.Down)); AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); AddStep("Hide overlay", () => pauseOverlay.Hide()); @@ -125,11 +131,11 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => failOverlay.Show()); - AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddStep("Up arrow", () => press(Key.Up)); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); - AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddStep("Up arrow", () => press(Key.Up)); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); - AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddStep("Up arrow", () => press(Key.Up)); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); AddStep("Hide overlay", () => failOverlay.Hide()); @@ -142,11 +148,11 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => failOverlay.Show()); - AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddStep("Down arrow", () => press(Key.Down)); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); - AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddStep("Down arrow", () => press(Key.Down)); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); - AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddStep("Down arrow", () => press(Key.Down)); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); AddStep("Hide overlay", () => failOverlay.Hide()); @@ -161,8 +167,8 @@ namespace osu.Game.Tests.Visual 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)); + AddStep("Down arrow", () => press(Key.Down)); + AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected); AddAssert("Second button selected", () => secondButton.Selected); @@ -174,12 +180,16 @@ namespace osu.Game.Tests.Visual /// private void testKeySelectionAfterMouseSelection() { - AddStep("Show overlay", () => pauseOverlay.Show()); + AddStep("Show overlay", () => + { + pauseOverlay.Show(); + InputManager.MoveMouseTo(Vector2.Zero); + }); 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 })); + AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); + AddStep("Up arrow", () => press(Key.Up)); AddAssert("Second button not selected", () => !secondButton.Selected); AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); @@ -195,9 +205,9 @@ namespace osu.Game.Tests.Visual 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 })); + AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); + AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddStep("Down arrow", () => press(Key.Down)); AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition AddStep("Hide overlay", () => pauseOverlay.Hide()); @@ -218,7 +228,7 @@ namespace osu.Game.Tests.Visual var lastAction = pauseOverlay.OnRetry; pauseOverlay.OnRetry = () => triggered = true; - retryButton.TriggerOnClick(); + retryButton.Click(); pauseOverlay.OnRetry = lastAction; }); @@ -235,23 +245,28 @@ namespace osu.Game.Tests.Visual AddStep("Select second button", () => { - pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); - pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); + press(Key.Down); + press(Key.Down); }); - var retryButton = pauseOverlay.Buttons.Skip(1).First(); - bool triggered = false; + Action lastAction = null; AddStep("Press enter", () => { - var lastAction = pauseOverlay.OnRetry; + lastAction = pauseOverlay.OnRetry; pauseOverlay.OnRetry = () => triggered = true; - - retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }); - pauseOverlay.OnRetry = lastAction; + press(Key.Enter); }); - AddAssert("Action was triggered", () => triggered); + AddAssert("Action was triggered", () => + { + if (lastAction != null) + { + pauseOverlay.OnRetry = lastAction; + lastAction = null; + } + return triggered; + }); AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); } } diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index 5df371dd09..2629b29c6c 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -13,25 +13,34 @@ 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.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Screens.Compose.Layers; +using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseHitObjectComposer : OsuTestCase + [Cached(Type = typeof(IPlacementHandler))] + public class TestCaseHitObjectComposer : OsuTestCase, IPlacementHandler { public override IReadOnlyList RequiredTypes => new[] { - typeof(MaskSelection), - typeof(DragLayer), + typeof(SelectionBox), + typeof(DragBox), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), - typeof(HitObjectMaskLayer), - typeof(NotNullAttribute) + typeof(BlueprintContainer), + typeof(NotNullAttribute), + typeof(HitCirclePiece), + typeof(HitCircleSelectionBlueprint), + typeof(HitCirclePlacementBlueprint), }; + private HitObjectComposer composer; + [BackgroundDependencyLoader] private void load() { @@ -44,14 +53,12 @@ namespace osu.Game.Tests.Visual new Slider { Position = new Vector2(128, 256), - ControlPoints = new List + ControlPoints = new[] { Vector2.Zero, new Vector2(216, 0), }, - Distance = 400, - Velocity = 1, - TickDistance = 100, + Distance = 216, Scale = 0.5f, } }, @@ -61,7 +68,15 @@ namespace osu.Game.Tests.Visual Dependencies.CacheAs(clock); Dependencies.CacheAs(clock); - Child = new OsuHitObjectComposer(new OsuRuleset()); + Child = composer = new OsuHitObjectComposer(new OsuRuleset()); } + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); + + public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index b98875cd6a..178b47ed33 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -1,32 +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 System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.MathUtils; +using osu.Framework.Timing; using osu.Game.Screens.Play; using OpenTK.Input; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseKeyCounter : OsuTestCase + public class TestCaseKeyCounter : ManualInputManagerTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(KeyCounterKeyboard), + typeof(KeyCounterMouse), + typeof(KeyCounterCollection) + }; + public TestCaseKeyCounter() { + KeyCounterKeyboard rewindTestKeyCounterKeyboard; KeyCounterCollection kc = new KeyCounterCollection { Origin = Anchor.Centre, Anchor = Anchor.Centre, Children = new KeyCounter[] { - new KeyCounterKeyboard(Key.Z), + rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X), new KeyCounterKeyboard(Key.X), new KeyCounterMouse(MouseButton.Left), new KeyCounterMouse(MouseButton.Right), }, }; + AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); @@ -34,7 +47,57 @@ namespace osu.Game.Tests.Visual }); AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v); + Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key; + double time1 = 0; + + AddStep($"Press {testKey} key", () => + { + InputManager.PressKey(testKey); + InputManager.ReleaseKey(testKey); + }); + + AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1); + + AddStep($"Press {testKey} key", () => + { + InputManager.PressKey(testKey); + InputManager.ReleaseKey(testKey); + time1 = Clock.CurrentTime; + }); + + AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2); + + IFrameBasedClock oldClock = null; + + AddStep($"Rewind {testKey} counter once", () => + { + oldClock = rewindTestKeyCounterKeyboard.Clock; + rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10)); + }); + + AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1); + + AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0))); + + AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0); + + AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock); + Add(kc); } + + private class FixedClock : IClock + { + private readonly double time; + + public FixedClock(double time) + { + this.time = time; + } + + public double CurrentTime => time; + public double Rate => 1; + public bool IsRunning => false; + } } } diff --git a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs b/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs index d41739bfb5..e1470a860e 100644 --- a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs @@ -5,9 +5,9 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Edit.Screens.Setup.Components.LabelledComponents; using System; using System.Collections.Generic; +using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs index bbc9d2b860..b254325472 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -121,14 +121,21 @@ namespace osu.Game.Tests.Visual Direction = direction; Padding = new MarginPadding(2); - Content.Masking = true; - AddInternal(new Box + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0.5f, - Depth = float.MaxValue - }); + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = HitObjectContainer + } + }; } } diff --git a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs index 8bd1b79a84..3bf809ebde 100644 --- a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using OpenTK; using OpenTK.Graphics; diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index d638af0c38..c0f0695ff8 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -2,9 +2,11 @@ - - - + + + + + WinExe diff --git a/osu.Game.props b/osu.Game.props index ec859e64a5..4bcac479a0 100644 --- a/osu.Game.props +++ b/osu.Game.props @@ -1,7 +1,7 @@ - 7 + 7.2 ..\app.manifest diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 9aabb434a3..4ef7b28d49 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Beatmaps [JsonConverter(typeof(TypedListConverter))] public List HitObjects = new List(); - IEnumerable IBeatmap.HitObjects => HitObjects; + IReadOnlyList IBeatmap.HitObjects => HitObjects; public virtual IEnumerable GetStatistics() => Enumerable.Empty(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index a1bb70135a..3cb7833a12 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -55,39 +55,40 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo = original.BeatmapInfo; beatmap.ControlPointInfo = original.ControlPointInfo; - beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList(); + beatmap.HitObjects = convertHitObjects(original.HitObjects, original); 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, IBeatmap beatmap) + private List convertHitObjects(IReadOnlyList hitObjects, IBeatmap beatmap) { - // Check if the hitobject is already the converted type - T tObject = original as T; - if (tObject != null) - { - yield return tObject; - yield break; - } + var result = new List(hitObjects.Count); - var converted = ConvertHitObject(original, beatmap).ToList(); - ObjectConverted?.Invoke(original, converted); - - // Convert the hit object - foreach (var obj in converted) + foreach (var obj in hitObjects) { - if (obj == null) + if (obj is T tObj) + { + result.Add(tObj); continue; + } - yield return obj; + var converted = ConvertHitObject(obj, beatmap); + + if (ObjectConverted != null) + { + converted = converted.ToList(); + ObjectConverted.Invoke(obj, converted); + } + + foreach (var c in converted) + { + if (c != null) + result.Add(c); + } } + + return result; } /// diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 303a19aab3..3e1f3bdf54 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -19,7 +19,6 @@ namespace osu.Game.Beatmaps [JsonIgnore] public int ID { get; set; } - //TODO: should be in database public int BeatmapVersion; private int? onlineBeatmapID; @@ -27,13 +26,15 @@ namespace osu.Game.Beatmaps [JsonProperty("id")] public int? OnlineBeatmapID { - get { return onlineBeatmapID; } - set { onlineBeatmapID = value > 0 ? value : null; } + get => onlineBeatmapID; + set => onlineBeatmapID = value > 0 ? value : null; } [JsonIgnore] public int BeatmapSetInfoID { get; set; } + public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None; + [Required] public BeatmapSetInfo BeatmapSet { get; set; } @@ -82,7 +83,7 @@ namespace osu.Game.Beatmaps [JsonIgnore] public string StoredBookmarks { - get { return string.Join(",", Bookmarks); } + get => string.Join(",", Bookmarks); set { if (string.IsNullOrEmpty(value)) @@ -93,8 +94,7 @@ namespace osu.Game.Beatmaps Bookmarks = value.Split(',').Select(v => { - int val; - bool result = int.TryParse(v, out val); + bool result = int.TryParse(v, out int val); return new { result, val }; }).Where(p => p.result).Select(p => p.val).ToArray(); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 21df9a6c68..24c68d392b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; @@ -104,7 +104,7 @@ namespace osu.Game.Beatmaps validateOnlineIds(beatmapSet.Beatmaps); foreach (BeatmapInfo b in beatmapSet.Beatmaps) - fetchAndPopulateOnlineIDs(b, beatmapSet.Beatmaps); + fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps); // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) @@ -148,11 +148,12 @@ namespace osu.Game.Beatmaps /// /// The to be downloaded. /// Whether the beatmap should be downloaded without video. Defaults to false. - public void Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) + /// Downloading can happen + public bool Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) { var existing = GetExistingDownload(beatmapSetInfo); - if (existing != null || api == null) return; + if (existing != null || api == null) return false; if (!api.LocalUser.Value.IsSupporter) { @@ -161,7 +162,7 @@ namespace osu.Game.Beatmaps Icon = FontAwesome.fa_superpowers, Text = "You gotta be an osu!supporter to download for now 'yo" }); - return; + return false; } var downloadNotification = new DownloadNotification @@ -227,6 +228,7 @@ namespace osu.Game.Beatmaps // 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); + return true; } protected override void PresentCompletedImport(IEnumerable imported) @@ -348,7 +350,7 @@ namespace osu.Game.Beatmaps OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, Beatmaps = new List(), Hash = computeBeatmapSetHash(reader), - Metadata = beatmap.Metadata + Metadata = beatmap.Metadata, }; } @@ -388,21 +390,22 @@ namespace osu.Game.Beatmaps } /// - /// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties. + /// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status. /// /// The beatmap to populate. /// The other beatmaps contained within this set. /// Whether to re-query if the provided beatmap already has populated values. /// True if population was successful. - private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false) + private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false) { if (api?.State != APIState.Online) return false; - if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null) + if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null + && beatmap.Status != BeatmapSetOnlineStatus.None && beatmap.BeatmapSet.Status != BeatmapSetOnlineStatus.None) return true; - Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database); + Logger.Log("Attempting online lookup for the missing values...", LoggingTarget.Database); try { @@ -414,6 +417,9 @@ namespace osu.Game.Beatmaps Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database); + beatmap.Status = res.Status; + beatmap.BeatmapSet.Status = res.BeatmapSet.Status; + if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID)) { Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database); @@ -422,6 +428,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + return true; } catch (Exception e) diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index 9d7cd673dc..9db2c5f08e 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -2,6 +2,7 @@ // 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 @@ -44,6 +45,25 @@ namespace osu.Game.Beatmaps public virtual void PostProcess() { + void updateNestedCombo(HitObject obj, int comboIndex, int indexInCurrentCombo) + { + if (obj is IHasComboInformation objectComboInfo) + { + objectComboInfo.ComboIndex = comboIndex; + objectComboInfo.IndexInCurrentCombo = indexInCurrentCombo; + foreach (var nestedObject in obj.NestedHitObjects) + updateNestedCombo(nestedObject, comboIndex, indexInCurrentCombo); + } + } + + foreach (var hitObject in Beatmap.HitObjects) + { + if (hitObject is IHasComboInformation objectComboInfo) + { + foreach (var nested in hitObject.NestedHitObjects) + updateNestedCombo(nested, objectComboInfo.ComboIndex, objectComboInfo.IndexInCurrentCombo); + } + } } } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index ebebe42097..7a7d010a31 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -17,10 +17,12 @@ namespace osu.Game.Beatmaps public int? OnlineBeatmapSetID { - get { return onlineBeatmapSetID; } - set { onlineBeatmapSetID = value > 0 ? value : null; } + get => onlineBeatmapSetID; + set => onlineBeatmapSetID = value > 0 ? value : null; } + public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None; + public BeatmapMetadata Metadata { get; set; } public List Beatmaps { get; set; } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index dad69321a5..f064d53358 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -55,14 +55,14 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the sound control point at. /// The sound control point. - public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault()); + public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null); /// /// 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()); + public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); /// /// Finds the maximum BPM represented by any timing control point. @@ -104,17 +104,26 @@ namespace osu.Game.Beatmaps.ControlPoints if (time < list[0].Time) return prePoint ?? new T(); - int index = list.BinarySearch(new T { Time = time }); + if (time >= list[list.Count - 1].Time) + return list[list.Count - 1]; - // Check if we've found an exact match (t == time) - if (index >= 0) - return list[index]; + int l = 0; + int r = list.Count - 2; - index = ~index; + while (l <= r) + { + int pivot = l + ((r - l) >> 1); - // 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]; + if (list[pivot].Time < time) + l = pivot + 1; + else if (list[pivot].Time > time) + r = pivot - 1; + else + return list[pivot]; + } + + // l will be the first control point with Time > time, but we want the one before it + return list[l - 1]; } } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index eb60133fed..81eddaa43a 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -16,7 +16,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The beat length at this control point. /// - public double BeatLength + public virtual double BeatLength { get => beatLength; set => beatLength = MathHelper.Clamp(value, 6, 60000); diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs index 6f4d4c0d6f..5b5dbec9c8 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs @@ -71,9 +71,11 @@ namespace osu.Game.Beatmaps.Drawables if (DownloadState.Value > DownloadStatus.NotDownloaded) return; - beatmaps.Download(set, noVideo); - - DownloadState.Value = DownloadStatus.Downloading; + if (beatmaps.Download(set, noVideo)) + { + // Only change state if download can happen + DownloadState.Value = DownloadStatus.Downloading; + } } private void setAdded(BeatmapSetInfo s) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs index c7e97cef55..da281b4db3 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs @@ -1,7 +1,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.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,20 +13,35 @@ namespace osu.Game.Beatmaps.Drawables { private readonly OsuSpriteText statusText; - private BeatmapSetOnlineStatus status = BeatmapSetOnlineStatus.None; + private BeatmapSetOnlineStatus status; + public BeatmapSetOnlineStatus Status { - get { return status; } + get => status; set { - if (value == status) return; + if (status == value) + return; status = value; - statusText.Text = Enum.GetName(typeof(BeatmapSetOnlineStatus), Status)?.ToUpperInvariant(); + Alpha = value == BeatmapSetOnlineStatus.None ? 0 : 1; + statusText.Text = value.ToString().ToUpperInvariant(); } } - public BeatmapSetOnlineStatusPill(float textSize, MarginPadding textPadding) + public float TextSize + { + get => statusText.TextSize; + set => statusText.TextSize = value; + } + + public MarginPadding TextPadding + { + get => statusText.Padding; + set => statusText.Padding = value; + } + + public BeatmapSetOnlineStatusPill() { AutoSizeAxes = Axes.Both; Masking = true; @@ -45,10 +59,10 @@ namespace osu.Game.Beatmaps.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = @"Exo2.0-Bold", - TextSize = textSize, - Padding = textPadding, }, }; + + Status = BeatmapSetOnlineStatus.None; } } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 25a76b52a7..5c129f76ec 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -26,15 +26,7 @@ namespace osu.Game.Beatmaps Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty - { - DrainRate = 0, - CircleSize = 0, - OverallDifficulty = 0, - ApproachRate = 0, - SliderMultiplier = 0, - SliderTickRate = 0, - }, + BaseDifficulty = new BeatmapDifficulty(), Ruleset = new DummyRulesetInfo() }) { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 181d17932d..71b7a65ccb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; using System.IO; using System.Linq; +using osu.Framework.IO.File; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -58,7 +59,7 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); } - protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"); + protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal); protected override void ParseLine(Beatmap beatmap, Section section, string line) { @@ -100,7 +101,7 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"AudioFilename": - metadata.AudioFile = pair.Value; + metadata.AudioFile = FileSafety.PathStandardise(pair.Value); break; case @"AudioLeadIn": beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); @@ -256,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats { case EventType.Background: string filename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); break; case EventType.Break: var breakEvent = new BreakPeriod @@ -318,12 +319,12 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { - handleTimingControlPoint(new TimingControlPoint - { - Time = time, - BeatLength = beatLength, - TimeSignature = timeSignature - }); + var controlPoint = CreateTimingControlPoint(); + controlPoint.Time = time; + controlPoint.BeatLength = beatLength; + controlPoint.TimeSignature = timeSignature; + + handleTimingControlPoint(controlPoint); } handleDifficultyControlPoint(new DifficultyControlPoint @@ -418,6 +419,8 @@ namespace osu.Game.Beatmaps.Formats private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0); + protected virtual TimingControlPoint CreateTimingControlPoint() => new TimingControlPoint(); + [Flags] internal enum EffectFlags { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index e9f37e583b..2ef7c5de30 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Formats protected string StripComments(string line) { - var index = line.IndexOf("//", StringComparison.Ordinal); + var index = line.AsSpan().IndexOf("//".AsSpan()); if (index > 0) return line.Substring(0, index); return line; @@ -85,13 +85,19 @@ namespace osu.Game.Beatmaps.Formats 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 (split.Length != 3 && split.Length != 4) + throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}"); - if (!byte.TryParse(split[0], out var r) || !byte.TryParse(split[1], out var g) || !byte.TryParse(split[2], out var b)) + Color4 colour; + + try + { + colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); + } + catch (Exception e) + { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); - - Color4 colour = new Color4(r, g, b, 255); + } if (isCombo) { diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs new file mode 100644 index 0000000000..13a71aac3d --- /dev/null +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -0,0 +1,37 @@ +// 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.ControlPoints; + +namespace osu.Game.Beatmaps.Formats +{ + /// + /// A built for difficulty calculation of legacy s + /// + /// To use this, the decoder must be registered by the application through . + /// Doing so will override any existing decoders. + /// + /// + public class LegacyDifficultyCalculatorBeatmapDecoder : LegacyBeatmapDecoder + { + public LegacyDifficultyCalculatorBeatmapDecoder(int version = LATEST_VERSION) + : base(version) + { + ApplyOffsets = false; + } + + public new static void Register() + { + AddDecoder(@"osu file format v", m => new LegacyDifficultyCalculatorBeatmapDecoder(int.Parse(m.Split('v').Last()))); + } + + protected override TimingControlPoint CreateTimingControlPoint() + => new LegacyDifficultyCalculatorControlPoint(); + + private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint + { + public override double BeatLength { get; set; } = 1000; + } + } +} diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index a73a32325a..375c0b29c0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -298,6 +298,6 @@ namespace osu.Game.Beatmaps.Formats } } - private string cleanFilename(string path) => FileSafety.PathStandardise(FileSafety.PathSanitise(path.Trim('\"'))); + private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('"')); } } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index fe20bce98a..3d8b94dc46 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps /// /// The hitobjects contained by this beatmap. /// - IEnumerable HitObjects { get; } + IReadOnlyList HitObjects { get; } /// /// Returns statistics for the contained in this beatmap. diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e0a22460ef..5b76122616 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -41,8 +41,13 @@ namespace osu.Game.Beatmaps beatmap = new RecyclableLazy(() => { var b = GetBeatmap() ?? new Beatmap(); - // use the database-backed info. + + // The original beatmap version needs to be preserved as the database doesn't contain it + BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion; + + // Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc) b.BeatmapInfo = BeatmapInfo; + return b; }); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f7fe424aa9..9ac2cabe9f 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -83,8 +83,6 @@ namespace osu.Game.Configuration Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); - Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential); - Set(OsuSetting.IncreaseFirstObjectVisibility, true); // Update @@ -143,7 +141,6 @@ namespace osu.Game.Configuration ChatDisplayHeight, Version, ShowConvertedBeatmaps, - SpeedChangeVisualisation, Skin, ScreenshotFormat, ScreenshotCaptureMenuCursor, diff --git a/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs b/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs index d38b1a89c5..39c6e5649c 100644 --- a/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs +++ b/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs @@ -10,6 +10,8 @@ namespace osu.Game.Configuration [Description("Sequential")] Sequential, [Description("Overlapping")] - Overlapping + Overlapping, + [Description("Constant")] + Constant } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index f4f169f27c..3686b702c4 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -96,7 +96,8 @@ namespace osu.Game.Database private void handleEvent(Action a) { if (delayingEvents) - lock (queuedEvents) queuedEvents.Add(a); + lock (queuedEvents) + queuedEvents.Add(a); else a.Invoke(); } @@ -281,17 +282,19 @@ namespace osu.Game.Database /// Is a no-op for already deleted items. /// /// The item to delete. - public void Delete(TModel item) + /// false if no operation was performed + public bool Delete(TModel item) { using (ContextFactory.GetForWrite()) { // 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); + var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).FirstOrDefault(s => s.ID == item.ID); - if (foundModel.DeletePending) return; + if (foundModel == null || foundModel.DeletePending) return false; if (ModelStore.Delete(foundModel)) Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); + return true; } } @@ -401,7 +404,7 @@ namespace osu.Game.Database using (Stream s = reader.GetStream(file)) fileInfos.Add(new TFileModel { - Filename = FileSafety.PathSanitise(file), + Filename = FileSafety.PathStandardise(file), FileInfo = files.Add(s) }); @@ -438,6 +441,13 @@ namespace osu.Game.Database return Task.CompletedTask; } + if (!stable.ExistsDirectory(ImportFromStablePath)) + { + // This handles situations like when the user does not have a Skins folder + Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + return Task.Factory.StartNew(() => Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning); } diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 6fc8b0e070..d3d530a540 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using OpenTK.Graphics; namespace osu.Game.Graphics.Backgrounds { @@ -28,7 +27,6 @@ namespace osu.Game.Graphics.Backgrounds RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = Color4.DarkGray, FillMode = FillMode.Fill, }); } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 0f382900ce..4a86d0e4f6 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -30,10 +30,6 @@ namespace osu.Game.Graphics.Backgrounds /// 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; diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 38d82e26ec..5c8fddc342 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -21,8 +21,6 @@ namespace osu.Game.Graphics.Containers { } - public override bool HandleMouseInput => true; - private OsuGame game; private ChannelManager channelManager; private Action showNotImplementedError; diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index d2ab8441eb..2aa3fede2c 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using OpenTK; using osu.Framework.Configuration; using osu.Framework.Input.Bindings; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.Containers protected virtual bool PlaySamplesOnStateChange => true; - protected override bool BlockPassThroughKeyboard => true; + protected override bool BlockNonPositionalInput => true; private PreviewTrackManager previewTrackManager; @@ -54,20 +54,20 @@ namespace osu.Game.Graphics.Containers /// 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. /// - public virtual bool BlockScreenWideMouse => BlockPassThroughMouse; + public virtual bool BlockScreenWideMouse => BlockPositionalInput; // receive input outside our bounds so we can trigger a close event on ourselves. - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceivePositionalInputAt(screenSpacePos); - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { - if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) + if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) { State = Visibility.Hidden; return true; } - return base.OnClick(state); + return base.OnClick(e); } public virtual bool OnPressed(GlobalAction action) diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 12df19e7c0..577d889be3 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -6,7 +6,7 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.Containers { @@ -18,16 +18,16 @@ namespace osu.Game.Graphics.Containers protected virtual IEnumerable EffectTargets => new[] { Content }; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { EffectTargets.ForEach(d => d.FadeColour(HoverColour, 500, Easing.OutQuint)); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { EffectTargets.ForEach(d => d.FadeColour(IdleColour, 500, Easing.OutQuint)); - base.OnHoverLost(state); + base.OnHoverLost(e); } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 6d42be6fca..4f18eb9e7b 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -2,8 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics.Containers; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using OpenTK.Input; namespace osu.Game.Graphics.Containers @@ -21,7 +20,7 @@ namespace osu.Game.Graphics.Containers /// public double DistanceDecayOnRightMouseScrollbar = 0.02; - private bool shouldPerformRightMouseScroll(InputState state) => RightMouseScrollbar && state.Mouse.IsPressed(MouseButton.Right); + private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right; private void scrollToRelative(float value) => ScrollTo(Clamp((value - Scrollbar.DrawSize[ScrollDim] / 2) / Scrollbar.Size[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar); @@ -29,40 +28,40 @@ namespace osu.Game.Graphics.Containers protected override bool IsDragging => base.IsDragging || mouseScrollBarDragging; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { - if (shouldPerformRightMouseScroll(state)) + if (shouldPerformRightMouseScroll(e)) { - scrollToRelative(state.Mouse.Position[ScrollDim]); + scrollToRelative(e.MousePosition[ScrollDim]); return true; } - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnDrag(InputState state) + protected override bool OnDrag(DragEvent e) { if (mouseScrollBarDragging) { - scrollToRelative(state.Mouse.Position[ScrollDim]); + scrollToRelative(e.MousePosition[ScrollDim]); return true; } - return base.OnDrag(state); + return base.OnDrag(e); } - protected override bool OnDragStart(InputState state) + protected override bool OnDragStart(DragStartEvent e) { - if (shouldPerformRightMouseScroll(state)) + if (shouldPerformRightMouseScroll(e)) { mouseScrollBarDragging = true; return true; } - return base.OnDragStart(state); + return base.OnDragStart(e); } - protected override bool OnDragEnd(InputState state) + protected override bool OnDragEnd(DragEndEvent e) { if (mouseScrollBarDragging) { @@ -70,7 +69,7 @@ namespace osu.Game.Graphics.Containers return true; } - return base.OnDragEnd(state); + return base.OnDragEnd(e); } } } diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 8e1e5d54fa..a6b79a20dc 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers if (parallaxEnabled) { - Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount; + Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.Position) - DrawSize / 2) * ParallaxAmount; double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index b55e1aa5dd..ba858bf52d 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -12,8 +12,7 @@ using osu.Game.Configuration; using System; using JetBrains.Annotations; using osu.Framework.Graphics.Textures; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using OpenTK.Input; namespace osu.Game.Graphics.Cursor @@ -40,11 +39,11 @@ namespace osu.Game.Graphics.Cursor screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility); } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { if (dragRotationState != DragRotationState.NotDragging) { - var position = state.Mouse.Position; + var position = e.MousePosition; var distance = Vector2Extensions.Distance(position, positionMouseDown); // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. @@ -53,7 +52,7 @@ namespace osu.Game.Graphics.Cursor // don't rotate when distance is zero to avoid NaN if (dragRotationState == DragRotationState.Rotating && distance > 0) { - Vector2 offset = state.Mouse.Position - positionMouseDown; + Vector2 offset = e.MousePosition - positionMouseDown; float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f; // Always rotate in the direction of least distance @@ -66,13 +65,13 @@ namespace osu.Game.Graphics.Cursor } } - return base.OnMouseMove(state); + return base.OnMouseMove(e); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { // only trigger animation for main mouse buttons - if (args.Button <= MouseButton.Right) + if (e.Button <= MouseButton.Right) { activeCursor.Scale = new Vector2(1); activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint); @@ -81,29 +80,29 @@ namespace osu.Game.Graphics.Cursor activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); } - if (args.Button == MouseButton.Left && cursorRotate) + if (e.Button == MouseButton.Left && cursorRotate) { dragRotationState = DragRotationState.DragStarted; - positionMouseDown = state.Mouse.Position; + positionMouseDown = e.MousePosition; } - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { - if (!state.Mouse.HasMainButtonPressed) + if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Right)) { activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); activeCursor.ScaleTo(1, 500, Easing.OutElastic); } - if (args.Button == MouseButton.Left) + if (e.Button == MouseButton.Left) { if (dragRotationState == DragRotationState.Rotating) activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); dragRotationState = DragRotationState.NotDragging; } - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } protected override void PopIn() diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index be794d93a6..28f8bdf82f 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -54,8 +54,6 @@ namespace osu.Game.Graphics Scheduler.AddDelayed(updateTimeWithReschedule, timeUntilNextUpdate); } - public override bool HandleMouseInput => true; - protected virtual string Format() => Date.Humanize(); private void updateTime() => Text = Format(); diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index f5017de639..ebb7b686e4 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -47,10 +47,10 @@ namespace osu.Game.Graphics.UserInterface 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 ReceivePositionalInputAt(Vector2 screenSpacePos) => Alpha == 1f && Text.ReceivePositionalInputAt(screenSpacePos); - public override bool HandleKeyboardInput => State == Visibility.Visible; - public override bool HandleMouseInput => State == Visibility.Visible; + public override bool HandleNonPositionalInput => State == Visibility.Visible; + public override bool HandlePositionalInput => State == Visibility.Visible; public override bool IsRemovable => true; private Visibility state; diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index ee2448ff02..b2220267ff 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Configuration; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { @@ -211,9 +211,9 @@ namespace osu.Game.Graphics.UserInterface } } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos); - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); flash(); @@ -225,20 +225,20 @@ namespace osu.Game.Graphics.UserInterface glowContainer.FadeOut(); }); - return base.OnClick(state); + return base.OnClick(e); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { - base.OnHover(state); + base.OnHover(e); Selected.Value = true; return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { - base.OnHoverLost(state); + base.OnHoverLost(e); Selected.Value = false; } diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index fe8995f310..d82448e8a2 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Platform; using OpenTK; using OpenTK.Graphics; @@ -37,19 +37,19 @@ namespace osu.Game.Graphics.UserInterface this.host = host; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { InternalChild.FadeColour(hoverColour, 500, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { InternalChild.FadeColour(Color4.White, 500, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if(Link != null) host.OpenUrlExternally(Link); diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index e4fd71e17e..122ac7b627 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -3,8 +3,7 @@ using OpenTK.Graphics; using System; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using OpenTK.Input; @@ -34,22 +33,22 @@ namespace osu.Game.Graphics.UserInterface } // 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; + public override bool HandleNonPositionalInput => HoldFocus || base.HandleNonPositionalInput; - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { - base.OnFocus(state); + base.OnFocus(e); BorderThickness = 0; } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { if (!HasFocus) return false; - if (args.Key == Key.Escape) + if (e.Key == Key.Escape) return false; // disable the framework-level handling of escape key for confority (we use GlobalAction.Back). - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } public override bool OnPressed(GlobalAction action) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 27a06ba0b7..3641e251bc 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { @@ -21,10 +21,10 @@ namespace osu.Game.Graphics.UserInterface { } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { sampleClick?.Play(); - return base.OnClick(state); + return base.OnClick(e); } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 821305bc92..710948121e 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -8,7 +8,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { @@ -28,10 +28,10 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.Both; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { sampleHover?.Play(); - return base.OnHover(state); + return base.OnHover(e); } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index be60812ba6..f10f03873d 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -4,7 +4,7 @@ using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { @@ -84,16 +84,16 @@ namespace osu.Game.Graphics.UserInterface }); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { icon.FadeColour(IconHoverColour, 500, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { icon.FadeColour(IconColour, 500, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } } } diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 3cb2446acc..ff2c4cf7cd 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface { Masking = true, RelativeSizeAxes = Axes.Both, - Child = path = new Path { RelativeSizeAxes = Axes.Both, PathWidth = 1 } + Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathWidth = 1 } }); } diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 4428a058db..4516d7ce76 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -6,8 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using OpenTK.Graphics; @@ -77,34 +76,34 @@ namespace osu.Game.Graphics.UserInterface Enabled.BindValueChanged(enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint), true); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { hover.FadeIn(500, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { hover.FadeOut(500, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { hover.FlashColour(FlashColour, 800, Easing.OutQuint); - return base.OnClick(state); + return base.OnClick(e); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { Content.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { Content.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } } } diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index bb6a032a12..ab880cd473 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -7,8 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; using OpenTK.Graphics; @@ -56,28 +55,28 @@ namespace osu.Game.Graphics.UserInterface this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { hover.FadeIn(200); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { hover.FadeOut(200); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { Content.ScaleTo(0.9f, 4000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { Content.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } protected override SpriteText CreateText() => new OsuSpriteText diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 68f59bd8cd..e267a7f848 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -8,7 +8,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; using OpenTK.Graphics; @@ -95,18 +95,18 @@ namespace osu.Game.Graphics.UserInterface }; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { Nub.Glowing = true; Nub.Expanded = true; - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { Nub.Glowing = false; Nub.Expanded = false; - base.OnHoverLost(state); + base.OnHoverLost(e); } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 830bde9dac..30803d1545 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -30,7 +30,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (accentColour == default(Color4)) + if (accentColour == default) accentColour = colours.PinkDarker; updateAccentColour(); } @@ -51,6 +51,8 @@ namespace osu.Game.Graphics.UserInterface #region OsuDropdownMenu protected class OsuDropdownMenu : DropdownMenu, IHasAccentColour { + public override bool HandleNonPositionalInput => State == MenuState.Open; + // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring public OsuDropdownMenu() { @@ -97,6 +99,9 @@ namespace osu.Game.Graphics.UserInterface #region DrawableOsuDropdownMenuItem public class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour { + // IsHovered is used + public override bool HandlePositionalInput => true; + private Color4? accentColour; public Color4 AccentColour { diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index abb077e94f..fe3e866a70 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; using OpenTK; @@ -97,25 +97,25 @@ namespace osu.Game.Graphics.UserInterface } } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { sampleHover.Play(); text.BoldText.FadeIn(transition_length, Easing.OutQuint); text.NormalText.FadeOut(transition_length, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { text.BoldText.FadeOut(transition_length, Easing.OutQuint); text.NormalText.FadeIn(transition_length, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { sampleClick.Play(); - return base.OnClick(state); + return base.OnClick(e); } protected sealed override Drawable CreateContent() => text = CreateTextContainer(); diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index 75655ddb36..f4ec67db23 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -9,8 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Platform; namespace osu.Game.Graphics.UserInterface @@ -43,23 +42,23 @@ namespace osu.Game.Graphics.UserInterface this.host = host; } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Key == Key.CapsLock) + if (e.Key == Key.CapsLock) updateCapsWarning(host.CapsLockEnabled); - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { updateCapsWarning(host.CapsLockEnabled); - base.OnFocus(state); + base.OnFocus(e); } - protected override void OnFocusLost(InputState state) + protected override void OnFocusLost(FocusLostEvent e) { updateCapsWarning(false); - base.OnFocusLost(state); + base.OnFocusLost(e); } private void updateCapsWarning(bool visible) => warning.FadeTo(visible ? 1 : 0, 250, Easing.OutQuint); diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index b7b5319e06..eecc10469e 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -13,8 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { @@ -125,16 +124,16 @@ namespace osu.Game.Graphics.UserInterface AccentColour = colours.Pink; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { Nub.Glowing = true; - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { Nub.Glowing = false; - base.OnHoverLost(state); + base.OnHoverLost(e); } protected override void OnUserChange() @@ -164,16 +163,16 @@ namespace osu.Game.Graphics.UserInterface sample.Play(); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { Nub.Current.Value = true; - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { Nub.Current.Value = false; - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } protected override void UpdateAfterChildren() diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index e2a0b88b2a..e7d6a87349 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -14,7 +14,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics.Sprites; @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (accentColour == default(Color4)) + if (accentColour == default) AccentColour = colours.Blue; } @@ -126,14 +126,14 @@ namespace osu.Game.Graphics.UserInterface Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { if (!Active) fadeActive(); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (!Active) fadeInactive(); @@ -142,7 +142,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (accentColour == default(Color4)) + if (accentColour == default) AccentColour = colours.Blue; } @@ -265,16 +265,16 @@ namespace osu.Game.Graphics.UserInterface Padding = new MarginPadding { Left = 5, Right = 5 }; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { Foreground.Colour = BackgroundColour; - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { Foreground.Colour = BackgroundColourHover; - base.OnHoverLost(state); + base.OnHoverLost(e); } } } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 04ba111153..1355ffdb8e 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { @@ -59,18 +59,18 @@ namespace osu.Game.Graphics.UserInterface text.FadeColour(AccentColour, transition_length, Easing.OutQuint); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { fadeIn(); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (!Current) fadeOut(); - base.OnHoverLost(state); + base.OnHoverLost(e); } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 7a45ffdd4b..37464faa73 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics.Sprites; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Input.Bindings; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface @@ -44,17 +44,17 @@ namespace osu.Game.Graphics.UserInterface BorderColour = colour.Yellow; } - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { BorderThickness = 3; - base.OnFocus(state); + base.OnFocus(e); } - protected override void OnFocusLost(InputState state) + protected override void OnFocusLost(FocusLostEvent e) { BorderThickness = 0; - base.OnFocusLost(state); + base.OnFocusLost(e); } 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 d203532f9c..fb7b4c5676 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface @@ -67,14 +67,14 @@ namespace osu.Game.Graphics.UserInterface box.Colour = colours.Yellow; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { if (!Active) slideActive(); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (!Active) slideInactive(); diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index f84404a911..c2162b8a42 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -134,7 +134,7 @@ namespace osu.Game.Graphics.UserInterface /// public virtual void ResetCount() { - SetCountWithoutRolling(default(T)); + SetCountWithoutRolling(default); } /// diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 6067481979..08e93fad18 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -2,8 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using OpenTK; using OpenTK.Input; @@ -33,11 +32,11 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = "type to search"; } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (!state.Keyboard.ControlPressed && !state.Keyboard.ShiftPressed) + if (!e.ControlPressed && !e.ShiftPressed) { - switch (args.Key) + switch (e.Key) { case Key.Left: case Key.Right: @@ -49,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface if (!AllowCommit) { - switch (args.Key) + switch (e.Key) { case Key.KeypadEnter: case Key.Enter: @@ -57,16 +56,16 @@ namespace osu.Game.Graphics.UserInterface } } - if (state.Keyboard.ShiftPressed) + if (e.ShiftPressed) { - switch (args.Key) + switch (e.Key) { case Key.Delete: return false; } } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } } } diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 4e6361d1ae..2000eb47e4 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -13,8 +13,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using System; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { @@ -170,9 +169,9 @@ namespace osu.Game.Graphics.UserInterface } } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => IconLayer.ReceiveMouseInputAt(screenSpacePos) || TextLayer.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => IconLayer.ReceivePositionalInputAt(screenSpacePos) || TextLayer.ReceivePositionalInputAt(screenSpacePos); - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { this.ResizeTo(SIZE_EXTENDED, transform_time, Easing.OutElastic); IconLayer.FadeColour(HoverColour, transform_time, Easing.OutElastic); @@ -182,7 +181,7 @@ namespace osu.Game.Graphics.UserInterface return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { this.ResizeTo(SIZE_RETRACTED, transform_time, Easing.OutElastic); IconLayer.FadeColour(TextLayer.Colour, transform_time, Easing.OutElastic); @@ -190,12 +189,12 @@ namespace osu.Game.Graphics.UserInterface bouncingIcon.ScaleTo(1, transform_time, Easing.OutElastic); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { return true; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { var flash = new Box { @@ -209,7 +208,7 @@ namespace osu.Game.Graphics.UserInterface flash.FadeOut(500, Easing.OutQuint); flash.Expire(); - return base.OnClick(state); + return base.OnClick(e); } private class BouncingIcon : BeatSyncedContainer diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index 6783b9712c..fa8168c1de 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -37,7 +37,7 @@ namespace osu.Game.IPC return; } - if (importer.HandledExtensions.Contains(Path.GetExtension(path))) + if (importer.HandledExtensions.Contains(Path.GetExtension(path)?.ToLowerInvariant())) importer.Import(path); } } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index b21deff509..2f5f1aea3f 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -71,17 +71,17 @@ namespace osu.Game.Input.Bindings ToggleSettings, [Description("Toggle osu!direct")] ToggleDirect, - [Description("Increase Volume")] + [Description("Increase volume")] IncreaseVolume, - [Description("Decrease Volume")] + [Description("Decrease volume")] DecreaseVolume, [Description("Toggle mute")] ToggleMute, // In-Game Keybindings - [Description("Skip Cutscene")] + [Description("Skip cutscene")] SkipCutscene, - [Description("Quick Retry (Hold)")] + [Description("Quick retry (hold)")] QuickRetry, [Description("Take screenshot")] diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index 5f53868b28..0a62c6ca72 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -3,10 +3,13 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Input.Handlers; using osu.Framework.Input.StateChanges; +using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Framework.Platform; +using osu.Game.Rulesets.UI; using OpenTK; namespace osu.Game.Input.Handlers @@ -40,7 +43,29 @@ namespace osu.Game.Input.Handlers public void Apply(InputState state, IInputStateChangeHandler handler) { - handler.HandleCustomInput(state, this); + if (!(state is RulesetInputManagerInputState inputState)) + throw new InvalidOperationException($"{nameof(ReplayState)} should only be applied to a {nameof(RulesetInputManagerInputState)}"); + + var lastPressed = inputState.LastReplayState?.PressedActions ?? new List(); + var released = lastPressed.Except(PressedActions).ToArray(); + var pressed = PressedActions.Except(lastPressed).ToArray(); + + inputState.LastReplayState = this; + + handler.HandleInputStateChange(new ReplayStateChangeEvent(state, this, released, pressed)); + } + } + + public class ReplayStateChangeEvent : InputStateChangeEvent + { + public readonly T[] ReleasedActions; + public readonly T[] PressedActions; + + public ReplayStateChangeEvent(InputState state, IInput input, T[] releasedActions, T[] pressedActions) + : base(state, input) + { + ReleasedActions = releasedActions; + PressedActions = pressedActions; } } } diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs new file mode 100644 index 0000000000..5ab43da046 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180913080842_AddRankStatus")] + partial class AddRankStatus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); + + 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("Status"); + + 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("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.Property("Status"); + + 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/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs new file mode 100644 index 0000000000..bba4944bb7 --- /dev/null +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddRankStatus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: -3); // NONE + + migrationBuilder.AddColumn( + name: "Status", + table: "BeatmapInfo", + nullable: false, + defaultValue: -3); // NONE + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapSetInfo"); + + migrationBuilder.DropColumn( + name: "Status", + table: "BeatmapInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs new file mode 100644 index 0000000000..b387a45ecf --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -0,0 +1,380 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181007180454_StandardizePaths")] + partial class StandardizePaths + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + 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("Status"); + + 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("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.Property("Status"); + + 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/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs new file mode 100644 index 0000000000..274b8030a9 --- /dev/null +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using System.IO; + +namespace osu.Game.Migrations +{ + public partial class StandardizePaths : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + string windowsStyle = @"\"; + string standardized = "/"; + + // Escaping \ does not seem to be needed. + migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 6dbeaed62f..663676a6d3 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,7 +1,8 @@ // - +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using osu.Game.Database; namespace osu.Game.Migrations @@ -13,7 +14,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -78,6 +79,8 @@ namespace osu.Game.Migrations b.Property("StarDifficulty"); + b.Property("Status"); + b.Property("StoredBookmarks"); b.Property("TimelineZoom"); @@ -173,6 +176,8 @@ namespace osu.Game.Migrations b.Property("Protected"); + b.Property("Status"); + b.HasKey("ID"); b.HasIndex("DeletePending"); diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 67b908e894..7892df9aab 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Diagnostics; +using System.Net.Http; using osu.Framework.Configuration; using osu.Framework.IO.Network; @@ -40,7 +41,7 @@ namespace osu.Game.Online.API using (var req = new AccessTokenRequestPassword(username, password) { Url = $@"{endpoint}/oauth/token", - Method = HttpMethod.POST, + Method = HttpMethod.Post, ClientId = clientId, ClientSecret = clientSecret }) @@ -66,7 +67,7 @@ namespace osu.Game.Online.API using (var req = new AccessTokenRequestRefresh(refresh) { Url = $@"{endpoint}/oauth/token", - Method = HttpMethod.POST, + Method = HttpMethod.Post, ClientId = clientId, ClientSecret = clientSecret }) diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs index d82046e1e5..94f5a114ad 100644 --- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs +++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs @@ -1,38 +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.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 + public class GetMessagesRequest : APIRequest> { - private readonly IEnumerable channels; + private readonly Channel channel; - public GetMessagesRequest(IEnumerable channels, long? sinceId) : base(sinceId) + public GetMessagesRequest(Channel channel) { - 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 a {nameof(Channel.Target)} of {nameof(TargetType.Channel)}"); - - this.channels = channels; + this.channel = channel; } - 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"; + protected override string Target => $@"chat/channels/{channel.Id}/messages"; } } diff --git a/osu.Game/Online/API/Requests/GetUpdatesRequest.cs b/osu.Game/Online/API/Requests/GetUpdatesRequest.cs new file mode 100644 index 0000000000..950ad93396 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUpdatesRequest.cs @@ -0,0 +1,32 @@ +// 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.IO.Network; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class GetUpdatesRequest : APIRequest + { + private readonly long since; + private readonly Channel channel; + + public GetUpdatesRequest(long sinceId, [CanBeNull] Channel channel = null) + { + this.channel = channel; + since = sinceId; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + if (channel != null) req.AddParameter(@"channel", channel.Id.ToString()); + req.AddParameter(@"since", since.ToString()); + + return req; + } + + protected override string Target => @"chat/updates"; + } +} diff --git a/osu.Game/Online/API/Requests/GetUpdatesResponse.cs b/osu.Game/Online/API/Requests/GetUpdatesResponse.cs new file mode 100644 index 0000000000..474d8e1501 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUpdatesResponse.cs @@ -0,0 +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; +using Newtonsoft.Json; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class GetUpdatesResponse + { + [JsonProperty] + public List Presence; + + [JsonProperty] + public List Messages; + } +} diff --git a/osu.Game/Online/API/Requests/JoinChannelRequest.cs b/osu.Game/Online/API/Requests/JoinChannelRequest.cs new file mode 100644 index 0000000000..a0a4667e98 --- /dev/null +++ b/osu.Game/Online/API/Requests/JoinChannelRequest.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class JoinChannelRequest : APIRequest + { + private readonly Channel channel; + private readonly User user; + + public JoinChannelRequest(Channel channel, User user) + { + this.channel = channel; + this.user = user; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Put; + return req; + } + + protected override string Target => $@"chat/channels/{channel.Id}/users/{user.Id}"; + } +} diff --git a/osu.Game/Online/API/Requests/LeaveChannelRequest.cs b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs new file mode 100644 index 0000000000..89bfa303c6 --- /dev/null +++ b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class LeaveChannelRequest : APIRequest + { + private readonly Channel channel; + private readonly User user; + + public LeaveChannelRequest(Channel channel, User user) + { + this.channel = channel; + this.user = user; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Delete; + return req; + } + + protected override string Target => $@"chat/channels/{channel.Id}/users/{user.Id}"; + } +} diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs index e0a9fb83b2..8d9ba5dd5d 100644 --- a/osu.Game/Online/API/Requests/PostMessageRequest.cs +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -1,7 +1,7 @@ // 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 System.Net.Http; using osu.Framework.IO.Network; using osu.Game.Online.Chat; @@ -20,15 +20,13 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); - req.Method = HttpMethod.POST; - req.AddParameter(@"target_type", message.TargetType.GetDescription()); - req.AddParameter(@"target_id", message.TargetId.ToString()); + req.Method = HttpMethod.Post; req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant()); req.AddParameter(@"message", message.Content); return req; } - protected override string Target => @"chat/messages"; + protected override string Target => $@"chat/channels/{message.ChannelId}/messages"; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 99e4392374..193ccf1f6b 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -15,6 +15,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"beatmapset_id")] public int OnlineBeatmapSetID { get; set; } + [JsonProperty(@"status")] + public BeatmapSetOnlineStatus Status { get; set; } + + [JsonProperty(@"beatmapset")] + public APIBeatmapSet BeatmapSet { get; set; } + [JsonProperty(@"playcount")] private int playCount { get; set; } @@ -59,11 +65,13 @@ namespace osu.Game.Online.API.Requests.Responses Ruleset = rulesets.GetRuleset(ruleset), StarDifficulty = starDifficulty, OnlineBeatmapID = OnlineBeatmapID, + Version = version, + Status = Status, BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = OnlineBeatmapSetID, + Status = BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None }, - Version = version, BaseDifficulty = new BeatmapDifficulty { DrainRate = drainRate, diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index bbaaa0c756..8446285070 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -20,10 +20,13 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"id")] public int? OnlineBeatmapSetID { - get { return onlineBeatmapSetID; } - set { onlineBeatmapSetID = value > 0 ? value : null; } + get => onlineBeatmapSetID; + set => onlineBeatmapSetID = value > 0 ? value : null; } + [JsonProperty(@"status")] + public BeatmapSetOnlineStatus Status { get; set; } + [JsonProperty(@"preview_url")] private string preview { get; set; } @@ -42,9 +45,6 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"storyboard")] private bool hasStoryboard { get; set; } - [JsonProperty(@"status")] - private BeatmapSetOnlineStatus status { get; set; } - [JsonProperty(@"submitted_date")] private DateTimeOffset submitted { get; set; } @@ -57,7 +57,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"user_id")] private long creatorId { - set { Author.Id = value; } + set => Author.Id = value; } [JsonProperty(@"beatmaps")] @@ -69,6 +69,7 @@ namespace osu.Game.Online.API.Requests.Responses { OnlineBeatmapSetID = OnlineBeatmapSetID, Metadata = this, + Status = Status, OnlineInfo = new BeatmapSetOnlineInfo { Covers = covers, @@ -76,7 +77,7 @@ namespace osu.Game.Online.API.Requests.Responses PlayCount = playCount, FavouriteCount = favouriteCount, BPM = bpm, - Status = status, + Status = Status, HasVideo = hasVideo, HasStoryboard = hasStoryboard, Submitted = submitted, diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 3c808d1bee..ffea7b83e1 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,16 +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 System.ComponentModel; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class SearchBeatmapSetsRequest : APIRequest> + public class SearchBeatmapSetsRequest : APIRequest { private readonly string query; private readonly RulesetInfo ruleset; @@ -35,6 +33,7 @@ namespace osu.Game.Online.API.Requests public enum BeatmapSearchCategory { Any = 7, + [Description("Ranked & Approved")] RankedApproved = 0, Approved = 1, @@ -43,6 +42,7 @@ namespace osu.Game.Online.API.Requests Qualified = 3, Pending = 4, Graveyard = 5, + [Description("My Maps")] MyMaps = 6, } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs new file mode 100644 index 0000000000..cf8b40d068 --- /dev/null +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs @@ -0,0 +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.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class SearchBeatmapSetsResponse + { + public IEnumerable BeatmapSets; + + /// + /// A collection of parameters which should be passed to the search endpoint to fetch the next page. + /// + [JsonProperty("cursor")] + public dynamic CursorJson; + } +} diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 5f11739227..e6ad72a2bc 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -47,11 +47,6 @@ namespace osu.Game.Online.Chat /// public event Action MessageRemoved; - /// - /// Signalles if the current user joined this channel or not. Defaults to false. - /// - public readonly Bindable Joined = new Bindable(); - /// /// Signalles whether the channels target is a private channel or public channel. /// @@ -68,11 +63,21 @@ namespace osu.Game.Online.Chat public string Topic; [JsonProperty(@"type")] - public string Type; + public ChannelType Type; [JsonProperty(@"channel_id")] public long Id; + [JsonProperty(@"last_message_id")] + public long? LastMessageId; + + /// + /// Signalles if the current user joined this channel or not. Defaults to false. + /// + public Bindable Joined = new Bindable(); + + public const int MAX_HISTORY = 300; + [JsonConstructor] public Channel() { @@ -90,6 +95,8 @@ namespace osu.Game.Online.Chat NewMessagesArrived?.Invoke(new[] { message }); } + public bool MessagesLoaded { get; private set; } + /// /// Adds new messages to the channel and purges old messages. Triggers the event. /// @@ -98,7 +105,14 @@ namespace osu.Game.Online.Chat { messages = messages.Except(Messages).ToArray(); + if (messages.Length == 0) return; + Messages.AddRange(messages); + MessagesLoaded = true; + + var maxMessageId = messages.Max(m => m.Id); + if (maxMessageId > LastMessageId) + LastMessageId = maxMessageId; purgeOldMessages(); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 21e1739c59..377e9ee7bb 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -46,8 +46,7 @@ namespace osu.Game.Online.Chat /// public ObservableCollection AvailableChannels { get; } = new ObservableCollection(); - private readonly IncomingMessagesHandler channelMessagesHandler; - private readonly IncomingMessagesHandler privateMessagesHandler; + /*private readonly IncomingMessagesHandler privateMessagesHandler;*/ private IAPIProvider api; private ScheduledDelegate fetchMessagesScheduleder; @@ -56,11 +55,11 @@ namespace osu.Game.Online.Chat { CurrentChannel.ValueChanged += currentChannelChanged; - channelMessagesHandler = new IncomingMessagesHandler( - lastId => new GetMessagesRequest(JoinedChannels.Where(c => c.Target == TargetType.Channel), lastId), handleChannelMessages); + /*channelMessagesHandler = new IncomingMessagesHandler( + lastId => new GetMessagesRequest(JoinedChannels.Where(c => c.Target == TargetType.Channel)), handleChannelMessages); privateMessagesHandler = new IncomingMessagesHandler( - lastId => new GetPrivateMessagesRequest(lastId),handleUserMessages); + lastId => new GetPrivateMessagesRequest(lastId),handleUserMessages);*/ } /// @@ -118,8 +117,7 @@ namespace osu.Game.Online.Chat { Sender = api.LocalUser.Value, Timestamp = DateTimeOffset.Now, - TargetType = CurrentChannel.Value.Target, - TargetId = CurrentChannel.Value.Id, + ChannelId = CurrentChannel.Value.Id, IsAction = isAction, Content = text }; @@ -173,11 +171,11 @@ namespace osu.Game.Online.Chat private void fetchNewMessages() { - if (channelMessagesHandler.CanRequestNewMessages) + /*if (channelMessagesHandler.CanRequestNewMessages) channelMessagesHandler.RequestNewMessages(api); if (privateMessagesHandler.CanRequestNewMessages) - privateMessagesHandler.RequestNewMessages(api); + privateMessagesHandler.RequestNewMessages(api);*/ } private void handleUserMessages(IEnumerable messages) @@ -200,7 +198,7 @@ namespace osu.Game.Online.Chat long localUserId = api.LocalUser.Value.Id; - var outgoingGroups = messages.Where(m => m.Sender.Id == localUserId).GroupBy(m => m.TargetId); + var outgoingGroups = messages.Where(m => m.Sender.Id == localUserId).GroupBy(m => m.ChannelId); var incomingGroups = messages.Where(m => m.Sender.Id != localUserId).GroupBy(m => m.UserId); foreach (var group in incomingGroups) @@ -223,7 +221,7 @@ namespace osu.Game.Online.Chat foreach (var withoutReplyGroup in withoutReplyGroups) { - var userReq = new GetUserRequest(withoutReplyGroup.First().TargetId); + var userReq = new GetUserRequest(withoutReplyGroup.First().ChannelId); userReq.Failure += exception => Logger.Error(exception, "Failed to get user informations."); userReq.Success += user => @@ -240,7 +238,7 @@ namespace osu.Game.Online.Chat { var channels = JoinedChannels.ToList(); - foreach (var group in messages.GroupBy(m => m.TargetId)) + foreach (var group in messages.GroupBy(m => m.ChannelId)) channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); } @@ -289,7 +287,7 @@ namespace osu.Game.Online.Chat /// The channel public void FetchInitalMessages(Channel channel) { - var fetchInitialMsgReq = new GetMessagesRequest(new[] { channel }, null); + var fetchInitialMsgReq = new GetMessagesRequest(channel); fetchInitialMsgReq.Success += handleChannelMessages; fetchInitialMsgReq.Failure += exception => Logger.Error(exception, $"Failed to fetch inital messages for the channel {channel.Name}"); api.Queue(fetchInitialMsgReq); @@ -306,8 +304,8 @@ namespace osu.Game.Online.Chat fetchMessagesScheduleder = Scheduler.AddDelayed(fetchNewMessages, 1000, true); break; default: - channelMessagesHandler.CancelOngoingRequests(); - privateMessagesHandler.CancelOngoingRequests(); + /*channelMessagesHandler.CancelOngoingRequests(); + privateMessagesHandler.CancelOngoingRequests();*/ fetchMessagesScheduleder?.Cancel(); fetchMessagesScheduleder = null; diff --git a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs b/osu.Game/Online/Chat/ChannelType.cs similarity index 61% rename from osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs rename to osu.Game/Online/Chat/ChannelType.cs index ce8b139b43..4ac0a99fc6 100644 --- a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs +++ b/osu.Game/Online/Chat/ChannelType.cs @@ -1,10 +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.Rulesets.Edit.Tools +namespace osu.Game.Online.Chat { - public interface ICompositionTool + public enum ChannelType { - string Name { get; } + PM, + Public } } diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 475363bd51..0148d1d2c3 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -24,7 +24,7 @@ namespace osu.Game.Online.Chat /// public List Parts; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); @@ -53,7 +53,7 @@ namespace osu.Game.Online.Chat this.parts = parts; } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); } } } diff --git a/osu.Game/Online/Chat/InfoMessage.cs b/osu.Game/Online/Chat/InfoMessage.cs index 2ff901deb1..103a999591 100644 --- a/osu.Game/Online/Chat/InfoMessage.cs +++ b/osu.Game/Online/Chat/InfoMessage.cs @@ -15,11 +15,7 @@ namespace osu.Game.Online.Chat Timestamp = DateTimeOffset.Now; Content = message; - Sender = new User - { - Username = @"system", - Colour = @"0000ff", - }; + Sender = User.SYSTEM_USER; } } } diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 6c3d118d1b..b4a7b6faa7 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -16,13 +16,10 @@ namespace osu.Game.Online.Chat //todo: this should be inside sender. [JsonProperty(@"sender_id")] - public int UserId; + public long UserId; - [JsonProperty(@"target_type")] - public TargetType TargetType; - - [JsonProperty(@"target_id")] - public long TargetId; + [JsonProperty(@"channel_id")] + public long ChannelId; [JsonProperty(@"is_action")] public bool IsAction; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f08ee42c40..76a9102c5e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -66,7 +66,8 @@ namespace osu.Game private BeatmapSetOverlay beatmapSetOverlay; - private ScreenshotManager screenshotManager; + [Cached] + private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); protected RavenLogger RavenLogger; @@ -284,9 +285,6 @@ namespace osu.Game protected override void LoadComplete() { - // this needs to be cached before base.LoadComplete as it is used by MenuCursorContainer. - dependencies.Cache(screenshotManager = new ScreenshotManager()); - base.LoadComplete(); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results @@ -355,7 +353,7 @@ namespace osu.Game loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); loadComponentSingleFile(musicController = new MusicController { - Depth = -4, + Depth = -5, Position = new Vector2(0, Toolbar.HEIGHT), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9a5dac35b9..73c970ce5d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -154,7 +154,7 @@ namespace osu.Game 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(ScoreStore = new ScoreStore(contextFactory, Host, BeatmapManager, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); @@ -243,7 +243,7 @@ namespace osu.Game public void Import(params string[] paths) { - var extension = Path.GetExtension(paths.First()); + var extension = Path.GetExtension(paths.First())?.ToLowerInvariant(); foreach (var importer in fileImporters) if (importer.HandledExtensions.Contains(extension)) importer.Import(paths); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 8186bf8685..bd9d65ccd8 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -10,7 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -174,9 +174,9 @@ namespace osu.Game.Overlays.BeatmapSet { public Action OnLostHover; - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { - base.OnHoverLost(state); + base.OnHoverLost(e); OnLostHover?.Invoke(); } } @@ -241,24 +241,24 @@ namespace osu.Game.Overlays.BeatmapSet }; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { fadeIn(); OnHovered?.Invoke(Beatmap); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (State == DifficultySelectorState.NotSelected) fadeOut(); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { OnClicked?.Invoke(Beatmap); - return base.OnClick(state); + return base.OnClick(e); } private void fadeIn() diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index 18391c1177..1988b7d222 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -7,7 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }; - Action = () => playButton.TriggerOnClick(); + Action = () => playButton.Click(); Playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100); } @@ -89,16 +89,16 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons progress.Width = 0; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { bg.FadeColour(Color4.Black.Opacity(0.5f), 100); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { bg.FadeColour(Color4.Black.Opacity(0.25f), 100); - base.OnHoverLost(state); + base.OnHoverLost(e); } } } diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index afba99f928..6f01fae92d 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -230,10 +230,12 @@ namespace osu.Game.Overlays.BeatmapSet Spacing = new Vector2(10), Children = new Drawable[] { - onlineStatusPill = new BeatmapSetOnlineStatusPill(14, new MarginPadding { Horizontal = 25, Vertical = 8 }) + onlineStatusPill = new BeatmapSetOnlineStatusPill { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + TextSize = 14, + TextPadding = new MarginPadding { Horizontal = 25, Vertical = 8 } }, Details = new Details(), }, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs index dc350d2c4c..0be83db39e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs @@ -3,7 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores this.profile = profile; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { profile?.ShowUser(user); return true; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index d9af81c19a..309d75f60a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -6,7 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; @@ -125,18 +125,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores background.Colour = colours.Gray4; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { background.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { background.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index a1ebab09b1..d954b48b86 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -8,7 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; @@ -184,16 +184,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores BorderColour = rankText.Colour = colours.Yellow; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { background.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { background.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } private class InfoColumn : FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 02cc89e57e..23c8ec3aab 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -7,7 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -51,7 +51,7 @@ namespace osu.Game.Overlays } // receive input outside our bounds so we can trigger a close event on ourselves. - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public BeatmapSetOverlay() { @@ -127,7 +127,7 @@ namespace osu.Game.Overlays FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { State = Visibility.Hidden; return true; diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs new file mode 100644 index 0000000000..ec4fd85901 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChatTabControl.cs @@ -0,0 +1,348 @@ +// 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.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using OpenTK; +using OpenTK.Input; +using OpenTK.Graphics; +using osu.Framework.Configuration; +using System; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Chat +{ + public class ChatTabControl : OsuTabControl + { + private const float shear_width = 10; + + public Action OnRequestLeave; + + public readonly Bindable ChannelSelectorActive = new Bindable(); + + private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab; + + public ChatTabControl() + { + TabContainer.Margin = new MarginPadding { Left = 50 }; + TabContainer.Spacing = new Vector2(-shear_width, 0); + TabContainer.Masking = false; + + AddInternal(new SpriteIcon + { + Icon = FontAwesome.fa_comments, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + Margin = new MarginPadding(10), + }); + + AddTabItem(selectorTab = new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" })); + + ChannelSelectorActive.BindTo(selectorTab.Active); + } + + protected override void AddTabItem(TabItem item, bool addToDropdown = true) + { + if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue) + // performTabSort might've made selectorTab's position wonky, fix it + TabContainer.SetLayoutPosition(selectorTab, float.MaxValue); + + base.AddTabItem(item, addToDropdown); + + if (SelectedTab == null) + SelectTab(item); + } + + protected override TabItem CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + + protected override void SelectTab(TabItem tab) + { + if (tab is ChannelTabItem.ChannelSelectorTabItem) + { + tab.Active.Toggle(); + return; + } + + selectorTab.Active.Value = false; + + base.SelectTab(tab); + } + + private void tabCloseRequested(TabItem tab) + { + int totalTabs = TabContainer.Count - 1; // account for selectorTab + int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); + + if (tab == SelectedTab && totalTabs > 1) + // Select the tab after tab-to-be-removed's index, or the tab before if current == last + SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]); + else if (totalTabs == 1 && !selectorTab.Active) + // Open channel selection overlay if all channel tabs will be closed after removing this tab + SelectTab(selectorTab); + + OnRequestLeave?.Invoke(tab.Value); + } + + private class ChannelTabItem : TabItem + { + private Color4 backgroundInactive; + private Color4 backgroundHover; + private Color4 backgroundActive; + + public override bool IsRemovable => !Pinned; + + private readonly SpriteText text; + private readonly SpriteText textBold; + private readonly ClickableContainer closeButton; + private readonly Box box; + private readonly Box highlightBox; + private readonly SpriteIcon icon; + + public Action OnRequestClose; + + private void updateState() + { + if (Active) + fadeActive(); + else + fadeInactive(); + } + + private const float transition_length = 400; + + private void fadeActive() + { + this.ResizeTo(new Vector2(Width, 1.1f), transition_length, Easing.OutQuint); + + box.FadeColour(backgroundActive, transition_length, Easing.OutQuint); + highlightBox.FadeIn(transition_length, Easing.OutQuint); + + text.FadeOut(transition_length, Easing.OutQuint); + textBold.FadeIn(transition_length, Easing.OutQuint); + } + + private void fadeInactive() + { + this.ResizeTo(new Vector2(Width, 1), transition_length, Easing.OutQuint); + + box.FadeColour(backgroundInactive, transition_length, Easing.OutQuint); + highlightBox.FadeOut(transition_length, Easing.OutQuint); + + text.FadeIn(transition_length, Easing.OutQuint); + textBold.FadeOut(transition_length, Easing.OutQuint); + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + if (e.Button == MouseButton.Middle) + { + closeButton.Action(); + return true; + } + + return false; + } + + protected override bool OnHover(HoverEvent e) + { + if (IsRemovable) + closeButton.FadeIn(200, Easing.OutQuint); + + if (!Active) + box.FadeColour(backgroundHover, transition_length, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + closeButton.FadeOut(200, Easing.OutQuint); + updateState(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + backgroundActive = colours.ChatBlue; + backgroundInactive = colours.Gray4; + backgroundHover = colours.Gray7; + + highlightBox.Colour = colours.Yellow; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + } + + public ChannelTabItem(Channel value) : base(value) + { + Width = 150; + + RelativeSizeAxes = Axes.Y; + + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + Shear = new Vector2(shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0); + + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 10, + Colour = Color4.Black.Opacity(0.2f), + }; + + Children = new Drawable[] + { + box = new Box + { + EdgeSmoothness = new Vector2(1, 0), + RelativeSizeAxes = Axes.Both, + }, + highlightBox = new Box + { + Width = 5, + Alpha = 0, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + EdgeSmoothness = new Vector2(1, 0), + RelativeSizeAxes = Axes.Y, + }, + new Container + { + Shear = new Vector2(-shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0), + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + icon = new SpriteIcon + { + Icon = FontAwesome.fa_hashtag, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.Black, + X = -10, + Alpha = 0.2f, + Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT), + }, + text = new OsuSpriteText + { + Margin = new MarginPadding(5), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = value.ToString(), + TextSize = 18, + }, + textBold = new OsuSpriteText + { + Alpha = 0, + Margin = new MarginPadding(5), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = value.ToString(), + Font = @"Exo2.0-Bold", + TextSize = 18, + }, + closeButton = new CloseButton + { + Alpha = 0, + Margin = new MarginPadding { Right = 20 }, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Action = delegate + { + if (IsRemovable) OnRequestClose?.Invoke(this); + }, + }, + }, + }, + }; + } + + public class CloseButton : OsuClickableContainer + { + private readonly SpriteIcon icon; + + public CloseButton() + { + Size = new Vector2(20); + + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.75f), + Icon = FontAwesome.fa_close, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + icon.ScaleTo(0.5f, 1000, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + icon.ScaleTo(0.75f, 1000, Easing.OutElastic); + return base.OnMouseUp(e); + } + + protected override bool OnHover(HoverEvent e) + { + icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.FadeColour(Color4.White, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + } + + public class ChannelSelectorTabItem : ChannelTabItem + { + public override bool IsRemovable => false; + + public override bool IsSwitchable => false; + + public ChannelSelectorTabItem(Channel value) : base(value) + { + Depth = float.MaxValue; + Width = 45; + + icon.Alpha = 0; + + text.TextSize = 45; + textBold.TextSize = 45; + } + + [BackgroundDependencyLoader] + private new void load(OsuColour colour) + { + backgroundInactive = colour.Gray2; + backgroundActive = colour.Gray3; + } + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + } + } +} diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index 1d537381c6..cb6caf1506 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -9,7 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -155,15 +155,15 @@ namespace osu.Game.Overlays.Chat.Selection FinishTransforms(true); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { if (!channel.Joined.Value) name.FadeColour(hoverColour, 50, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (!channel.Joined.Value) name.FadeColour(Color4.White, transition_duration); diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index e2d064474f..9766c0f2c5 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -10,7 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; @@ -157,10 +157,10 @@ namespace osu.Game.Overlays.Chat.Selection headerBg.Colour = colours.Gray2.Opacity(0.75f); } - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { GetContainingInputManager().ChangeFocus(search); - base.OnFocus(state); + base.OnFocus(e); } protected override void PopIn() diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 13913baefe..e6de55f9b2 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -122,7 +122,7 @@ namespace osu.Game.Overlays.Chat.Tabs protected virtual bool ShowCloseOnHover => true; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { if (IsRemovable && ShowCloseOnHover) CloseButton.FadeIn(200, Easing.OutQuint); @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.Chat.Tabs return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { CloseButton.FadeOut(200, Easing.OutQuint); updateState(); diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs index 4476e61dfc..0adaa40889 100644 --- a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs +++ b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs @@ -2,8 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using OpenTK; @@ -29,28 +28,28 @@ namespace osu.Game.Overlays.Chat.Tabs }; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { icon.ScaleTo(0.5f, 1000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { icon.ScaleTo(0.75f, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { icon.FadeColour(Color4.Red, 200, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { icon.FadeColour(Color4.White, 200, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } } } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 912734ba0c..9fc4c15849 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; -using System.Diagnostics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -12,7 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -47,7 +46,7 @@ namespace osu.Game.Overlays private readonly ChannelTabControl channelTabControl; private readonly Container chatContainer; - private readonly Container tabsArea; + private readonly TabsArea tabsArea; private readonly Box chatBackground; private readonly Box tabBackground; @@ -56,8 +55,7 @@ namespace osu.Game.Overlays 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 override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || channelSelection.State == Visibility.Visible && channelSelection.ReceivePositionalInputAt(screenSpacePos); public ChatOverlay() { @@ -141,11 +139,8 @@ namespace osu.Game.Overlays loading = new LoadingAnimation(), } }, - tabsArea = new Container + tabsArea = new TabsArea { - Name = @"tabs area", - RelativeSizeAxes = Axes.X, - Height = TAB_AREA_HEIGHT, Children = new Drawable[] { tabBackground = new Box @@ -258,25 +253,22 @@ namespace osu.Game.Overlays private double startDragChatHeight; private bool isDragging; - protected override bool OnDragStart(InputState state) + protected override bool OnDragStart(DragStartEvent e) { isDragging = tabsArea.IsHovered; if (!isDragging) - return base.OnDragStart(state); + return base.OnDragStart(e); startDragChatHeight = ChatHeight.Value; return true; } - protected override bool OnDrag(InputState state) + protected override bool OnDrag(DragEvent e) { 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; + double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.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) @@ -288,19 +280,19 @@ namespace osu.Game.Overlays return true; } - protected override bool OnDragEnd(InputState state) + protected override bool OnDragEnd(DragEndEvent e) { isDragging = false; - return base.OnDragEnd(state); + return base.OnDragEnd(e); } public override bool AcceptsFocus => true; - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { //this is necessary as textbox is masked away and therefore can't get focus :( GetContainingInputManager().ChangeFocus(textbox); - base.OnFocus(state); + base.OnFocus(e); } protected override void PopIn() @@ -334,6 +326,7 @@ namespace osu.Game.Overlays ChatHeight.TriggerChange(); chatBackground.Colour = colours.ChatBlue; + loading.Show(); this.channelManager = channelManager; @@ -377,5 +370,18 @@ namespace osu.Game.Overlays textbox.Text = string.Empty; } + + private class TabsArea : Container + { + // IsHovered is used + public override bool HandlePositionalInput => true; + + public TabsArea() + { + Name = @"tabs area"; + RelativeSizeAxes = Axes.X; + Height = TAB_AREA_HEIGHT; + } + } } } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index c9c90b4555..f421d2202c 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -8,8 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -26,7 +25,7 @@ namespace osu.Game.Overlays.Dialog public static readonly float ENTER_DURATION = 500; public static readonly float EXIT_DURATION = 200; - protected override bool BlockPassThroughMouse => false; + protected override bool BlockPositionalInput => false; private readonly Vector2 ringSize = new Vector2(100f); private readonly Vector2 ringMinifiedSize = new Vector2(20f); @@ -199,19 +198,19 @@ namespace osu.Game.Overlays.Dialog switch (action) { case GlobalAction.Select: - Buttons.OfType().FirstOrDefault()?.TriggerOnClick(); + Buttons.OfType().FirstOrDefault()?.Click(); return true; } return base.OnPressed(action); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Repeat) return false; + if (e.Repeat) return false; // press button at number if 1-9 on number row or keypad are pressed - var k = args.Key; + var k = e.Key; if (k >= Key.Number1 && k <= Key.Number9) { pressButtonAtIndex(k - Key.Number1); @@ -224,7 +223,7 @@ namespace osu.Game.Overlays.Dialog return true; } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } protected override void PopIn() @@ -252,7 +251,7 @@ namespace osu.Game.Overlays.Dialog if (!actionInvoked) // In the case a user did not choose an action before a hide was triggered, press the last button. // This is presumed to always be a sane default "cancel" action. - buttonsContainer.Last().TriggerOnClick(); + buttonsContainer.Last().Click(); base.PopOut(); content.FadeOut(EXIT_DURATION, Easing.InSine); @@ -261,7 +260,7 @@ namespace osu.Game.Overlays.Dialog private void pressButtonAtIndex(int index) { if (index < Buttons.Count()) - Buttons.Skip(index).First().TriggerOnClick(); + Buttons.Skip(index).First().Click(); } } } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index c40f517023..dae502dbd9 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays protected override bool PlaySamplesOnStateChange => false; - protected override bool BlockPassThroughKeyboard => true; + protected override bool BlockNonPositionalInput => true; private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) { diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index eb940bff60..1c462e3a73 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -7,11 +7,11 @@ 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.States; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Direct } [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationEngine localisation) + private void load(OsuColour colours) { Content.CornerRadius = 4; @@ -74,13 +74,13 @@ namespace osu.Game.Overlays.Direct { new OsuSpriteText { - Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)), TextSize = 18, Font = @"Exo2.0-BoldItalic", }, new OsuSpriteText { - Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)), Font = @"Exo2.0-BoldItalic", }, }, @@ -217,23 +217,25 @@ namespace osu.Game.Overlays.Direct statusContainer.Add(new IconPill(FontAwesome.fa_image)); } - statusContainer.Add(new BeatmapSetOnlineStatusPill(12, new MarginPadding { Horizontal = 10, Vertical = 5 }) + statusContainer.Add(new BeatmapSetOnlineStatusPill { + TextSize = 12, + TextPadding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, }); PreviewPlaying.ValueChanged += _ => updateStatusContainer(); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { updateStatusContainer(); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { - base.OnHoverLost(state); + base.OnHoverLost(e); updateStatusContainer(); } diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 850ead37f6..6cb5ebad4e 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -10,8 +10,8 @@ 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.Framework.Localisation; using osu.Game.Beatmaps; namespace osu.Game.Overlays.Direct @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Direct } [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation, OsuColour colours) + private void load(OsuColour colours) { Content.CornerRadius = 5; @@ -94,13 +94,13 @@ namespace osu.Game.Overlays.Direct { new OsuSpriteText { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)), TextSize = 18, Font = @"Exo2.0-BoldItalic", }, new OsuSpriteText { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)), Font = @"Exo2.0-BoldItalic", }, } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 2ee1857ca2..5c7c34a0ed 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -121,27 +121,27 @@ namespace osu.Game.Overlays.Direct } } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); content.MoveToY(-4, hover_transition_time, Easing.OutQuint); if (FadePlayButton) PlayButton.FadeIn(120, Easing.InOutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); content.MoveToY(0, hover_transition_time, Easing.OutQuint); if (FadePlayButton && !PreviewPlaying) PlayButton.FadeOut(120, Easing.InOutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { ShowInformation(); return true; diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index df98cc3c32..c4825f72fe 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -73,8 +73,8 @@ namespace osu.Game.Overlays.Direct iconContainer.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100); } - public override bool HandleKeyboardInput => !bindable.Disabled && base.HandleKeyboardInput; - public override bool HandleMouseInput => !bindable.Disabled && base.HandleMouseInput; + public override bool HandleNonPositionalInput => !bindable.Disabled && base.HandleNonPositionalInput; + public override bool HandlePositionalInput => !bindable.Disabled && base.HandlePositionalInput; public RulesetToggleButton(Bindable bindable, RulesetInfo ruleset) { diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 092c9ddbea..ac7a26fca3 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -93,23 +93,23 @@ namespace osu.Game.Overlays.Direct hoverColour = colour.Yellow; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { Playing.Toggle(); return true; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { icon.FadeColour(hoverColour, 120, Easing.InOutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (!Playing.Value) icon.FadeColour(Color4.White, 120, Easing.InOutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } private void playingStateChanged(bool playing) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index f63d314053..641f57d25f 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -288,7 +288,7 @@ namespace osu.Game.Overlays { Task.Run(() => { - var sets = response.Select(r => r.ToBeatmapSet(rulesets)).ToList(); + var sets = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); // may not need scheduling; loads async internally. Schedule(() => diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 86baaf3905..63ddc25fde 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -10,15 +10,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input; using OpenTK.Graphics; using OpenTK.Input; -using JoystickEventArgs = osu.Framework.Input.EventArgs.JoystickEventArgs; namespace osu.Game.Overlays.KeyBinding { @@ -50,7 +48,7 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer buttons; - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text); + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text); public KeyBindingRow(object action, IEnumerable bindings) { @@ -125,18 +123,18 @@ namespace osu.Game.Overlays.KeyBinding } } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { FadeEdgeEffectTo(1, transition_time, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { FadeEdgeEffectTo(0, transition_time, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } public override bool AcceptsFocus => bindTarget == null; @@ -149,16 +147,16 @@ namespace osu.Game.Overlays.KeyBinding private bool isModifier(Key k) => k < Key.F1; - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { if (!HasFocus || !bindTarget.IsHovered) - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); if (!AllowMainMouseButtons) { - switch (args.Button) + switch (e.Button) { case MouseButton.Left: case MouseButton.Right: @@ -166,15 +164,15 @@ namespace osu.Game.Overlays.KeyBinding } } - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); return true; } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { // don't do anything until the last button is released. - if (!HasFocus || state.Mouse.Buttons.Any()) - return base.OnMouseUp(state, args); + if (!HasFocus || e.HasAnyButtonPressed) + return base.OnMouseUp(e); if (bindTarget.IsHovered) finalise(); @@ -183,31 +181,31 @@ namespace osu.Game.Overlays.KeyBinding return true; } - protected override bool OnScroll(InputState state) + protected override bool OnScroll(ScrollEvent e) { if (HasFocus) { if (bindTarget.IsHovered) { - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state, state.Mouse.ScrollDelta)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta)); finalise(); return true; } } - return base.OnScroll(state); + return base.OnScroll(e); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { if (!HasFocus) return false; - switch (args.Key) + switch (e.Key) { case Key.Delete: { - if (state.Keyboard.ShiftPressed) + if (e.ShiftPressed) { bindTarget.UpdateKeyCombination(InputKey.None); finalise(); @@ -218,35 +216,35 @@ namespace osu.Game.Overlays.KeyBinding } } - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); - if (!isModifier(args.Key)) finalise(); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + if (!isModifier(e.Key)) finalise(); return true; } - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + protected override bool OnKeyUp(KeyUpEvent e) { - if (!HasFocus) return base.OnKeyUp(state, args); + if (!HasFocus) return base.OnKeyUp(e); finalise(); return true; } - protected override bool OnJoystickPress(InputState state, JoystickEventArgs args) + protected override bool OnJoystickPress(JoystickPressEvent e) { if (!HasFocus) return false; - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); finalise(); return true; } - protected override bool OnJoystickRelease(InputState state, JoystickEventArgs args) + protected override bool OnJoystickRelease(JoystickReleaseEvent e) { if (!HasFocus) - return base.OnJoystickRelease(state, args); + return base.OnJoystickRelease(e); finalise(); return true; @@ -273,7 +271,7 @@ namespace osu.Game.Overlays.KeyBinding pressAKey.BypassAutoSizeAxes |= Axes.Y; } - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { AutoSizeDuration = 500; AutoSizeEasing = Easing.OutQuint; @@ -282,13 +280,13 @@ namespace osu.Game.Overlays.KeyBinding pressAKey.BypassAutoSizeAxes &= ~Axes.Y; updateBindTarget(); - base.OnFocus(state); + base.OnFocus(e); } - protected override void OnFocusLost(InputState state) + protected override void OnFocusLost(FocusLostEvent e) { finalise(); - base.OnFocusLost(state); + base.OnFocusLost(e); } private void updateBindTarget() @@ -367,16 +365,16 @@ namespace osu.Game.Overlays.KeyBinding hoverColour = colours.YellowDark; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { updateHoverState(); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { updateHoverState(); - base.OnHoverLost(state); + base.OnHoverLost(e); } private void updateHoverState() diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs index 6e8b4494dc..736843ee4d 100644 --- a/osu.Game/Overlays/MainSettings.cs +++ b/osu.Game/Overlays/MainSettings.cs @@ -6,8 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -138,16 +137,16 @@ namespace osu.Game.Overlays }; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { aspect.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } public bool OnPressed(GlobalAction action) @@ -155,7 +154,7 @@ namespace osu.Game.Overlays switch (action) { case GlobalAction.Back: - TriggerOnClick(); + Click(); return true; } diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index bd67a718a7..dcd325490a 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -19,8 +19,7 @@ using osu.Framework.Graphics.Textures; using OpenTK.Input; using osu.Framework.Graphics.Shapes; using System; -using System.Linq; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; namespace osu.Game.Overlays @@ -176,15 +175,15 @@ namespace osu.Game.Overlays particleContainer.Add(new MedalParticle(RNG.Next(0, 359))); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { dismiss(); return true; } - protected override void OnFocusLost(InputState state) + protected override void OnFocusLost(FocusLostEvent e) { - if (state.Keyboard.Keys.Contains(Key.Escape)) dismiss(); + if (e.CurrentState.Keyboard.Keys.IsPressed(Key.Escape)) dismiss(); } private const double initial_duration = 400; diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 8024f9a732..ecb65f6df2 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -13,8 +13,7 @@ using osu.Game.Rulesets.UI; using System; using System.Linq; using osu.Framework.Graphics.Cursor; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Mods @@ -149,20 +148,20 @@ namespace osu.Game.Overlays.Mods public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { scaleContainer.ScaleTo(0.9f, 800, Easing.Out); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { scaleContainer.ScaleTo(1, 500, Easing.OutElastic); // only trigger the event if we are inside the area of the button - if (Contains(ToScreenSpace(state.Mouse.Position - Position))) + if (Contains(e.ScreenSpaceMousePosition)) { - switch (args.Button) + switch (e.Button) { case MouseButton.Left: SelectNext(1); diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 37bffaaf12..c0d2d889c6 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -10,8 +10,7 @@ using osu.Game.Rulesets.Mods; using System; using System.Linq; using System.Collections.Generic; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Mods { @@ -55,16 +54,16 @@ namespace osu.Game.Overlays.Mods private ModButton[] buttons = { }; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { if (ToggleKeys != null) { - var index = Array.IndexOf(ToggleKeys, args.Key); + var index = Array.IndexOf(ToggleKeys, e.Key); if (index > -1 && index < buttons.Length) - buttons[index].SelectNext(state.Keyboard.ShiftPressed ? -1 : 1); + buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e83dedaf35..55d5d797e7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Mods protected readonly OsuSpriteText MultiplierLabel, UnrankedLabel; private readonly FillFlowContainer footerContainer; - protected override bool BlockPassThroughKeyboard => false; + protected override bool BlockNonPositionalInput => false; protected readonly FillFlowContainer ModSectionsContainer; diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 62c9020160..c0a59df767 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -8,8 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -28,8 +27,8 @@ namespace osu.Game.Overlays.Music private SpriteIcon handle; private TextFlowContainer text; private IEnumerable titleSprites; - private UnicodeBindableString titleBind; - private UnicodeBindableString artistBind; + private ILocalisedBindableString titleBind; + private ILocalisedBindableString artistBind; public readonly BeatmapSetInfo BeatmapSetInfo; @@ -37,16 +36,16 @@ namespace osu.Game.Overlays.Music public bool IsDraggable { get; private set; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { IsDraggable = handle.IsHovered; - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { IsDraggable = false; - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } private bool selected; @@ -74,7 +73,7 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationEngine localisation) + private void load(OsuColour colours, LocalisationManager localisation) { hoverColour = colours.Yellow; artistColour = colours.Gray9; @@ -97,11 +96,10 @@ namespace osu.Game.Overlays.Music }, }; - titleBind = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); - artistBind = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); + titleBind = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title))); + artistBind = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist))); - artistBind.ValueChanged += newText => recreateText(); - artistBind.TriggerChange(); + artistBind.BindValueChanged(newText => recreateText(), true); } private void recreateText() @@ -124,19 +122,19 @@ namespace osu.Game.Overlays.Music }); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { handle.FadeIn(fade_duration); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { handle.FadeOut(fade_duration); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { OnSelect?.Invoke(BeatmapSetInfo); return true; diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 32674f0a84..17c8d2f154 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -8,7 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using OpenTK; @@ -115,25 +115,25 @@ namespace osu.Game.Overlays.Music private Vector2 nativeDragPosition; private PlaylistItem draggedItem; - protected override bool OnDragStart(InputState state) + protected override bool OnDragStart(DragStartEvent e) { - nativeDragPosition = state.Mouse.NativeState.Position; + nativeDragPosition = e.ScreenSpaceMousePosition; draggedItem = items.FirstOrDefault(d => d.IsDraggable); - return draggedItem != null || base.OnDragStart(state); + return draggedItem != null || base.OnDragStart(e); } - protected override bool OnDrag(InputState state) + protected override bool OnDrag(DragEvent e) { - nativeDragPosition = state.Mouse.NativeState.Position; + nativeDragPosition = e.ScreenSpaceMousePosition; if (draggedItem == null) - return base.OnDrag(state); + return base.OnDrag(e); return true; } - protected override bool OnDragEnd(InputState state) + protected override bool OnDragEnd(DragEndEvent e) { - nativeDragPosition = state.Mouse.NativeState.Position; - var handled = draggedItem != null || base.OnDragEnd(state); + nativeDragPosition = e.ScreenSpaceMousePosition; + var handled = draggedItem != null || base.OnDragEnd(e); draggedItem = null; return handled; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 886b5fb95b..b32fd265cb 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -47,7 +47,6 @@ namespace osu.Game.Overlays private PlaylistOverlay playlist; private BeatmapManager beatmaps; - private LocalisationEngine localisation; private List beatmapSets; private BeatmapSetInfo currentSet; @@ -67,11 +66,10 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) + private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours) { this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; - this.localisation = localisation; Children = new Drawable[] { @@ -198,10 +196,16 @@ namespace osu.Game.Overlays playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } + private ScheduledDelegate seekDelegate; + private void attemptSeek(double progress) { - if (!beatmap.Disabled) - current?.Track.Seek(progress); + seekDelegate?.Cancel(); + seekDelegate = Schedule(() => + { + if (!beatmap.Disabled) + current?.Track.Seek(progress); + }); } private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index) @@ -351,17 +355,14 @@ namespace osu.Game.Overlays { 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); + title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)); + artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)); } }); @@ -462,20 +463,14 @@ namespace osu.Game.Overlays private class DragContainer : Container { - private Vector2 dragStart; - - protected override bool OnDragStart(InputState state) + protected override bool OnDragStart(DragStartEvent e) { - base.OnDragStart(state); - dragStart = state.Mouse.Position; return true; } - protected override bool OnDrag(InputState state) + protected override bool OnDrag(DragEvent e) { - if (base.OnDrag(state)) return true; - - Vector2 change = state.Mouse.Position - dragStart; + Vector2 change = e.MousePosition - e.MouseDownPosition; // 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; @@ -484,10 +479,10 @@ namespace osu.Game.Overlays return true; } - protected override bool OnDragEnd(InputState state) + protected override bool OnDragEnd(DragEndEvent e) { this.MoveTo(Vector2.Zero, 800, Easing.OutElastic); - return base.OnDragEnd(state); + return base.OnDragEnd(e); } } } diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 6798ae2bb2..aa2b248bc4 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Notifications @@ -118,19 +118,19 @@ namespace osu.Game.Overlays.Notifications }); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { closeButton.FadeIn(75); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { closeButton.FadeOut(75); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (Activated?.Invoke() ?? true) Close(); @@ -185,16 +185,16 @@ namespace osu.Game.Overlays.Notifications hoverColour = colours.Yellow; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { this.FadeColour(hoverColour, 200); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { this.FadeColour(OsuColour.Gray(0.2f), 200); - base.OnHoverLost(state); + base.OnHoverLost(e); } } diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 041ceab365..97c6554908 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -25,9 +25,6 @@ namespace osu.Game.Overlays { 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; diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs index 33baaf1fff..6a87db4211 100644 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -153,13 +153,13 @@ namespace osu.Game.Overlays.Profile.Header this.hoverLostAction = hoverLostAction; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { hoverAction(); return true; } - protected override void OnHoverLost(InputState state) => hoverLostAction(); + protected override void OnHoverLost(HoverLostEvent e) => hoverLostAction(); } private class DrawableBadge : Container, IHasTooltip diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs index a059792796..bfb01ce1c8 100644 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = 75, + Height = 60, Y = -secondary_textsize, Alpha = 0, } @@ -137,25 +137,25 @@ namespace osu.Game.Overlays.Profile.Header relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { if (ranks?.Length > 1) { - graph.UpdateBallPosition(state.Mouse.Position.X); + graph.UpdateBallPosition(e.MousePosition.X); graph.ShowBall(); } - return base.OnHover(state); + return base.OnHover(e); } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { if (ranks?.Length > 1) - graph.UpdateBallPosition(state.Mouse.Position.X); + graph.UpdateBallPosition(e.MousePosition.X); - return base.OnMouseMove(state); + return base.OnMouseMove(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (ranks?.Length > 1) { @@ -163,7 +163,7 @@ namespace osu.Game.Overlays.Profile.Header updateRankTexts(); } - base.OnHoverLost(state); + base.OnHoverLost(e); } private class RankChartLineGraph : LineGraph diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 9d09836d25..4839348e0e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -386,10 +386,13 @@ namespace osu.Game.Overlays.Profile infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic); } - infoTextLeft.NewLine(); - infoTextLeft.AddText("Last seen ", lightText); - infoTextLeft.AddText(new DrawableDate(user.LastVisit), boldItalic); - infoTextLeft.NewParagraph(); + if (user.LastVisit.HasValue) + { + infoTextLeft.NewLine(); + infoTextLeft.AddText("Last seen ", lightText); + infoTextLeft.AddText(new DrawableDate(user.LastVisit.Value), boldItalic); + infoTextLeft.NewParagraph(); + } if (user.PlayStyle?.Length > 0) { diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 1a1f13933d..4278598bf5 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Sections public string TooltipText { get; } [BackgroundDependencyLoader(true)] - private void load(LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) + private void load(BeatmapSetOverlay beatmapSetOverlay) { Action = () => { @@ -46,16 +46,14 @@ namespace osu.Game.Overlays.Profile.Sections { new OsuSpriteText { - Current = locale.GetUnicodePreference( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] " - ), + Text = new LocalisedString(($"{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), + Text = new LocalisedString((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/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index 3a4bfc6804..165299e8c0 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -6,7 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; @@ -105,20 +105,20 @@ namespace osu.Game.Overlays.Profile.Sections coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; } - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { background.FadeIn(fade_duration, Easing.OutQuint); underscoreLine.FadeOut(fade_duration, Easing.OutQuint); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { background.FadeOut(fade_duration, Easing.OutQuint); underscoreLine.FadeIn(fade_duration, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 38bc419838..788041205b 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -8,7 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu }; } - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; private class CountSection : Container { diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index f9e4a983bb..df249b0b88 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -5,7 +5,7 @@ using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.SearchableList scrollContainer.Padding = new MarginPadding { Top = Header.Height + Filter.Height }; } - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { GetContainingInputManager().ChangeFocus(Filter.Search); } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 0fe41327db..da96c6ef30 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { new SettingsSlider { - LabelText = "Audio Offset", + LabelText = "Audio offset", Bindable = config.GetBindable(OsuSetting.AudioOffset), KeyboardStep = 1f }, diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index 369c751448..fa4a714ba3 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Children = new Drawable[] { new SettingsSlider { LabelText = "Master", Bindable = audio.Volume, KeyboardStep = 0.01f }, - new SettingsSlider { LabelText = "Master (Window Inactive)", Bindable = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.01f }, + new SettingsSlider { LabelText = "Master (window inactive)", Bindable = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.01f }, new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.01f }, new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.01f }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs deleted file mode 100644 index 0e661aeba6..0000000000 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs +++ /dev/null @@ -1,26 +0,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 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/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index 8add0b01ec..f565ff8556 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -21,7 +21,6 @@ namespace osu.Game.Overlays.Settings.Sections { new GeneralSettings(), new SongSelectSettings(), - new ScrollingSettings(), new ModsSettings(), }; } @@ -29,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - foreach(Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) + foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) { SettingsSubsection section = ruleset.CreateSettings(); if (section != null) diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index c7f98f4107..11a3d36779 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -16,7 +16,7 @@ using System.ComponentModel; using osu.Game.Graphics; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; using Container = osu.Framework.Graphics.Containers.Container; @@ -175,12 +175,12 @@ namespace osu.Game.Overlays.Settings.Sections.General public override bool AcceptsFocus => true; - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { if (form != null) GetContainingInputManager().ChangeFocus(form); - base.OnFocus(state); + base.OnFocus(e); } private class LoginForm : FillFlowContainer @@ -244,9 +244,9 @@ namespace osu.Game.Overlays.Settings.Sections.General public override bool AcceptsFocus => true; - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4a8164c6df..254de6c6f7 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -35,6 +35,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics letterboxing = config.GetBindable(FrameworkSetting.Letterboxing); sizeFullscreen = config.GetBindable(FrameworkSetting.SizeFullscreen); + Container resolutionSettingsContainer; + Children = new Drawable[] { windowModeDropdown = new SettingsEnumDropdown @@ -42,12 +44,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Screen mode", Bindable = config.GetBindable(FrameworkSetting.WindowMode), }, - resolutionDropdown = new SettingsDropdown + resolutionSettingsContainer = new Container { - LabelText = "Resolution", - ShowsDefaultIndicator = false, - Items = getResolutions(), - Bindable = sizeFullscreen + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y }, new SettingsCheckbox { @@ -81,16 +81,29 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, }; - windowModeDropdown.Bindable.BindValueChanged(windowMode => + var resolutions = getResolutions(); + + if (resolutions.Count > 1) { - if (windowMode == WindowMode.Fullscreen) + resolutionSettingsContainer.Child = resolutionDropdown = new SettingsDropdown { - resolutionDropdown.Show(); - sizeFullscreen.TriggerChange(); - } - else - resolutionDropdown.Hide(); - }, true); + LabelText = "Resolution", + ShowsDefaultIndicator = false, + Items = resolutions, + Bindable = sizeFullscreen + }; + + windowModeDropdown.Bindable.BindValueChanged(windowMode => + { + if (windowMode == WindowMode.Fullscreen) + { + resolutionDropdown.Show(); + sizeFullscreen.TriggerChange(); + } + else + resolutionDropdown.Hide(); + }, true); + } letterboxing.BindValueChanged(isVisible => { @@ -102,7 +115,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, true); } - private IEnumerable> getResolutions() + private IReadOnlyList> getResolutions() { var resolutions = new KeyValuePair("Default", new Size(9999, 9999)).Yield(); @@ -112,8 +125,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics .OrderByDescending(r => r.Width) .ThenByDescending(r => r.Height) .Select(res => new KeyValuePair($"{res.Width}x{res.Height}", new Size(res.Width, res.Height))) - .Distinct()).ToList(); - return resolutions; + .Distinct()); + return resolutions.ToList(); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs index 456d1c9a2f..51b0296ca4 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { new SettingsButton { - Text = "Key Configuration", + 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 71ab4d3782..7b9b7bf57f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -26,12 +26,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input { new SettingsCheckbox { - LabelText = "Raw Input", + LabelText = "Raw input", Bindable = rawInputToggle }, sensitivity = new SensitivitySetting { - LabelText = "Cursor Sensitivity", + LabelText = "Cursor sensitivity", Bindable = config.GetBindable(FrameworkSetting.CursorSensitivity) }, new SettingsCheckbox @@ -124,18 +124,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input private bool isDragging; - protected override bool OnDragStart(InputState state) + protected override bool OnDragStart(DragStartEvent e) { isDragging = true; - return base.OnDragStart(state); + return base.OnDragStart(e); } - protected override bool OnDragEnd(InputState state) + protected override bool OnDragEnd(DragEndEvent e) { isDragging = false; Current.TriggerChange(); - return base.OnDragEnd(state); + return base.OnDragEnd(e); } 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/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 57e9a528d2..835ec808bc 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -13,57 +13,59 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class GeneralSettings : SettingsSubsection { - private TriangleButton importButton; - private TriangleButton deleteButton; + protected override string Header => "General"; + + private TriangleButton importBeatmapsButton; + private TriangleButton importSkinsButton; + private TriangleButton deleteSkinsButton; + private TriangleButton deleteBeatmapsButton; private TriangleButton restoreButton; private TriangleButton undeleteButton; - protected override string Header => "General"; - [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay) { Children = new Drawable[] { - importButton = new SettingsButton + importBeatmapsButton = new SettingsButton { Text = "Import beatmaps from stable", Action = () => { - importButton.Enabled.Value = false; - beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true)); + importBeatmapsButton.Enabled.Value = false; + beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }, - deleteButton = new DangerousSettingsButton + deleteBeatmapsButton = 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)); + deleteBeatmapsButton.Enabled.Value = false; + Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); })); } }, - importButton = new SettingsButton + importSkinsButton = new SettingsButton { Text = "Import skins from stable", Action = () => { - importButton.Enabled.Value = false; - skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true)); + importSkinsButton.Enabled.Value = false; + skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); } }, - deleteButton = new DangerousSettingsButton + deleteSkinsButton = new DangerousSettingsButton { Text = "Delete ALL skins", Action = () => { dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => { - deleteButton.Enabled.Value = false; - Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + deleteSkinsButton.Enabled.Value = false; + Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); })); } }, diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 0f8d3aa2ac..4f947cd812 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -12,8 +12,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; @@ -168,25 +167,25 @@ namespace osu.Game.Overlays.Settings public string TooltipText => "Revert to default"; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + protected override bool OnMouseUp(MouseUpEvent e) => true; - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (bindable != null && !bindable.Disabled) bindable.SetDefault(); return true; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { hovering = true; UpdateState(); return false; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { hovering = false; UpdateState(); diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index fdda5b870e..862011b6e2 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -9,7 +9,7 @@ using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Overlays.Toolbar; @@ -55,25 +55,25 @@ namespace osu.Game.Overlays.Settings private ScheduledDelegate expandEvent; private ExpandedState state; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { queueExpandIfHovering(); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { expandEvent?.Cancel(); lastHoveredButton = null; State = ExpandedState.Contracted; - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { queueExpandIfHovering(); - return base.OnMouseMove(state); + return base.OnMouseMove(e); } private class SidebarScrollContainer : ScrollContainer diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index 28e2b773ec..b6d1cf609e 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -109,22 +109,22 @@ namespace osu.Game.Overlays.Settings selectionIndicator.Colour = colours.Yellow; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { Action?.Invoke(section); - return base.OnClick(state); + return base.OnClick(e); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { Background.FadeTo(0.4f, 200); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { Background.FadeTo(0, 200); - base.OnHoverLost(state); + base.OnHoverLost(e); } } } diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 83e121e998..c971ab5005 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -11,7 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -177,10 +177,10 @@ namespace osu.Game.Overlays public override bool AcceptsFocus => true; - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { GetContainingInputManager().ChangeFocus(searchTextBox); - base.OnFocus(state); + base.OnFocus(e); } protected override void UpdateAfterChildren() diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs index b0455f7edd..cfee639d53 100644 --- a/osu.Game/Overlays/Social/SocialPanel.cs +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -6,7 +6,7 @@ using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Users; namespace osu.Game.Overlays.Social @@ -35,20 +35,20 @@ namespace osu.Game.Overlays.Social Colour = Color4.Black.Opacity(0.3f), }; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { Content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); Content.MoveToY(-4, hover_transition_time, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { Content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); Content.MoveToY(0, hover_transition_time, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index cdc3bc2b51..611b42383e 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -11,7 +11,7 @@ using OpenTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Toolbar { @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Toolbar private readonly ToolbarUserArea userArea; - protected override bool BlockPassThroughMouse => false; + protected override bool BlockPositionalInput => false; private const double transition_time = 500; @@ -121,14 +121,14 @@ namespace osu.Game.Overlays.Toolbar }; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { solidBackground.FadeTo(alpha_hovering, transition_time, Easing.OutQuint); gradientBackground.FadeIn(transition_time, Easing.OutQuint); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { solidBackground.FadeTo(alpha_normal, transition_time, Easing.OutQuint); gradientBackground.FadeOut(transition_time, Easing.OutQuint); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 74af5d7e9c..5cb26974e6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -11,8 +11,7 @@ using osu.Game.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -145,22 +144,22 @@ namespace osu.Game.Overlays.Toolbar }; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { HoverBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); - return base.OnClick(state); + return base.OnClick(e); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { HoverBackground.FadeOut(200); tooltipContainer.FadeOut(100); diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index f2744ae83f..fa35e53531 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -11,8 +11,7 @@ using OpenTK.Input; using OpenTK.Graphics; using osu.Framework.Configuration; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Rulesets; namespace osu.Game.Overlays.Toolbar @@ -86,13 +85,13 @@ namespace osu.Game.Overlays.Toolbar ruleset.BindTo(parentRuleset); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - base.OnKeyDown(state, args); + base.OnKeyDown(e); - if (state.Keyboard.ControlPressed && !args.Repeat && args.Key >= Key.Number1 && args.Key <= Key.Number9) + if (e.ControlPressed && !e.Repeat && e.Key >= Key.Number1 && e.Key <= Key.Number9) { - int requested = args.Key - Key.Number1; + int requested = e.Key - Key.Number1; RulesetInfo found = rulesets.AvailableRulesets.Skip(requested).FirstOrDefault(); if (found != null) @@ -103,8 +102,10 @@ namespace osu.Game.Overlays.Toolbar return false; } - public override bool HandleKeyboardInput => !ruleset.Disabled && base.HandleKeyboardInput; - public override bool HandleMouseInput => !ruleset.Disabled && base.HandleMouseInput; + public override bool HandleNonPositionalInput => !ruleset.Disabled && base.HandleNonPositionalInput; + public override bool HandlePositionalInput => !ruleset.Disabled && base.HandlePositionalInput; + + public override bool PropagatePositionalInputSubTree => !ruleset.Disabled && base.PropagatePositionalInputSubTree; private void disabledChanged(bool isDisabled) => this.FadeColour(isDisabled ? Color4.Gray : Color4.White, 300); diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index ea077ff645..c106446fe0 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -77,9 +77,11 @@ namespace osu.Game.Overlays public void ShowUser(User user, bool fetchOnline = true) { + if (user == User.SYSTEM_USER) return; + Show(); - if (user.Id == Header?.User.Id) + if (user.Id == Header?.User?.Id) return; userReq?.Cancel(); diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 47169d7a7b..a099a10096 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; @@ -63,18 +63,18 @@ namespace osu.Game.Overlays.Volume Current.TriggerChange(); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { this.TransformTo("BorderColour", hoveredColour, 500, Easing.OutQuint); return false; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { this.TransformTo("BorderColour", unhoveredColour, 500, Easing.OutQuint); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { Current.Value = !Current.Value; return true; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index a6c98aa97e..86be652c8c 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -239,21 +239,21 @@ namespace osu.Game.Overlays.Volume adjustAccumulator = 0; } - protected override bool OnScroll(InputState state) + protected override bool OnScroll(ScrollEvent e) { - adjust(state.Mouse.ScrollDelta.Y, state.Mouse.HasPreciseScroll); + adjust(e.ScrollDelta.Y, e.IsPrecise); return true; } private const float transition_length = 500; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { this.ScaleTo(1.04f, transition_length, Easing.OutExpo); return false; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { this.ScaleTo(1f, transition_length, Easing.OutExpo); } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 4dcdd23768..d45d097a09 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Input.Bindings; @@ -28,7 +28,7 @@ namespace osu.Game.Overlays private VolumeMeter volumeMeterMusic; private MuteButton muteButton; - protected override bool BlockPassThroughMouse => false; + protected override bool BlockPositionalInput => false; private readonly BindableDouble muteAdjustment = new BindableDouble(); @@ -143,23 +143,23 @@ namespace osu.Game.Overlays this.FadeOut(100); } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { // keep the scheduled event correctly timed as long as we have movement. schedulePopOut(); - return base.OnMouseMove(state); + return base.OnMouseMove(e); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { schedulePopOut(); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { schedulePopOut(); - base.OnHoverLost(state); + base.OnHoverLost(e); } private void schedulePopOut() diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 97f52d88f7..c5a4953c5e 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays { protected readonly WaveContainer Waves; - protected override bool BlockPassThroughKeyboard => true; + protected override bool BlockNonPositionalInput => true; protected override Container Content => Waves; protected WaveOverlayContainer() diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs new file mode 100644 index 0000000000..bc54c907ab --- /dev/null +++ b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs @@ -0,0 +1,106 @@ +// 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.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class EditRulesetContainer : CompositeDrawable + { + /// + /// The contained by this . + /// + public abstract Playfield Playfield { get; } + + internal EditRulesetContainer() + { + RelativeSizeAxes = Axes.Both; + } + + /// + /// Adds a to the and displays a visual representation of it. + /// + /// The to add. + /// The visual representation of . + internal abstract DrawableHitObject Add(HitObject hitObject); + + /// + /// Removes a from the and the display. + /// + /// The to remove. + /// The visual representation of the removed . + internal abstract DrawableHitObject Remove(HitObject hitObject); + } + + public class EditRulesetContainer : EditRulesetContainer + where TObject : HitObject + { + public override Playfield Playfield => rulesetContainer.Playfield; + + private Ruleset ruleset => rulesetContainer.Ruleset; + private Beatmap beatmap => rulesetContainer.Beatmap; + + private readonly RulesetContainer rulesetContainer; + + public EditRulesetContainer(RulesetContainer rulesetContainer) + { + this.rulesetContainer = rulesetContainer; + + InternalChild = rulesetContainer; + + Playfield.DisplayJudgements.Value = false; + } + + internal override DrawableHitObject Add(HitObject hitObject) + { + var tObject = (TObject)hitObject; + + // Add to beatmap, preserving sorting order + var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); + beatmap.HitObjects.Insert(insertionIndex + 1, tObject); + + // Process object + var processor = ruleset.CreateBeatmapProcessor(beatmap); + + processor.PreProcess(); + tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + processor.PostProcess(); + + // Add visual representation + var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); + + rulesetContainer.Playfield.Add(drawableObject); + rulesetContainer.Playfield.PostProcess(); + + return drawableObject; + } + + internal override DrawableHitObject Remove(HitObject hitObject) + { + var tObject = (TObject)hitObject; + + // Remove from beatmap + beatmap.HitObjects.Remove(tObject); + + // Process the beatmap + var processor = ruleset.CreateBeatmapProcessor(beatmap); + + processor.PreProcess(); + processor.PostProcess(); + + // Remove visual representation + var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); + + rulesetContainer.Playfield.Remove(drawableObject); + rulesetContainer.Playfield.PostProcess(); + + return drawableObject; + } + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a3253250f2..932cfe5789 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -13,30 +13,33 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects; 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; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Edit { public abstract class HitObjectComposer : CompositeDrawable { - private readonly Ruleset ruleset; - public IEnumerable HitObjects => rulesetContainer.Playfield.AllHitObjects; - protected ICompositionTool CurrentTool { get; private set; } + protected readonly Ruleset Ruleset; + + protected readonly IBindable Beatmap = new Bindable(); + protected IRulesetConfigManager Config { get; private set; } private readonly List layerContainers = new List(); - private readonly IBindable beatmap = new Bindable(); - private RulesetContainer rulesetContainer; + private EditRulesetContainer rulesetContainer; - protected HitObjectComposer(Ruleset ruleset) + private BlueprintContainer blueprintContainer; + + internal HitObjectComposer(Ruleset ruleset) { - this.ruleset = ruleset; + Ruleset = ruleset; RelativeSizeAxes = Axes.Both; } @@ -44,11 +47,11 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(IBindableBeatmap beatmap, IFrameBasedClock framedClock) { - this.beatmap.BindTo(beatmap); + Beatmap.BindTo(beatmap); try { - rulesetContainer = CreateRulesetContainer(ruleset, beatmap.Value); + rulesetContainer = CreateRulesetContainer(); rulesetContainer.Clock = framedClock; } catch (Exception e) @@ -57,14 +60,11 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = new BorderLayer - { - RelativeSizeAxes = Axes.Both, - Child = CreateLayerContainer() - }; + var layerBelowRuleset = CreateLayerContainer(); + layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; var layerAboveRuleset = CreateLayerContainer(); - layerAboveRuleset.Child = new HitObjectMaskLayer(); + layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); layerContainers.Add(layerAboveRuleset); @@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Edit }; toolboxCollection.Items = - CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) - .Prepend(new RadioButton("Select", () => setCompositionTool(null))) + CompositionTools.Select(t => new RadioButton(t.Name, () => blueprintContainer.CurrentTool = t)) + .Prepend(new RadioButton("Select", () => blueprintContainer.CurrentTool = null)) .ToList(); toolboxCollection.Items[0].Select(); @@ -119,18 +119,11 @@ namespace osu.Game.Rulesets.Edit var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(this); - Config = dependencies.Get().GetConfigFor(ruleset); + Config = dependencies.Get().GetConfigFor(Ruleset); return dependencies; } - protected override void LoadComplete() - { - base.LoadComplete(); - - rulesetContainer.Playfield.DisplayJudgements.Value = false; - } - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -144,27 +137,47 @@ namespace osu.Game.Rulesets.Edit }); } - private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; + /// + /// Adds a to the and visualises it. + /// + /// The to add. + public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(rulesetContainer.Add(hitObject)); - protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap); + public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(rulesetContainer.Remove(hitObject)); - protected abstract IReadOnlyList CompositionTools { get; } + internal abstract EditRulesetContainer CreateRulesetContainer(); + + protected abstract IReadOnlyList CompositionTools { get; } /// - /// Creates a for a specific . + /// Creates a for a specific . /// /// The to create the overlay for. - public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; + public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; /// - /// Creates a which outlines s + /// Creates a which outlines s /// and handles hitobject pattern adjustments. /// - public virtual MaskSelection CreateMaskSelection() => new MaskSelection(); + public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); /// /// Creates a which provides a layer above or below the . /// - protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both }; + protected virtual Container CreateLayerContainer() => new Container { RelativeSizeAxes = Axes.Both }; + } + + public abstract class HitObjectComposer : HitObjectComposer + where TObject : HitObject + { + protected HitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + internal override EditRulesetContainer CreateRulesetContainer() + => new EditRulesetContainer(CreateRulesetContainer(Ruleset, Beatmap.Value)); + + protected abstract RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs new file mode 100644 index 0000000000..b726b683ea --- /dev/null +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -0,0 +1,96 @@ +// 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.Input; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose; +using OpenTK; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// A blueprint which governs the creation of a new to actualisation. + /// + public abstract class PlacementBlueprint : CompositeDrawable, IRequireHighFrequencyMousePosition + { + /// + /// The that is being placed. + /// + protected readonly HitObject HitObject; + + protected IClock EditorClock { get; private set; } + + private readonly IBindable beatmap = new Bindable(); + + [Resolved] + private IPlacementHandler placementHandler { get; set; } + + protected PlacementBlueprint(HitObject hitObject) + { + HitObject = hitObject; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap, IAdjustableClock clock) + { + this.beatmap.BindTo(beatmap); + + EditorClock = clock; + + ApplyDefaultsToHitObject(); + } + + private bool placementBegun; + + /// + /// Signals that the placement of has started. + /// + protected void BeginPlacement() + { + placementHandler.BeginPlacement(HitObject); + placementBegun = true; + } + + /// + /// Signals that the placement of has finished. + /// This will destroy this , and add the to the . + /// + protected void EndPlacement() + { + if (!placementBegun) + BeginPlacement(); + placementHandler.EndPlacement(HitObject); + } + + /// + /// Invokes , refreshing and parameters for the . + /// + protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; + + protected override bool Handle(UIEvent e) + { + base.Handle(e); + + switch (e) + { + case ScrollEvent _: + return false; + case MouseEvent _: + return true; + default: + return false; + } + } + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs similarity index 56% rename from osu.Game/Rulesets/Edit/HitObjectMask.cs rename to osu.Game/Rulesets/Edit/SelectionBlueprint.cs index ada026b32f..db35d47b2b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -3,9 +3,10 @@ using System; using osu.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input.EventArgs; +using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects.Drawables; @@ -14,44 +15,46 @@ using OpenTK; namespace osu.Game.Rulesets.Edit { /// - /// A mask placed above a adding editing functionality. + /// A blueprint placed above a adding editing functionality. /// - public class HitObjectMask : CompositeDrawable, IStateful + public abstract class SelectionBlueprint : CompositeDrawable, IStateful { /// - /// Invoked when this has been selected. + /// Invoked when this has been selected. /// - public event Action Selected; + public event Action Selected; /// - /// Invoked when this has been deselected. + /// Invoked when this has been deselected. /// - public event Action Deselected; + public event Action Deselected; /// - /// Invoked when this has requested selection. + /// Invoked when this has requested selection. /// Will fire even if already selected. Does not actually perform selection. /// - public event Action SelectionRequested; + public event Action SelectionRequested; /// - /// Invoked when this has requested drag. + /// Invoked when this has requested drag. /// - public event Action DragRequested; + public event Action DragRequested; /// - /// The which this applies to. + /// 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 HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - public HitObjectMask(DrawableHitObject hitObject) + protected SelectionBlueprint(DrawableHitObject hitObject) { HitObject = hitObject; + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; Alpha = 0; } @@ -83,60 +86,64 @@ namespace osu.Game.Rulesets.Edit } /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; /// - /// Deselects this , causing it to become invisible. + /// Deselects this , causing it to become invisible. /// public void Deselect() => State = SelectionState.NotSelected; public bool IsSelected => State == SelectionState.Selected; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObject.ReceivePositionalInputAt(screenSpacePos); + private bool selectionRequested; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { selectionRequested = false; if (State == SelectionState.NotSelected) { - SelectionRequested?.Invoke(this, state); + SelectionRequested?.Invoke(this, e.CurrentState); selectionRequested = true; } return IsSelected; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (State == SelectionState.Selected && !selectionRequested) { selectionRequested = true; - SelectionRequested?.Invoke(this, state); + SelectionRequested?.Invoke(this, e.CurrentState); return true; } - return base.OnClick(state); + return base.OnClick(e); } - protected override bool OnDragStart(InputState state) => true; + protected override bool OnDragStart(DragStartEvent e) => true; - protected override bool OnDrag(InputState state) + protected override bool OnDrag(DragEvent e) { - DragRequested?.Invoke(this, state); + DragRequested?.Invoke(e); return true; } - /// - /// The screen-space point that causes this to be selected. - /// - public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; + public abstract void AdjustPosition(DragEvent dragEvent); /// - /// The screen-space quad that outlines this for selections. + /// The screen-space point that causes this to be selected. /// - public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; + public virtual Vector2 SelectionPoint => HitObject.ScreenSpaceDrawQuad.Centre; + + /// + /// The screen-space quad that outlines this for selections. + /// + public virtual Quad SelectionQuad => HitObject.ScreenSpaceDrawQuad; } } diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs index 78ad236e74..1cb3c4c451 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs @@ -1,23 +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; - namespace osu.Game.Rulesets.Edit.Tools { - public class HitObjectCompositionTool : ICompositionTool - where T : HitObject + public abstract class HitObjectCompositionTool { - public string Name { get; } + public readonly string Name; - public HitObjectCompositionTool() - : this(typeof(T).Name) - { - } - - public HitObjectCompositionTool(string name) + protected HitObjectCompositionTool(string name) { Name = name; } + + public abstract PlacementBlueprint CreatePlacementBlueprint(); } } diff --git a/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs b/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs deleted file mode 100644 index 7107b6c763..0000000000 --- a/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs +++ /dev/null @@ -1,13 +0,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.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 65b2ef75c4..e8e775a20d 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Judgements /// /// A drawable object which visualises the hit result of a . /// - public class DrawableJudgement : Container + public class DrawableJudgement : CompositeDrawable { private const float judgement_size = 80; @@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Judgements public readonly DrawableHitObject JudgedObject; + protected Container JudgementBody; protected SpriteText JudgementText; /// @@ -49,14 +50,20 @@ namespace osu.Game.Rulesets.Judgements { this.colours = colours; - Child = new SkinnableDrawable($"Play/{Result.Type}", _ => JudgementText = new OsuSpriteText + InternalChild = JudgementBody = new Container { - Text = Result.Type.GetDescription().ToUpperInvariant(), - Font = @"Venera", - Colour = judgementColour(Result.Type), - Scale = new Vector2(0.85f, 1), - TextSize = 12 - }, restrictSize: false); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Child = new SkinnableDrawable($"Play/{Result.Type}", _ => JudgementText = new OsuSpriteText + { + Text = Result.Type.GetDescription().ToUpperInvariant(), + Font = @"Venera", + Colour = judgementColour(Result.Type), + Scale = new Vector2(0.85f, 1), + TextSize = 12 + }, restrictSize: false) + }; } protected override void LoadComplete() @@ -70,17 +77,17 @@ namespace osu.Game.Rulesets.Judgements case HitResult.None: break; case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); + JudgementBody.ScaleTo(1.6f); + JudgementBody.ScaleTo(1, 100, Easing.In); - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - this.RotateTo(40, 800, Easing.InQuint); + JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + JudgementBody.RotateTo(40, 800, Easing.InQuint); this.Delay(600).FadeOut(200); break; default: - this.ScaleTo(0.9f); - this.ScaleTo(1, 500, Easing.OutElastic); + JudgementBody.ScaleTo(0.9f); + JudgementBody.ScaleTo(1, 500, Easing.OutElastic); this.Delay(100).FadeOut(400); break; @@ -103,9 +110,9 @@ namespace osu.Game.Rulesets.Judgements return colours.Yellow; case HitResult.Miss: return colours.Red; + default: + return Color4.White; } - - return Color4.White; } } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs new file mode 100644 index 0000000000..1eb74ca76a --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs @@ -0,0 +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 + /// after conversion and post-processing has completed. + /// + public interface IApplicableToBeatmap : IApplicableMod + where TObject : HitObject + { + /// + /// Applies this to a . + /// + /// The to apply to. + void ApplyToBeatmap(Beatmap beatmap); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index d871cdd322..fca04ca513 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Mods { /// /// Applies this to a list of s. + /// This will only be invoked with top-level s. Access if adjusting nested objects is necessary. /// /// The list of s to apply to. void ApplyToDrawableHitObjects(IEnumerable drawables); diff --git a/osu.Game/Rulesets/Objects/BezierApproximator.cs b/osu.Game/Rulesets/Objects/BezierApproximator.cs deleted file mode 100644 index 6094934510..0000000000 --- a/osu.Game/Rulesets/Objects/BezierApproximator.cs +++ /dev/null @@ -1,150 +0,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; - -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 deleted file mode 100644 index fabb55e8f6..0000000000 --- a/osu.Game/Rulesets/Objects/CatmullApproximator.cs +++ /dev/null @@ -1,70 +0,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; - -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 deleted file mode 100644 index 7fb8d8a40e..0000000000 --- a/osu.Game/Rulesets/Objects/CircularArcApproximator.cs +++ /dev/null @@ -1,99 +0,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.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/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a274d9b12f..e9e9d93ed5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.TypeExtensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -78,8 +77,8 @@ namespace osu.Game.Rulesets.Objects.Drawables private bool judgementOccurred; public bool Interactive = true; - public override bool HandleKeyboardInput => Interactive; - public override bool HandleMouseInput => Interactive; + public override bool HandleNonPositionalInput => Interactive; + public override bool HandlePositionalInput => Interactive; public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -145,7 +144,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public event Action ApplyCustomUpdateState; /// - /// Plays all the hitsounds for this . + /// Plays all the hit sounds for this . /// public void PlaySamples() => Samples?.Play(); @@ -167,13 +166,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) - { - if (!AllJudged) - return false; - - return base.UpdateSubTreeMasking(source, maskingBounds); - } + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => AllJudged && base.ComputeIsMaskedAway(maskingBounds); protected override void UpdateAfterChildren() { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index beb9620f78..67a3db7a00 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -1,11 +1,8 @@ // 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 osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -58,10 +55,10 @@ namespace osu.Game.Rulesets.Objects /// public HitWindows HitWindows { get; set; } - private readonly Lazy> nestedHitObjects = new Lazy>(() => new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime))); + private readonly List nestedHitObjects = new List(); [JsonIgnore] - public IReadOnlyList NestedHitObjects => nestedHitObjects.Value; + public IReadOnlyList NestedHitObjects => nestedHitObjects; /// /// Applies default values to this HitObject. @@ -72,18 +69,16 @@ namespace osu.Game.Rulesets.Objects { ApplyDefaultsToSelf(controlPointInfo, difficulty); - if (nestedHitObjects.IsValueCreated) - nestedHitObjects.Value.Clear(); + nestedHitObjects.Clear(); CreateNestedHitObjects(); - if (nestedHitObjects.IsValueCreated) + nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); + + foreach (var h in nestedHitObjects) { - nestedHitObjects.Value.ForEach(h => - { - h.HitWindows = HitWindows; - h.ApplyDefaults(controlPointInfo, difficulty); - }); + h.HitWindows = HitWindows; + h.ApplyDefaults(controlPointInfo, difficulty); } } @@ -104,7 +99,7 @@ namespace osu.Game.Rulesets.Objects { } - protected void AddNested(HitObject hitObject) => nestedHitObjects.Value.Add(hitObject); + protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject); /// /// Creates the that represents the scoring information for this . diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 802080aedb..c805c55ed1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, + PathType = pathType, + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 72168a4cd2..3cc695447e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -70,41 +70,51 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(ConvertHitObjectType.Slider)) { - CurveType curveType = CurveType.Catmull; + PathType pathType = PathType.Catmull; double length = 0; - var points = new List { Vector2.Zero }; - string[] pointsplit = split[5].Split('|'); - foreach (string t in pointsplit) + string[] pointSplit = split[5].Split('|'); + + int pointCount = 1; + foreach (var t in pointSplit) + if (t.Length > 1) + pointCount++; + + var points = new Vector2[pointCount]; + + int pointIndex = 1; + foreach (string t in pointSplit) { if (t.Length == 1) { switch (t) { case @"C": - curveType = CurveType.Catmull; + pathType = PathType.Catmull; break; case @"B": - curveType = CurveType.Bezier; + pathType = PathType.Bezier; break; case @"L": - curveType = CurveType.Linear; + pathType = PathType.Linear; break; case @"P": - curveType = CurveType.PerfectCurve; + pathType = PathType.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); + points[pointIndex++] = 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; + bool isLinear(Vector2[] 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.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) + pathType = PathType.Linear; int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); @@ -168,7 +178,10 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, points, length, curveType, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); + + // The samples are played when the slider ends, which is the last node + result.Samples = nodeSamples[nodeSamples.Count - 1]; } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { @@ -200,7 +213,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset; - result.Samples = convertSoundType(soundType, bankInfo); + + if (result.Samples.Count == 0) + result.Samples = convertSoundType(soundType, bankInfo); FirstObject = false; @@ -230,7 +245,7 @@ namespace osu.Game.Rulesets.Objects.Legacy stringAddBank = null; bankInfo.Normal = stringBank; - bankInfo.Add = stringAddBank; + bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; if (split.Length > 2) bankInfo.CustomSampleBank = int.Parse(split[2]); @@ -258,11 +273,11 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The slider control points. /// The slider length. - /// The slider curve type. + /// 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 samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples); /// /// Creates a legacy Spinner-type hit object. diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index ef1eecec3d..b3d9f3c40c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -20,13 +20,13 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// 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 SliderPath Path { get; } = null; + public Vector2[] ControlPoints { get; set; } + public PathType PathType { get; set; } public double Distance { get; set; } - public List> RepeatSamples { get; set; } + public List> NodeSamples { get; set; } public int RepeatCount { get; set; } public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 6f59965e18..90b7f3d554 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -26,15 +26,15 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { return new ConvertSlider { X = position.X, ControlPoints = controlPoints, Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, + PathType = pathType, + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index acd0de8688..bb41a147b0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -53,8 +53,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = Math.Max(0, length), - CurveType = curveType, - RepeatSamples = repeatSamples, + PathType = pathType, + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index e5904825c2..ae913b3bef 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -23,14 +23,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { return new ConvertSlider { ControlPoints = controlPoints, Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, + PathType = pathType, + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderPath.cs similarity index 69% rename from osu.Game/Rulesets/Objects/SliderCurve.cs rename to osu.Game/Rulesets/Objects/SliderPath.cs index 3932d8ed9d..423cd3b069 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -1,6 +1,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.Linq; using osu.Framework.MathUtils; @@ -9,43 +10,43 @@ using OpenTK; namespace osu.Game.Rulesets.Objects { - public class SliderCurve + public class SliderPath { public double Distance; - public List ControlPoints; + public Vector2[] ControlPoints = Array.Empty(); - public CurveType CurveType = CurveType.PerfectCurve; + public PathType PathType = PathType.PerfectCurve; public Vector2 Offset; private readonly List calculatedPath = new List(); private readonly List cumulativeLength = new List(); - private List calculateSubpath(List subControlPoints) + private List calculateSubpath(ReadOnlySpan subControlPoints) { - switch (CurveType) + switch (PathType) { - case CurveType.Linear: - return subControlPoints; - case CurveType.PerfectCurve: + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. - if (ControlPoints.Count != 3 || subControlPoints.Count != 3) + if (ControlPoints.Length != 3 || subControlPoints.Length != 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(); + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); // 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(); + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); } - return new BezierApproximator(subControlPoints).CreateBezier(); + return PathApproximator.ApproximateBezier(subControlPoints); } private void calculatePath() @@ -55,18 +56,23 @@ namespace osu.Game.Rulesets.Objects // 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) + + int start = 0; + int end = 0; + + for (int i = 0; i < ControlPoints.Length; ++i) { - subControlPoints.Add(ControlPoints[i]); - if (i == ControlPoints.Count - 1 || ControlPoints[i] == ControlPoints[i + 1]) + end++; + + if (i == ControlPoints.Length - 1 || ControlPoints[i] == ControlPoints[i + 1]) { - List subpath = calculateSubpath(subControlPoints); - foreach (Vector2 t in subpath) + ReadOnlySpan cpSpan = ControlPoints.AsSpan().Slice(start, end - start); + + foreach (Vector2 t in calculateSubpath(cpSpan)) if (calculatedPath.Count == 0 || calculatedPath.Last() != t) calculatedPath.Add(t); - subControlPoints.Clear(); + start = end; } } } @@ -83,7 +89,7 @@ namespace osu.Game.Rulesets.Objects Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; double d = diff.Length; - // Shorten slider curves that are too long compared to what's + // Shorten slider paths that are too long compared to what's // in the .osu file. if (Distance - l < d) { @@ -99,7 +105,7 @@ namespace osu.Game.Rulesets.Objects cumulativeLength.Add(l); } - // Lengthen slider curves that are too short compared to what's + // Lengthen slider paths that are too short compared to what's // in the .osu file. if (l < Distance && calculatedPath.Count > 1) { @@ -114,10 +120,33 @@ namespace osu.Game.Rulesets.Objects } } - public void Calculate() + private void calculateCumulativeLength() + { + 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; + + l += d; + cumulativeLength.Add(l); + } + + Distance = l; + } + + public void Calculate(bool updateDistance = false) { calculatePath(); - calculateCumulativeLengthAndTrimPath(); + + if (!updateDistance) + calculateCumulativeLengthAndTrimPath(); + else + calculateCumulativeLength(); } private int indexOfDistance(double d) @@ -158,15 +187,15 @@ namespace osu.Game.Rulesets.Objects } /// - /// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider) + /// Computes the slider path 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. + /// The list to be filled with the computed path. /// 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) + if (calculatedPath.Count == 0 && ControlPoints.Length > 0) Calculate(); double d0 = progressToDistance(p0); @@ -186,14 +215,14 @@ namespace osu.Game.Rulesets.Objects } /// - /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve) - /// to 1 (end of the curve). + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) + /// to 1 (end of the path). /// - /// Ranges from 0 (beginning of the curve) to 1 (end of the curve). + /// Ranges from 0 (beginning of the path) to 1 (end of the path). /// public Vector2 PositionAt(double progress) { - if (calculatedPath.Count == 0 && ControlPoints.Count > 0) + if (calculatedPath.Count == 0 && ControlPoints.Length > 0) Calculate(); double d = progressToDistance(progress); diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 54dcb26ae2..2a0d495e94 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -1,7 +1,6 @@ // 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 @@ -14,17 +13,17 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The curve. /// - SliderCurve Curve { get; } + SliderPath Path { get; } /// /// The control points that shape the curve. /// - List ControlPoints { get; } + Vector2[] ControlPoints { get; } /// /// The type of curve. /// - CurveType CurveType { get; } + PathType PathType { get; } } public static class HasCurveExtensions @@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types /// [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)); + => obj.Path.PositionAt(obj.ProgressAt(progress)); /// /// Computes the progress along the curve relative to how much of the has been completed. diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 7d918ff160..ea8784db47 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -17,9 +17,15 @@ namespace osu.Game.Rulesets.Objects.Types int RepeatCount { get; } /// - /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). + /// The samples to be played when each node of the is hit.
+ /// 0: The first node.
+ /// 1: The first repeat.
+ /// 2: The second repeat.
+ /// ...
+ /// n-1: The last repeat.
+ /// n: The last node. ///
- List> RepeatSamples { get; } + List> NodeSamples { get; } } public static class HasRepeatsExtensions diff --git a/osu.Game/Rulesets/Objects/Types/CurveType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs similarity index 91% rename from osu.Game/Rulesets/Objects/Types/CurveType.cs rename to osu.Game/Rulesets/Objects/Types/PathType.cs index 1cee6202b6..5156302fe1 100644 --- a/osu.Game/Rulesets/Objects/Types/CurveType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Objects.Types { - public enum CurveType + public enum PathType { Catmull, Bezier, diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index dfe7ff0195..02f528791a 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Users; @@ -32,6 +33,7 @@ namespace osu.Game.Rulesets.Scoring public User User; + [JsonIgnore] public Replay Replay; public BeatmapInfo Beatmap; diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index 69d25fcb67..091cb29a71 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; @@ -13,8 +14,6 @@ namespace osu.Game.Rulesets.Scoring { public class ScoreStore : DatabaseBackedStore, ICanAcceptFiles { - private readonly Storage storage; - private readonly BeatmapManager beatmaps; private readonly RulesetStore rulesets; @@ -25,9 +24,8 @@ namespace osu.Game.Rulesets.Scoring // 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) + public ScoreStore(DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory) { - this.storage = storage; this.beatmaps = beatmaps; this.rulesets = rulesets; @@ -49,8 +47,14 @@ namespace osu.Game.Rulesets.Scoring public Score ReadReplayFile(string replayFilename) { - using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename))) - return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(s); + if (File.Exists(replayFilename)) + { + using (var stream = File.OpenRead(replayFilename)) + return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream); + } + + Logger.Log($"Replay file {replayFilename} cannot be found", LoggingTarget.Information, LogLevel.Error); + return null; } } } diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index a5bd6bfde7..4988bac5ce 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -18,9 +18,14 @@ namespace osu.Game.Rulesets.Timing public double StartTime; /// - /// The multiplier which this provides. + /// The aggregate multiplier which this provides. /// - public double Multiplier => 1000 / TimingPoint.BeatLength * DifficultyPoint.SpeedMultiplier; + public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * 1000 / TimingPoint.BeatLength; + + /// + /// The velocity multiplier. + /// + public double Velocity = 1; /// /// The that provides the timing information for this . @@ -48,18 +53,6 @@ namespace osu.Game.Rulesets.Timing 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 af18d98561..261132c56b 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -14,6 +14,11 @@ namespace osu.Game.Rulesets.UI public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); + public HitObjectContainer() + { + RelativeSizeAxes = Axes.Both; + } + public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index e090a18eda..886eb3ac0e 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -9,17 +9,26 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using OpenTK; namespace osu.Game.Rulesets.UI { - public abstract class Playfield : ScalableContainer + public abstract class Playfield : CompositeDrawable { /// /// The contained in this Playfield. /// - public HitObjectContainer HitObjectContainer { get; private set; } + public HitObjectContainer HitObjectContainer => hitObjectContainerLazy.Value; + + private readonly Lazy hitObjectContainerLazy; + + /// + /// A function that converts gamefield coordinates to screen space. + /// + public Func GamefieldToScreenSpace => HitObjectContainer.ToScreenSpace; /// /// All the s contained in this and all . @@ -39,18 +48,13 @@ namespace osu.Game.Rulesets.UI public readonly BindableBool DisplayJudgements = new BindableBool(true); /// - /// A container for keeping track of DrawableHitObjects. + /// Creates a new . /// - /// 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) + protected Playfield() { RelativeSizeAxes = Axes.Both; + + hitObjectContainerLazy = new Lazy(CreateHitObjectContainer); } private WorkingBeatmap beatmap; @@ -59,11 +63,6 @@ namespace osu.Game.Rulesets.UI private void load(IBindableBeatmap beatmap) { this.beatmap = beatmap.Value; - - HitObjectContainer = CreateHitObjectContainer(); - HitObjectContainer.RelativeSizeAxes = Axes.Both; - - Add(HitObjectContainer); } /// @@ -94,10 +93,14 @@ namespace osu.Game.Rulesets.UI nestedPlayfields.Value.Add(otherPlayfield); } - /// - /// Creates the container that will be used to contain the s. - /// - protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); + protected override void LoadComplete() + { + base.LoadComplete(); + + // in the case a consumer forgets to add the HitObjectContainer, we will add it here. + if (HitObjectContainer.Parent == null) + AddInternal(HitObjectContainer); + } protected override void Update() { @@ -108,5 +111,10 @@ namespace osu.Game.Rulesets.UI if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); } + + /// + /// 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 a830803fb1..0abd358113 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -22,7 +22,6 @@ 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 { @@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.UI /// public readonly CursorContainer Cursor; - protected readonly Ruleset Ruleset; + public readonly Ruleset Ruleset; protected IRulesetConfigManager Config { get; private set; } @@ -239,6 +238,8 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager.RelativeSizeAxes = Axes.Both; + + applyBeatmapMods(Mods); } [BackgroundDependencyLoader] @@ -256,16 +257,29 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager.Add(Cursor); // Apply mods - applyMods(Mods, config); + applyRulesetMods(Mods, config); loadObjects(); } + /// + /// Applies the active mods to the Beatmap. + /// + /// + private void applyBeatmapMods(IEnumerable mods) + { + if (mods == null) + return; + + foreach (var mod in mods.OfType>()) + mod.ApplyToBeatmap(Beatmap); + } + /// /// Applies the active mods to this RulesetContainer. /// /// - private void applyMods(IEnumerable mods, OsuConfigManager config) + private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) { if (mods == null) return; @@ -291,17 +305,7 @@ namespace osu.Game.Rulesets.UI private void loadObjects() { foreach (TObject h in Beatmap.HitObjects) - { - var drawableObject = GetVisualRepresentation(h); - - if (drawableObject == null) - continue; - - drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); - drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); - - Playfield.Add(drawableObject); - } + AddRepresentation(h); Playfield.PostProcess(); @@ -309,32 +313,30 @@ namespace osu.Game.Rulesets.UI mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); } - protected override void Update() + /// + /// Creates and adds the visual representation of a to this . + /// + /// The to add the visual representation for. + internal void AddRepresentation(TObject hitObject) { - base.Update(); + var drawableObject = GetVisualRepresentation(hitObject); - Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea; + if (drawableObject == null) + return; + + drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); + drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); + + Playfield.Add(drawableObject); } - /// - /// 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 DrawableHitObject from a HitObject. /// /// The HitObject to make drawable. /// The DrawableHitObject. - protected abstract DrawableHitObject GetVisualRepresentation(TObject h); + public abstract DrawableHitObject GetVisualRepresentation(TObject h); } /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index b05efce146..340833c090 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -1,7 +1,6 @@ // 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; @@ -9,8 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.StateChanges; +using osu.Framework.Input.Events; +using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Framework.Timing; using osu.Game.Configuration; @@ -19,6 +18,9 @@ using osu.Game.Input.Handlers; using osu.Game.Screens.Play; using OpenTK.Input; using static osu.Game.Input.Handlers.ReplayInputHandler; +using JoystickState = osu.Framework.Input.States.JoystickState; +using KeyboardState = osu.Framework.Input.States.KeyboardState; +using MouseState = osu.Framework.Input.States.MouseState; namespace osu.Game.Rulesets.UI { @@ -36,13 +38,7 @@ namespace osu.Game.Rulesets.UI protected override InputState CreateInitialState() { var state = base.CreateInitialState(); - return new RulesetInputManagerInputState - { - Mouse = state.Mouse, - Keyboard = state.Keyboard, - Joystick = state.Joystick, - LastReplayState = null - }; + return new RulesetInputManagerInputState(state.Mouse, state.Keyboard, state.Joystick); } protected readonly KeyBindingContainer KeyBindingContainer; @@ -56,33 +52,20 @@ namespace osu.Game.Rulesets.UI #region Action mapping (for replays) - private List lastPressedActions = new List(); - - public override void HandleCustomInput(InputState state, IInput input) + public override void HandleInputStateChange(InputStateChangeEvent inputStateChange) { - if (!(input is ReplayState replayState)) + if (inputStateChange is ReplayStateChangeEvent replayStateChanged) { - base.HandleCustomInput(state, input); - return; - } + foreach (var action in replayStateChanged.ReleasedActions) + KeyBindingContainer.TriggerReleased(action); - if (state is RulesetInputManagerInputState inputState) + foreach (var action in replayStateChanged.PressedActions) + KeyBindingContainer.TriggerPressed(action); + } + else { - inputState.LastReplayState = replayState; + base.HandleInputStateChange(inputStateChange); } - - // 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 @@ -90,12 +73,10 @@ namespace osu.Game.Rulesets.UI #region IHasReplayHandler private ReplayInputHandler replayInputHandler; + public ReplayInputHandler ReplayInputHandler { - get - { - return replayInputHandler; - } + get => replayInputHandler; set { if (replayInputHandler != null) RemoveHandler(replayInputHandler); @@ -225,16 +206,20 @@ namespace osu.Game.Rulesets.UI mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool Handle(UIEvent e) { - if (mouseDisabled.Value && (args.Button == MouseButton.Left || args.Button == MouseButton.Right)) return false; - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - if (!CurrentState.Mouse.IsPressed(args.Button)) return false; - return base.OnMouseUp(state, args); + switch (e) + { + case MouseDownEvent mouseDown when mouseDown.Button == MouseButton.Left || mouseDown.Button == MouseButton.Right: + if (mouseDisabled.Value) + return false; + break; + case MouseUpEvent mouseUp: + if (!CurrentState.Mouse.IsPressed(mouseUp.Button)) + return false; + break; + } + return base.Handle(e); } #endregion @@ -286,8 +271,13 @@ namespace osu.Game.Rulesets.UI } public class RulesetInputManagerInputState : InputState - where T : struct + where T : struct { public ReplayState LastReplayState; + + public RulesetInputManagerInputState(MouseState mouse = null, KeyboardState keyboard = null, JoystickState joystick = null) + : base(mouse, keyboard, joystick) + { + } } } diff --git a/osu.Game/Rulesets/UI/ScalableContainer.cs b/osu.Game/Rulesets/UI/ScalableContainer.cs deleted file mode 100644 index 5ee03be7ee..0000000000 --- a/osu.Game/Rulesets/UI/ScalableContainer.cs +++ /dev/null @@ -1,99 +0,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.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 4fe727cb84..d0db8f8835 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs @@ -6,19 +6,19 @@ namespace osu.Game.Rulesets.UI.Scrolling public enum ScrollingDirection { /// - /// Hitobjects will scroll vertically from the bottom of the hitobject container. + /// Hit objects will scroll vertically from the bottom of the hitobject container. /// Up, /// - /// Hitobjects will scroll vertically from the top of the hitobject container. + /// Hit objects will scroll vertically from the top of the hitobject container. /// Down, /// - /// Hitobjects will scroll horizontally from the right of the hitobject container. + /// Hit objects will scroll horizontally from the right of the hitobject container. /// Left, /// - /// Hitobjects will scroll horizontally from the left of the hitobject container. + /// Hit objects 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 c64fca6eff..7307fc0ead 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -1,7 +1,6 @@ // 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; @@ -33,20 +32,16 @@ namespace osu.Game.Rulesets.UI.Scrolling private Cached initialStateCache = new Cached(); - public ScrollingHitObjectContainer() + private readonly ISpeedChangeVisualiser speedChangeVisualiser; + + public ScrollingHitObjectContainer(SpeedChangeVisualisationMethod visualisationMethod) { RelativeSizeAxes = Axes.Both; TimeRange.ValueChanged += _ => initialStateCache.Invalidate(); Direction.ValueChanged += _ => initialStateCache.Invalidate(); - } - private ISpeedChangeVisualiser speedChangeVisualiser; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - switch (config.Get(OsuSetting.SpeedChangeVisualisation)) + switch (visualisationMethod) { case SpeedChangeVisualisationMethod.Sequential: speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints); @@ -54,6 +49,9 @@ namespace osu.Game.Rulesets.UI.Scrolling case SpeedChangeVisualisationMethod.Overlapping: speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); break; + case SpeedChangeVisualisationMethod.Constant: + speedChangeVisualiser = new ConstantSpeedChangeVisualiser(); + break; } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index ec73c0fb14..a1fc13ce4d 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; @@ -62,19 +63,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// protected readonly Bindable Direction = new Bindable(); - /// - /// Creates a new . - /// - /// 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(float? customWidth = null, float? customHeight = null) - : base(customWidth, customHeight) - { - } + protected virtual SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Sequential; [BackgroundDependencyLoader] private void load() @@ -104,7 +93,7 @@ namespace osu.Game.Rulesets.UI.Scrolling protected sealed override HitObjectContainer CreateHitObjectContainer() { - var container = new ScrollingHitObjectContainer(); + var container = new ScrollingHitObjectContainer(VisualisationMethod); container.Direction.BindTo(Direction); return container; } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs index 3fc67e4e34..41cdd6c06f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs @@ -60,6 +60,7 @@ namespace osu.Game.Rulesets.UI.Scrolling return new MultiplierControlPoint(c.Time) { + Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier, TimingPoint = lastTimingPoint, DifficultyPoint = lastDifficultyPoint }; @@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // If we have no control points, add a default one if (DefaultControlPoints.Count == 0) - DefaultControlPoints.Add(new MultiplierControlPoint()); + DefaultControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield)); } @@ -88,22 +89,5 @@ namespace osu.Game.Rulesets.UI.Scrolling 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]); - } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs new file mode 100644 index 0000000000..9e910d6b11 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs @@ -0,0 +1,67 @@ +// 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 osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Visualisers +{ + public class ConstantSpeedChangeVisualiser : ISpeedChangeVisualiser + { + public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + obj.LifetimeStart = obj.HitObject.StartTime - timeRange; + + if (obj.HitObject is IHasEndTime endTime) + { + var hitObjectLength = (endTime.EndTime - obj.HitObject.StartTime) / timeRange; + + switch (direction) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + obj.Height = (float)(hitObjectLength * length.Y); + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + obj.Width = (float)(hitObjectLength * length.X); + break; + } + } + + ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); + + // Nested hitobjects don't need to scroll, but they do need accurate positions + UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); + } + } + + public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var position = (obj.HitObject.StartTime - currentTime) / 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; + } + } + } + } +} diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 46d1260410..7787b4ddc2 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -5,8 +5,7 @@ using System; using System.Threading; using osu.Framework.Screens; using osu.Framework.Graphics; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using OpenTK; namespace osu.Game.Screens @@ -21,7 +20,7 @@ namespace osu.Game.Screens private const float transition_length = 500; private const float x_movement_amount = 50; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { //we don't want to handle escape key. return false; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 78561cecbf..e326cdb0ca 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -4,19 +4,14 @@ 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 + public class BackgroundScreenBeatmap : BlurrableBackgroundScreen { - private Background background; - private WorkingBeatmap beatmap; - private Vector2 blurTarget; public WorkingBeatmap Beatmap { @@ -30,21 +25,21 @@ namespace osu.Game.Screens.Backgrounds Schedule(() => { - LoadComponentAsync(new BeatmapBackground(beatmap), b => + LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() => { float newDepth = 0; - if (background != null) + if (Background != null) { - newDepth = background.Depth + 1; - background.FinishTransforms(); - background.FadeOut(250); - background.Expire(); + newDepth = Background.Depth + 1; + Background.FinishTransforms(); + Background.FadeOut(250); + Background.Expire(); } b.Depth = newDepth; - Add(background = b); - background.BlurSigma = blurTarget; - }); + Add(Background = b); + Background.BlurSigma = BlurTarget; + })); }); } } @@ -54,9 +49,6 @@ namespace osu.Game.Screens.Backgrounds 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; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 38df9b13ef..989883c8b3 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -3,32 +3,32 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.MathUtils; using osu.Framework.Threading; using osu.Game.Graphics.Backgrounds; namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenDefault : BackgroundScreen + public class BackgroundScreenDefault : BlurrableBackgroundScreen { 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() { + currentDisplay = RNG.Next(0, background_count); display(new Background(backgroundName)); } private void display(Background newBackground) { - current?.FadeOut(800, Easing.InOutSine); - current?.Expire(); + Background?.FadeOut(800, Easing.InOutSine); + Background?.Expire(); - Add(current = newBackground); + Add(Background = newBackground); currentDisplay++; } diff --git a/osu.Game/Screens/BlurrableBackgroundScreen.cs b/osu.Game/Screens/BlurrableBackgroundScreen.cs new file mode 100644 index 0000000000..92d32badc4 --- /dev/null +++ b/osu.Game/Screens/BlurrableBackgroundScreen.cs @@ -0,0 +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.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Game.Graphics.Backgrounds; +using OpenTK; + +namespace osu.Game.Screens +{ + public abstract class BlurrableBackgroundScreen : BackgroundScreen + { + protected Background Background; + + protected Vector2 BlurTarget; + + public TransformSequence BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None) + => Background?.BlurTo(BlurTarget = sigma, duration, easing); + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs similarity index 96% rename from osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs rename to osu.Game/Screens/Edit/BindableBeatDivisor.cs index b7dce8c96e..3124482c73 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -5,7 +5,7 @@ using System; using System.Linq; using osu.Framework.Configuration; -namespace osu.Game.Screens.Edit.Screens.Compose +namespace osu.Game.Screens.Edit { public class BindableBeatDivisor : BindableNumber { diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs similarity index 94% rename from osu.Game/Screens/Edit/Menus/EditorMenuBar.cs rename to osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index c6351c8520..4b5c13efbd 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -2,19 +2,18 @@ // 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.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Input.States; -using osu.Game.Screens.Edit.Screens; -namespace osu.Game.Screens.Edit.Menus +namespace osu.Game.Screens.Edit.Components.Menus { public class EditorMenuBar : OsuMenu { @@ -171,18 +170,18 @@ namespace osu.Game.Screens.Edit.Menus { } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { if (Item is EditorMenuItemSpacer) return true; - return base.OnHover(state); + return base.OnHover(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (Item is EditorMenuItemSpacer) return true; - return base.OnClick(state); + return base.OnClick(e); } } } diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs similarity index 92% rename from osu.Game/Screens/Edit/Menus/EditorMenuItem.cs rename to osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs index 0ef1ad8c6b..1c9253cce7 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs @@ -4,7 +4,7 @@ using System; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Screens.Edit.Menus +namespace osu.Game.Screens.Edit.Components.Menus { public class EditorMenuItem : OsuMenuItem { diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs similarity index 86% rename from osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs rename to osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs index 91b40a2cfb..17ee88241e 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs @@ -1,7 +1,7 @@ // 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 +namespace osu.Game.Screens.Edit.Components.Menus { public class EditorMenuItemSpacer : EditorMenuItem { diff --git a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs similarity index 96% rename from osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs rename to osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs index f58e5b39eb..4ff01c0f90 100644 --- a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs @@ -1,7 +1,6 @@ // 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; @@ -9,10 +8,10 @@ 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; +using OpenTK.Graphics; -namespace osu.Game.Screens.Edit.Menus +namespace osu.Game.Screens.Edit.Components.Menus { public class ScreenSelectionTabControl : OsuTabControl { diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 6cd7fd52d4..3c8288f04a 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -8,7 +8,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Components new OsuSpriteText { Origin = Anchor.BottomLeft, - Text = "Playback Speed", + Text = "Playback speed", RelativePositionAxes = Axes.Y, Y = 0.5f, Padding = new MarginPadding { Left = 45 } @@ -138,13 +138,13 @@ namespace osu.Game.Screens.Edit.Components textBold.Colour = hoveredColour = colours.Yellow; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { updateState(); return true; } - protected override void OnHoverLost(InputState state) => updateState(); + protected override void OnHoverLost(HoverLostEvent e) => updateState(); protected override void OnActivated() => updateState(); protected override void OnDeactivated() => updateState(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs similarity index 94% rename from osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 803a1275d7..22f8417735 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -2,20 +2,20 @@ // 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.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using OpenTK; +using OpenTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +namespace osu.Game.Screens.Edit.Components.RadioButtons { public class DrawableRadioButton : TriangleButton { @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (button.Selected) return true; @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons button.Selected.Value = true; - return base.OnClick(state); + return base.OnClick(e); } protected override SpriteText CreateText() => new OsuSpriteText diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs similarity index 95% rename from osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index 09fe34bedc..c671fa71c2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -4,7 +4,7 @@ using System; using osu.Framework.Configuration; -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +namespace osu.Game.Screens.Edit.Components.RadioButtons { public class RadioButton { diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs similarity index 96% rename from osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs index 7ba16b3d3e..9bb2e11430 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs @@ -2,12 +2,12 @@ // 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; +using OpenTK; -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +namespace osu.Game.Screens.Edit.Components.RadioButtons { public class RadioButtonCollection : CompositeDrawable { 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 7ff3849361..11e9ecddc6 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -6,8 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -30,31 +30,37 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Add(marker = new MarkerVisualisation()); } - protected override bool OnDragStart(InputState state) => true; - protected override bool OnDragEnd(InputState state) => true; - protected override bool OnDrag(InputState state) + protected override bool OnDragStart(DragStartEvent e) => true; + protected override bool OnDragEnd(DragEndEvent e) => true; + protected override bool OnDrag(DragEvent e) { - seekToPosition(state.Mouse.NativeState.Position); + seekToPosition(e.ScreenSpaceMousePosition); return true; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { - seekToPosition(state.Mouse.NativeState.Position); + seekToPosition(e.ScreenSpaceMousePosition); return true; } + private ScheduledDelegate scheduledSeek; + /// /// 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; + scheduledSeek?.Cancel(); + scheduledSeek = Schedule(() => + { + if (Beatmap.Value == null) + return; - float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); - adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); + float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); + adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); + }); } protected override void Update() diff --git a/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs similarity index 93% rename from osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs rename to osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 63df143ca8..a5a1f590bf 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -12,15 +12,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; using OpenTK.Input; -namespace osu.Game.Screens.Edit.Screens.Compose +namespace osu.Game.Screens.Edit.Compose.Components { public class BeatDivisorControl : CompositeDrawable { @@ -231,11 +230,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose { } - public override bool HandleKeyboardInput => IsHovered && !CurrentNumber.Disabled; + public override bool HandleNonPositionalInput => IsHovered && !CurrentNumber.Disabled; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - switch (args.Key) + switch (e.Key) { case Key.Right: beatDivisor.Next(); @@ -250,34 +249,34 @@ namespace osu.Game.Screens.Edit.Screens.Compose } } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { marker.Active = true; - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { marker.Active = false; - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { - handleMouseInput(state); + handleMouseInput(e.ScreenSpaceMousePosition); return true; } - protected override bool OnDrag(InputState state) + protected override bool OnDrag(DragEvent e) { - handleMouseInput(state); + handleMouseInput(e.ScreenSpaceMousePosition); return true; } - private void handleMouseInput(InputState state) + private void handleMouseInput(Vector2 screenSpaceMousePosition) { // copied from SliderBar so we can do custom spacing logic. - var xPosition = (ToLocalSpace(state?.Mouse.NativeState.Position ?? Vector2.Zero).X - RangePadding) / UsableWidth; + var xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); OnUserChange(); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs new file mode 100644 index 0000000000..acbfd1f1d6 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -0,0 +1,191 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class BlueprintContainer : CompositeDrawable + { + private SelectionBlueprintContainer selectionBlueprints; + private Container placementBlueprintContainer; + private SelectionBox selectionBox; + + private IEnumerable selections => selectionBlueprints.Children.Where(c => c.IsAlive); + + [Resolved] + private HitObjectComposer composer { get; set; } + + public BlueprintContainer() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + selectionBox = composer.CreateSelectionBox(); + selectionBox.DeselectAll = deselectAll; + + var dragBox = new DragBox(select); + dragBox.DragEnd += () => selectionBox.UpdateVisibility(); + + InternalChildren = new[] + { + dragBox, + selectionBox, + selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, + placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }, + dragBox.CreateProxy() + }; + + foreach (var obj in composer.HitObjects) + AddBlueprintFor(obj); + } + + private HitObjectCompositionTool currentTool; + + /// + /// The current placement tool. + /// + public HitObjectCompositionTool CurrentTool + { + get => currentTool; + set + { + if (currentTool == value) + return; + currentTool = value; + + refreshTool(); + } + } + + /// + /// Adds a blueprint for a which adds movement support. + /// + /// The to create a blueprint for. + public void AddBlueprintFor(DrawableHitObject hitObject) + { + refreshTool(); + + var blueprint = composer.CreateBlueprintFor(hitObject); + if (blueprint == null) + return; + + blueprint.Selected += onBlueprintSelected; + blueprint.Deselected += onBlueprintDeselected; + blueprint.SelectionRequested += onSelectionRequested; + blueprint.DragRequested += onDragRequested; + + selectionBlueprints.Add(blueprint); + } + + /// + /// Removes a blueprint for a . + /// + /// The for which to remove the blueprint. + public void RemoveBlueprintFor(DrawableHitObject hitObject) + { + var blueprint = selectionBlueprints.Single(m => m.HitObject == hitObject); + if (blueprint == null) + return; + + blueprint.Deselect(); + + blueprint.Selected -= onBlueprintSelected; + blueprint.Deselected -= onBlueprintDeselected; + blueprint.SelectionRequested -= onSelectionRequested; + blueprint.DragRequested -= onDragRequested; + + selectionBlueprints.Remove(blueprint); + } + + protected override bool OnClick(ClickEvent e) + { + deselectAll(); + return true; + } + + /// + /// Refreshes the current placement tool. + /// + private void refreshTool() + { + placementBlueprintContainer.Clear(); + + var blueprint = CurrentTool?.CreatePlacementBlueprint(); + if (blueprint != null) + placementBlueprintContainer.Child = blueprint; + } + + + /// + /// Select all masks in a given rectangle selection area. + /// + /// The rectangle to perform a selection on in screen-space coordinates. + private void select(RectangleF rect) + { + foreach (var blueprint in selections.ToList()) + { + if (blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint)) + blueprint.Select(); + else + blueprint.Deselect(); + } + } + + /// + /// Deselects all selected s. + /// + private void deselectAll() => selections.ToList().ForEach(m => m.Deselect()); + + private void onBlueprintSelected(SelectionBlueprint blueprint) + { + selectionBox.HandleSelected(blueprint); + selectionBlueprints.ChangeChildDepth(blueprint, 1); + } + + private void onBlueprintDeselected(SelectionBlueprint blueprint) + { + selectionBox.HandleDeselected(blueprint); + selectionBlueprints.ChangeChildDepth(blueprint, 0); + } + + private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionBox.HandleSelectionRequested(blueprint, state); + + private void onDragRequested(DragEvent dragEvent) => selectionBox.HandleDrag(dragEvent); + + private class SelectionBlueprintContainer : Container + { + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is SelectionBlueprint xBlueprint) || !(y is SelectionBlueprint yBlueprint)) + return base.Compare(x, y); + return Compare(xBlueprint, yBlueprint); + } + + public int Compare(SelectionBlueprint x, SelectionBlueprint y) + { + // dpeth is used to denote selected status (we always want selected blueprints 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/DragLayer.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs similarity index 70% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs rename to osu.Game/Screens/Edit/Compose/Components/DragBox.cs index c4392bbc60..1a58f476ac 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -7,16 +7,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; -using osu.Game.Rulesets.Edit; +using osu.Framework.Input.Events; using OpenTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.Layers +namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A layer that handles and displays drag selection for a collection of s. + /// A box that displays the drag selection and provides selection events for users to handle. /// - public class DragLayer : CompositeDrawable + public class DragBox : CompositeDrawable { private readonly Action performSelection; @@ -28,10 +27,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private Drawable box; /// - /// Creates a new . + /// Creates a new . /// - /// The selectable s. - public DragLayer(Action performSelection) + /// A delegate that performs drag selection. + public DragBox(Action performSelection) { this.performSelection = performSelection; @@ -47,7 +46,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { Masking = true, BorderColour = Color4.White, - BorderThickness = MaskSelection.BORDER_RADIUS, + BorderThickness = SelectionBox.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, @@ -56,16 +55,16 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers }; } - protected override bool OnDragStart(InputState state) + protected override bool OnDragStart(DragStartEvent e) { this.FadeIn(250, Easing.OutQuint); return true; } - protected override bool OnDrag(InputState state) + protected override bool OnDrag(DragEvent e) { - var dragPosition = state.Mouse.NativeState.Position; - var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; + var dragPosition = e.ScreenSpaceMousePosition; + var dragStartPosition = e.ScreenSpaceMouseDownPosition; var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); @@ -82,7 +81,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return true; } - protected override bool OnDragEnd(InputState state) + protected override bool OnDragEnd(DragEndEvent e) { this.FadeOut(250, Easing.OutQuint); DragEnd?.Invoke(); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs b/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs new file mode 100644 index 0000000000..4956b7759f --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs @@ -0,0 +1,32 @@ +// 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.Compose.Components +{ + /// + /// Provides a border around the playfield. + /// + public class EditorPlayfieldBorder : CompositeDrawable + { + public EditorPlayfieldBorder() + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + BorderColour = Color4.White; + BorderThickness = 2; + + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs similarity index 54% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs rename to osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 927e7a2342..8732672723 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -3,32 +3,37 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Types; using OpenTK; +using OpenTK.Input; -namespace osu.Game.Screens.Edit.Screens.Compose.Layers +namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A box which surrounds s and provides interactive handles, context menus etc. + /// A box which surrounds s and provides interactive handles, context menus etc. /// - public class MaskSelection : CompositeDrawable + public class SelectionBox : CompositeDrawable { public const float BORDER_RADIUS = 2; - private readonly List selectedMasks; + private readonly List selectedBlueprints; private Drawable outline; - public MaskSelection() + [Resolved] + private IPlacementHandler placementHandler { get; set; } + + public SelectionBox() { - selectedMasks = new List(); + selectedBlueprints = new List(); RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -54,19 +59,28 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - public void HandleDrag(HitObjectMask m, InputState state) + public void HandleDrag(DragEvent dragEvent) { // Todo: Various forms of snapping - foreach (var mask in selectedMasks) + foreach (var blueprint in selectedBlueprints) + blueprint.AdjustPosition(dragEvent); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat) + return base.OnKeyDown(e); + + switch (e.Key) { - switch (mask.HitObject.HitObject) - { - case IHasEditablePosition editablePosition: - editablePosition.OffsetPosition(state.Mouse.Delta); - break; - } + case Key.Delete: + foreach (var h in selectedBlueprints.ToList()) + placementHandler.Delete(h.HitObject.HitObject); + return true; } + + return base.OnKeyDown(e); } #endregion @@ -74,49 +88,49 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region Selection Handling /// - /// Bind an action to deselect all selected masks. + /// Bind an action to deselect all selected blueprints. /// public Action DeselectAll { private get; set; } /// - /// Handle a mask becoming selected. + /// Handle a blueprint becoming selected. /// - /// The mask. - public void HandleSelected(HitObjectMask mask) => selectedMasks.Add(mask); + /// The blueprint. + public void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); /// - /// Handle a mask becoming deselected. + /// Handle a blueprint becoming deselected. /// - /// The mask. - public void HandleDeselected(HitObjectMask mask) + /// The blueprint. + public void HandleDeselected(SelectionBlueprint blueprint) { - selectedMasks.Remove(mask); + selectedBlueprints.Remove(blueprint); - // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection - if (selectedMasks.Count == 0) + // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection + if (selectedBlueprints.Count == 0) UpdateVisibility(); } /// - /// Handle a mask requesting selection. + /// Handle a blueprint requesting selection. /// - /// The mask. - public void HandleSelectionRequested(HitObjectMask mask, InputState state) + /// The blueprint. + public void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { - if (mask.IsSelected) - mask.Deselect(); + if (blueprint.IsSelected) + blueprint.Deselect(); else - mask.Select(); + blueprint.Select(); } else { - if (mask.IsSelected) + if (blueprint.IsSelected) return; DeselectAll?.Invoke(); - mask.Select(); + blueprint.Select(); } UpdateVisibility(); @@ -125,11 +139,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #endregion /// - /// Updates whether this is visible. + /// Updates whether this is visible. /// internal void UpdateVisibility() { - if (selectedMasks.Count > 0) + if (selectedBlueprints.Count > 0) Show(); else Hide(); @@ -139,7 +153,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { base.Update(); - if (selectedMasks.Count == 0) + if (selectedBlueprints.Count == 0) return; // Move the rectangle to cover the hitobjects @@ -148,10 +162,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers bool hasSelection = false; - foreach (var mask in selectedMasks) + foreach (var blueprint in selectedBlueprints) { - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight)); + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprint.SelectionQuad.BottomRight)); } topLeft -= new Vector2(5); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs similarity index 96% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/CentreMarker.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 8e932f307d..b2c6f02058 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using OpenTK; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class CentreMarker : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs similarity index 92% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 30205c5aa1..0c626f2c54 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -7,13 +7,12 @@ using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class Timeline : ZoomableScrollContainer { @@ -133,9 +132,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { - if (base.OnMouseDown(state, args)) + if (base.OnMouseDown(e)) { beginUserDrag(); return true; @@ -144,10 +143,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline return false; } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { endUserDrag(); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } private void beginUserDrag() diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs similarity index 97% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 006317e57e..5b98140a3b 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.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 OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using OpenTK; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineArea : CompositeDrawable { @@ -59,8 +59,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline Spacing = new Vector2(0, 4), Children = new[] { - hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" }, - hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" }, + hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hit objects" }, + hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hit sounds" }, waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs similarity index 96% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 5928fbaa1b..d481934347 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -2,15 +2,15 @@ // 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; +using OpenTK; +using OpenTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineButton : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs similarity index 93% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index d30aaacc6a..8d39f61d89 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -5,11 +5,11 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; using OpenTK; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class ZoomableScrollContainer : ScrollContainer { @@ -97,13 +97,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline zoomedContent.Width = DrawWidth * currentZoom; } - protected override bool OnScroll(InputState state) + protected override bool OnScroll(ScrollEvent e) { - if (state.Mouse.HasPreciseScroll) + if (e.IsPrecise) // for now, we don't support zoom when using a precision scroll device. this needs gesture support. - return base.OnScroll(state); + return base.OnScroll(e); - setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.Y, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X); + setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs similarity index 87% rename from osu.Game/Screens/Edit/Screens/Compose/Compose.cs rename to osu.Game/Screens/Edit/Compose/ComposeScreen.cs index a862485fd6..30962d5536 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -3,24 +3,28 @@ 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; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using OpenTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose +namespace osu.Game.Screens.Edit.Compose { - public class Compose : EditorScreen + [Cached(Type = typeof(IPlacementHandler))] + public class ComposeScreen : EditorScreen, IPlacementHandler { private const float vertical_margins = 10; private const float horizontal_margins = 20; private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private Container composerContainer; + private HitObjectComposer composer; [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) @@ -28,6 +32,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose if (beatDivisor != null) this.beatDivisor.BindTo(beatDivisor); + Container composerContainer; + Children = new Drawable[] { new GridContainer @@ -101,7 +107,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose return; } - var composer = ruleset.CreateHitObjectComposer(); + composer = ruleset.CreateHitObjectComposer(); if (composer == null) { Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); @@ -111,5 +117,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose composerContainer.Child = composer; } + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); + + public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } diff --git a/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs new file mode 100644 index 0000000000..f93b294536 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs @@ -0,0 +1,28 @@ +// 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.Screens.Edit.Compose +{ + public interface IPlacementHandler + { + /// + /// Notifies that a placement has begun. + /// + /// The being placed. + void BeginPlacement(HitObject hitObject); + + /// + /// Notifies that a placement has finished. + /// + /// The that has been placed. + void EndPlacement(HitObject hitObject); + + /// + /// Deletes a . + /// + /// The to delete. + void Delete(HitObject hitObject); + } +} diff --git a/osu.Game/Screens/Edit/Screens/Design/Design.cs b/osu.Game/Screens/Edit/Design/DesignScreen.cs similarity index 93% rename from osu.Game/Screens/Edit/Screens/Design/Design.cs rename to osu.Game/Screens/Edit/Design/DesignScreen.cs index 052a1c1d90..e99e352653 100644 --- a/osu.Game/Screens/Edit/Screens/Design/Design.cs +++ b/osu.Game/Screens/Edit/Design/DesignScreen.cs @@ -7,11 +7,11 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using OpenTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Design +namespace osu.Game.Screens.Edit.Design { - public class Design : EditorScreen + public class DesignScreen : EditorScreen { - public Design() + public DesignScreen() { Add(new Container { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7159cd919c..524f049284 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -8,18 +8,17 @@ 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.States; +using osu.Framework.Input.Events; using osu.Framework.Platform; 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; +using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Design; namespace osu.Game.Screens.Edit { @@ -169,10 +168,10 @@ namespace osu.Game.Screens.Edit switch (mode) { case EditorScreenMode.Compose: - currentScreen = new Compose(); + currentScreen = new ComposeScreen(); break; case EditorScreenMode.Design: - currentScreen = new Design(); + currentScreen = new DesignScreen(); break; default: currentScreen = new EditorScreen(); @@ -182,9 +181,9 @@ namespace osu.Game.Screens.Edit LoadComponentAsync(currentScreen, screenContainer.Add); } - protected override bool OnScroll(InputState state) + protected override bool OnScroll(ScrollEvent e) { - if (state.Mouse.ScrollDelta.X + state.Mouse.ScrollDelta.Y > 0) + if (e.ScrollDelta.X + e.ScrollDelta.Y > 0) clock.SeekBackward(!clock.IsRunning); else clock.SeekForward(!clock.IsRunning); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 1c40181ec9..5fa29d6005 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,7 +7,6 @@ using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit.Screens.Compose; using OpenTK; namespace osu.Game.Screens.Edit diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs similarity index 97% rename from osu.Game/Screens/Edit/Screens/EditorScreen.cs rename to osu.Game/Screens/Edit/EditorScreen.cs index f8402b9a9f..3a8fc3ef80 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -namespace osu.Game.Screens.Edit.Screens +namespace osu.Game.Screens.Edit { /// /// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor. diff --git a/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs b/osu.Game/Screens/Edit/EditorScreenMode.cs similarity index 91% rename from osu.Game/Screens/Edit/Screens/EditorScreenMode.cs rename to osu.Game/Screens/Edit/EditorScreenMode.cs index be8363680d..17de6c4125 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs +++ b/osu.Game/Screens/Edit/EditorScreenMode.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Screens.Edit.Screens +namespace osu.Game.Screens.Edit { public enum EditorScreenMode { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs deleted file mode 100644 index c46f9a1b7f..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs deleted file mode 100644 index d212bbe7dd..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ /dev/null @@ -1,74 +0,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 osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class HitObjectMaskLayer : CompositeDrawable - { - private MaskContainer maskContainer; - private HitObjectComposer composer; - - public HitObjectMaskLayer() - { - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(HitObjectComposer composer) - { - this.composer = composer; - - 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[] - { - dragLayer, - maskSelection, - maskContainer, - dragLayer.CreateProxy() - }; - - foreach (var obj in composer.HitObjects) - 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); - } - } -} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs deleted file mode 100644 index 5ee31769a3..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ /dev/null @@ -1,127 +0,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.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; -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) - { - if (drawable == null) throw new ArgumentNullException(nameof(drawable)); - - base.Add(drawable); - - drawable.Selected += onMaskSelected; - drawable.Deselected += onMaskDeselected; - drawable.SelectionRequested += onSelectionRequested; - drawable.DragRequested += onDragRequested; - } - - public override bool Remove(HitObjectMask drawable) - { - if (drawable == null) throw new ArgumentNullException(nameof(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/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs similarity index 98% rename from osu.Game/Screens/Edit/Screens/Setup/Components/LabelledComponents/LabelledTextBox.cs rename to osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 94200b7f4e..626c59981c 100644 --- a/osu.Game/Screens/Edit/Screens/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -10,8 +9,9 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using OpenTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Setup.Components.LabelledComponents +namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { public class LabelledTextBox : CompositeDrawable { diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index e53905a102..2b85ee6158 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -17,8 +17,7 @@ using OpenTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Audio.Track; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Screens.Menu @@ -47,7 +46,7 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleClick; private SampleChannel sampleHover; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public Button(string text, string sampleName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) { @@ -151,7 +150,7 @@ namespace osu.Game.Screens.Menu rightward = !rightward; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { if (State != ButtonState.Expanded) return true; @@ -167,7 +166,7 @@ namespace osu.Game.Screens.Menu return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { icon.ClearTransforms(); icon.RotateTo(0, 500, Easing.Out); @@ -186,30 +185,30 @@ namespace osu.Game.Screens.Menu sampleClick = audio.Sample.Get($@"Menu/{sampleName}"); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { boxHoverLayer.FadeTo(0.1f, 1000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { boxHoverLayer.FadeTo(0, 1000, Easing.OutQuint); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { trigger(); return true; } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Repeat || state.Keyboard.ControlPressed || state.Keyboard.ShiftPressed || state.Keyboard.AltPressed) + if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed) return false; - if (triggerKey == args.Key && triggerKey != Key.Unknown) + if (triggerKey == e.Key && triggerKey != Key.Unknown) { trigger(); return true; @@ -229,8 +228,8 @@ namespace osu.Game.Screens.Menu boxHoverLayer.FadeOut(800, Easing.OutExpo); } - public override bool HandleKeyboardInput => state == ButtonState.Expanded; - public override bool HandleMouseInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; + public override bool HandleNonPositionalInput => state == ButtonState.Expanded; + public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; protected override void Update() { diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index b9a799328e..5c17317fc1 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Menu case GlobalAction.Back: return goBack(); case GlobalAction.Select: - logo?.TriggerOnClick(); + logo?.Click(); return true; default: return false; @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Menu sampleBack?.Play(); return true; case ButtonSystemState.Play: - backButton.TriggerOnClick(); + backButton.Click(); return true; default: return false; @@ -150,18 +150,18 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.TopLevel; return true; case ButtonSystemState.TopLevel: - buttonsTopLevel.First().TriggerOnClick(); + buttonsTopLevel.First().Click(); return false; case ButtonSystemState.Play: - buttonsPlay.First().TriggerOnClick(); + buttonsPlay.First().Click(); return false; } } private ButtonSystemState state = ButtonSystemState.Initial; - public override bool HandleKeyboardInput => state != ButtonSystemState.Exit; - public override bool HandleMouseInput => state != ButtonSystemState.Exit; + public override bool HandleNonPositionalInput => state != ButtonSystemState.Exit; + public override bool HandlePositionalInput => state != ButtonSystemState.Exit; public ButtonSystemState State { diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index a3cb2f13d0..34fb0b196b 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -64,9 +64,6 @@ namespace osu.Game.Screens.Menu private readonly float[] frequencyAmplitudes = new float[256]; - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - private Shader shader; private readonly Texture texture; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 7e97859be6..2374d6a2fe 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -6,10 +6,10 @@ using OpenTK.Graphics; using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Charts; @@ -64,6 +64,20 @@ namespace osu.Game.Screens.Menu }, sideFlashes = new MenuSideFlashes(), }; + + buttons.StateChanged += state => + { + switch (state) + { + case ButtonSystemState.Initial: + case ButtonSystemState.Exit: + background.FadeColour(Color4.White, 500, Easing.OutSine); + break; + default: + background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); + break; + } + }; } [BackgroundDependencyLoader(true)] @@ -185,15 +199,15 @@ namespace osu.Game.Screens.Menu return base.OnExiting(next); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (!args.Repeat && state.Keyboard.ControlPressed && state.Keyboard.ShiftPressed && args.Key == Key.D) + if (!e.Repeat && e.ControlPressed && e.ShiftPressed && e.Key == Key.D) { Push(new Drawings()); return true; } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } } } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 7d46aad089..3de68fe914 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -19,9 +19,6 @@ namespace osu.Game.Screens.Menu { public class MenuSideFlashes : BeatSyncedContainer { - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - private readonly IBindable beatmap = new Bindable(); private Box leftBox; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f0f765a4c9..4e6a107f91 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -11,8 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -68,7 +67,7 @@ namespace osu.Game.Screens.Menu public bool BeatMatching = true; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => logoContainer.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => logoContainer.ReceivePositionalInputAt(screenSpacePos); public bool Ripple { @@ -84,11 +83,10 @@ namespace osu.Game.Screens.Menu private const double early_activation = 60; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + public OsuLogo() { - // Required to make Schedule calls run in OsuScreen even when we are not visible. - AlwaysPresent = true; - EarlyActivationMilliseconds = early_activation; Size = new Vector2(default_size); @@ -343,25 +341,25 @@ namespace osu.Game.Screens.Menu } } - public override bool HandleMouseInput => base.HandleMouseInput && Action != null && Alpha > 0.2f; + public override bool HandlePositionalInput => base.HandlePositionalInput && Action != null && Alpha > 0.2f; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { - if (args.Button != MouseButton.Left) return false; + if (e.Button != MouseButton.Left) return false; logoBounceContainer.ScaleTo(0.9f, 1000, Easing.Out); return true; } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { - if (args.Button != MouseButton.Left) return false; + if (e.Button != MouseButton.Left) return false; logoBounceContainer.ScaleTo(1f, 500, Easing.OutElastic); return true; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (Action?.Invoke() ?? true) sampleClick.Play(); @@ -372,13 +370,13 @@ namespace osu.Game.Screens.Menu return true; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { logoHoverContainer.ScaleTo(1, 500, Easing.OutElastic); } diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index 42863754c5..6dc59f5cac 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -1,7 +1,6 @@ // 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.Localisation; @@ -14,8 +13,6 @@ namespace osu.Game.Screens.Multi.Components { private readonly OsuSpriteText beatmapTitle, beatmapDash, beatmapArtist; - private LocalisationEngine localisation; - public float TextSize { set { beatmapTitle.TextSize = beatmapDash.TextSize = beatmapArtist.TextSize = value; } @@ -48,12 +45,6 @@ namespace osu.Game.Screens.Multi.Components }; } - [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation) - { - this.localisation = localisation; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -64,15 +55,14 @@ namespace osu.Game.Screens.Multi.Components { if (beatmap == null) { - beatmapTitle.Current = beatmapArtist.Current = null; beatmapTitle.Text = "Changing map"; beatmapDash.Text = beatmapArtist.Text = string.Empty; } else { - beatmapTitle.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); + beatmapTitle.Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)); beatmapDash.Text = @" - "; - beatmapArtist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); + beatmapArtist.Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)); } } } diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs index 739346fabe..67db23263b 100644 --- a/osu.Game/Screens/Multi/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs @@ -10,7 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -246,7 +246,7 @@ namespace osu.Game.Screens.Multi.Components this.FadeInFromZero(transition_duration); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (Enabled.Value) { diff --git a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs index 1a47829ad7..3b3f789628 100644 --- a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs +++ b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs @@ -6,7 +6,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Multi.Screens.Lounge }; } - protected override void OnFocus(InputState state) + protected override void OnFocus(FocusEvent e) { GetContainingInputManager().ChangeFocus(Filter.Search); } diff --git a/osu.Game/Screens/Multi/Screens/Match/Header.cs b/osu.Game/Screens/Multi/Screens/Match/Header.cs index cc31f10fd2..d469815d59 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Header.cs +++ b/osu.Game/Screens/Multi/Screens/Match/Header.cs @@ -8,8 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -151,28 +150,28 @@ namespace osu.Game.Screens.Multi.Screens.Match border.BorderColour = colours.Yellow; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { border.FadeIn(transition_duration); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { - base.OnHoverLost(state); + base.OnHoverLost(e); border.FadeOut(transition_duration); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { bg.FadeTo(0.75f, 1000, Easing.Out); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { bg.FadeTo(bg_opacity, transition_duration); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 1ca3bc2189..2c984e6135 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -16,8 +16,7 @@ using OpenTK.Input; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.Bindings; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play @@ -28,9 +27,9 @@ namespace osu.Game.Screens.Play private const int button_height = 70; private const float background_alpha = 0.75f; - protected override bool BlockPassThroughKeyboard => true; + protected override bool BlockNonPositionalInput => true; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public Action OnRetry; public Action OnQuit; @@ -38,7 +37,7 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Children.Last().TriggerOnClick(); + protected virtual Action BackAction => () => InternalButtons.Children.Last().Click(); public abstract string Header { get; } public abstract string Description { get; } @@ -155,11 +154,11 @@ namespace osu.Game.Screens.Play protected override void PopOut() => this.FadeOut(transition_duration, Easing.In); // Don't let mouse down events through the overlay or people can click circles while paused. - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + protected override bool OnMouseUp(MouseUpEvent e) => true; - protected override bool OnMouseMove(InputState state) => true; + protected override bool OnMouseMove(MouseMoveEvent e) => true; protected void AddButton(string text, Color4 colour, Action action) { @@ -204,11 +203,11 @@ namespace osu.Game.Screens.Play } } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (!args.Repeat) + if (!e.Repeat) { - switch (args.Key) + switch (e.Key) { case Key.Up: if (selectionIndex == -1 || selectionIndex == 0) @@ -225,7 +224,7 @@ namespace osu.Game.Screens.Play } } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } public bool OnPressed(GlobalAction action) @@ -283,20 +282,20 @@ namespace osu.Game.Screens.Play private class Button : DialogButton { - protected override bool OnHover(InputState state) => true; + protected override bool OnHover(HoverEvent e) => true; - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { Selected.Value = true; - return base.OnMouseMove(state); + return base.OnMouseMove(e); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Repeat || args.Key != Key.Enter || !Selected) + if (e.Repeat || e.Key != Key.Enter || !Selected) return false; - OnClick(state); + Click(); return true; } } diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 1a164b473d..04f086282e 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.UI; using OpenTK; using osu.Game.Graphics.Containers; using System.Linq; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; namespace osu.Game.Screens.Play.HUD { @@ -93,16 +93,16 @@ namespace osu.Game.Screens.Play.HUD private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { expand(); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { contract(); - base.OnHoverLost(state); + base.OnHoverLost(e); } } } diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index e5e2ed7ee0..debce8c680 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -3,8 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using OpenTK; using osu.Game.Screens.Play.PlayerSettings; using OpenTK.Input; @@ -51,22 +50,22 @@ namespace osu.Game.Screens.Play.HUD protected override void PopOut() => this.FadeOut(fade_duration); //We want to handle keyboard inputs all the time in order to trigger ToggleVisibility() when not visible - public override bool HandleKeyboardInput => true; + public override bool HandleNonPositionalInput => true; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Repeat) return false; + if (e.Repeat) return false; - if (state.Keyboard.ControlPressed) + if (e.ControlPressed) { - if (args.Key == Key.H && ReplayLoaded) + if (e.Key == Key.H && ReplayLoaded) { ToggleVisibility(); return true; } } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } } } diff --git a/osu.Game/Screens/Play/HUD/QuitButton.cs b/osu.Game/Screens/Play/HUD/QuitButton.cs index 2a4b1f408d..88547e0169 100644 --- a/osu.Game/Screens/Play/HUD/QuitButton.cs +++ b/osu.Game/Screens/Play/HUD/QuitButton.cs @@ -8,8 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -20,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD { public class QuitButton : FillFlowContainer { - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private readonly Button button; @@ -62,10 +61,10 @@ namespace osu.Game.Screens.Play.HUD private float positionalAdjust; - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { - positionalAdjust = Vector2.Distance(state.Mouse.NativeState.Position, button.ScreenSpaceDrawQuad.Centre) / 200; - return base.OnMouseMove(state); + positionalAdjust = Vector2.Distance(e.ScreenSpaceMousePosition, button.ScreenSpaceDrawQuad.Centre) / 200; + return base.OnMouseMove(e); } protected override void Update() @@ -170,28 +169,28 @@ namespace osu.Game.Screens.Play.HUD }); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { HoverGained?.Invoke(); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { HoverLost?.Invoke(); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { - if (!pendingAnimation && state.Mouse.Buttons.Count() == 1) + if (!pendingAnimation && e.CurrentState.Mouse.Buttons.Count() == 1) BeginConfirm(); return true; } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { - if (!state.Mouse.Buttons.Any()) + if (!e.HasAnyButtonPressed) AbortConfirm(); return true; } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f51ea6fe3e..0f9f4fc20a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,8 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -69,7 +68,7 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new Drawable[] { - KeyCounter = CreateKeyCounter(), + KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), HoldToQuit = CreateQuitButton(), } } @@ -135,11 +134,13 @@ namespace osu.Game.Screens.Play { PlayerSettingsOverlay.Show(); ModDisplay.FadeIn(200); + KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 }; } else { PlayerSettingsOverlay.Hide(); ModDisplay.Delay(2000).FadeOut(200); + KeyCounter.Margin = new MarginPadding(10); } } @@ -152,13 +153,13 @@ namespace osu.Game.Screens.Play Progress.BindRulestContainer(rulesetContainer); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Repeat) return false; + if (e.Repeat) return false; - if (state.Keyboard.ShiftPressed) + if (e.ShiftPressed) { - switch (args.Key) + switch (e.Key) { case Key.Tab: showHud.Value = !showHud.Value; @@ -166,7 +167,7 @@ namespace osu.Game.Screens.Play } } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter @@ -194,12 +195,13 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection + protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection { FadeTime = 50, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), + AudioClock = offsetClock }; protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 2c31e61114..d1efe4cab7 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -1,6 +1,8 @@ // 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.Framework.Graphics.Containers; @@ -19,6 +21,9 @@ namespace osu.Game.Screens.Play private Container textLayer; private SpriteText countSpriteText; + private readonly List states = new List(); + private KeyCounterState currentState; + public bool IsCounting { get; set; } = true; private int countPresses; public int CountPresses @@ -45,7 +50,10 @@ namespace osu.Game.Screens.Play isLit = value; updateGlowSprite(value); if (value && IsCounting) + { CountPresses++; + saveState(); + } } } } @@ -128,6 +136,32 @@ namespace osu.Game.Screens.Play } } - public void ResetCount() => CountPresses = 0; + public void ResetCount() + { + CountPresses = 0; + states.Clear(); + } + + protected override void Update() + { + base.Update(); + + if (currentState?.Time > Clock.CurrentTime) + restoreStateTo(Clock.CurrentTime); + } + + private void saveState() + { + if (currentState == null || currentState.Time < Clock.CurrentTime) + states.Add(currentState = new KeyCounterState(Clock.CurrentTime, CountPresses)); + } + + private void restoreStateTo(double time) + { + states.RemoveAll(state => state.Time > time); + + currentState = states.LastOrDefault(); + CountPresses = currentState?.Count ?? 0; + } } } diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 5cb5bb152a..925f96f33b 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -3,15 +3,15 @@ using System; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Allocation; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Configuration; using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Screens.Play { @@ -37,6 +37,9 @@ namespace osu.Game.Screens.Play key.FadeTime = FadeTime; key.KeyDownTextColor = KeyDownTextColor; key.KeyUpTextColor = KeyUpTextColor; + // Use the same clock object as SongProgress for saving KeyCounter state + if (AudioClock != null) + key.Clock = AudioClock; } public void ResetCount() @@ -115,8 +118,10 @@ namespace osu.Game.Screens.Play private void updateVisibility() => this.FadeTo(Visible.Value || configVisibility.Value ? 1 : 0, duration); - public override bool HandleKeyboardInput => receptor == null; - public override bool HandleMouseInput => receptor == null; + public override bool HandleNonPositionalInput => receptor == null; + public override bool HandlePositionalInput => receptor == null; + + public IFrameBasedClock AudioClock { get; set; } private Receptor receptor; @@ -144,15 +149,20 @@ namespace osu.Game.Screens.Play Target = target; } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyDown(state, args)); - - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyUp(state, args)); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => Target.Children.Any(c => c.TriggerOnMouseDown(state, args)); - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => Target.Children.Any(c => c.TriggerOnMouseUp(state, args)); + protected override bool Handle(UIEvent e) + { + switch (e) + { + case KeyDownEvent _: + case KeyUpEvent _: + case MouseDownEvent _: + case MouseUpEvent _: + return Target.Children.Any(c => c.TriggerEvent(e)); + } + return base.Handle(e); + } } } } diff --git a/osu.Game/Screens/Play/KeyCounterKeyboard.cs b/osu.Game/Screens/Play/KeyCounterKeyboard.cs index 1c2f2c0ac4..725eae0367 100644 --- a/osu.Game/Screens/Play/KeyCounterKeyboard.cs +++ b/osu.Game/Screens/Play/KeyCounterKeyboard.cs @@ -1,8 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using OpenTK.Input; namespace osu.Game.Screens.Play @@ -15,16 +14,16 @@ namespace osu.Game.Screens.Play Key = key; } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Key == Key) IsLit = true; - return base.OnKeyDown(state, args); + if (e.Key == Key) IsLit = true; + return base.OnKeyDown(e); } - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + protected override bool OnKeyUp(KeyUpEvent e) { - if (args.Key == Key) IsLit = false; - return base.OnKeyUp(state, args); + if (e.Key == Key) IsLit = false; + return base.OnKeyUp(e); } } } diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 20cc53caee..c1ed5f84f4 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -1,8 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using OpenTK.Input; using OpenTK; @@ -17,7 +16,7 @@ namespace osu.Game.Screens.Play Button = button; } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private static string getStringRepresentation(MouseButton button) { @@ -32,16 +31,16 @@ namespace osu.Game.Screens.Play } } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { - if (args.Button == Button) IsLit = true; - return base.OnMouseDown(state, args); + if (e.Button == Button) IsLit = true; + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { - if (args.Button == Button) IsLit = false; - return base.OnMouseUp(state, args); + if (e.Button == Button) IsLit = false; + return base.OnMouseUp(e); } } } diff --git a/osu.Game/Screens/Play/KeyCounterState.cs b/osu.Game/Screens/Play/KeyCounterState.cs new file mode 100644 index 0000000000..e5c0703319 --- /dev/null +++ b/osu.Game/Screens/Play/KeyCounterState.cs @@ -0,0 +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.Screens.Play +{ + public class KeyCounterState + { + public KeyCounterState(double time, int count) + { + Time = time; + Count = count; + } + + public readonly double Time; + public readonly int Count; + } +} diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index d9677e5daf..25e701294a 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Play public override string Header => "paused"; public override string Description => "you're not going to do what i think you're going to do, are ya?"; - protected override Action BackAction => () => InternalButtons.Children.First().TriggerOnClick(); + protected override Action BackAction => () => InternalButtons.Children.First().Click(); [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5ad0130fd7..b3cbeb3850 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -12,7 +12,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; @@ -370,7 +370,7 @@ namespace osu.Game.Screens.Play Content.FadeOut(fadeOutDuration); } - protected override bool OnScroll(InputState state) => mouseWheelDisabled.Value && !pauseContainer.IsPaused; + protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !pauseContainer.IsPaused; private void initializeStoryboard(bool asyncLoad) { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fd4322c268..d87fb1db86 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -7,7 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Threading; @@ -136,21 +136,21 @@ namespace osu.Game.Screens.Play private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { // restore our screen defaults InitializeBackgroundElements(); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { if (GetContainingInputManager().HoveredDrawables.Contains(visualSettings)) { // show user setting preview UpdateBackgroundElements(); } - base.OnHoverLost(state); + base.OnHoverLost(e); } protected override void InitializeBackgroundElements() @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation) + private void load() { var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); @@ -307,7 +307,7 @@ namespace osu.Game.Screens.Play { new OsuSpriteText { - Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title), + Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), TextSize = 36, Font = @"Exo2.0-MediumItalic", Origin = Anchor.TopCentre, @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Play }, new OsuSpriteText { - Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist), + Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), TextSize = 26, Font = @"Exo2.0-MediumItalic", Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 64c49099f2..6e317ccfc9 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -5,8 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -137,16 +136,16 @@ namespace osu.Game.Screens.Play.PlayerSettings this.Delay(600).FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { this.FadeIn(fade_duration, Easing.OutQuint); return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { this.FadeTo(inactive_alpha, fade_duration, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } [BackgroundDependencyLoader] @@ -161,6 +160,6 @@ namespace osu.Game.Screens.Play.PlayerSettings protected override Container Content => content; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseDown(MouseDownEvent e) => true; } } diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 439e344020..f762597e81 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.PlayerSettings }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hit sounds" } }; } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 06837c9274..cd34623951 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -18,8 +18,7 @@ using OpenTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Framework.Input.Bindings; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play @@ -37,8 +36,8 @@ namespace osu.Game.Screens.Play private FadeContainer fadeContainer; private double displayTime; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - protected override bool BlockPassThroughMouse => false; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + protected override bool BlockPositionalInput => false; public SkipOverlay(double startTime) { @@ -129,11 +128,11 @@ namespace osu.Game.Screens.Play remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); } - protected override bool OnMouseMove(InputState state) + protected override bool OnMouseMove(MouseMoveEvent e) { - if (!state.Mouse.HasAnyButtonPressed) + if (!e.HasAnyButtonPressed) fadeContainer.State = Visibility.Visible; - return base.OnMouseMove(state); + return base.OnMouseMove(e); } public bool OnPressed(GlobalAction action) @@ -141,7 +140,7 @@ namespace osu.Game.Screens.Play switch (action) { case GlobalAction.SkipCutscene: - button.TriggerOnClick(); + button.Click(); return true; } @@ -194,16 +193,16 @@ namespace osu.Game.Screens.Play State = Visibility.Visible; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { scheduledHide?.Cancel(); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { State = Visibility.Visible; - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } } @@ -284,7 +283,7 @@ namespace osu.Game.Screens.Play }; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { flow.TransformSpacingTo(new Vector2(5), 500, Easing.OutQuint); box.FadeColour(colourHover, 500, Easing.OutQuint); @@ -292,27 +291,27 @@ namespace osu.Game.Screens.Play return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { flow.TransformSpacingTo(new Vector2(0), 500, Easing.OutQuint); box.FadeColour(colourNormal, 500, Easing.OutQuint); background.FadeTo(0.2f, 500, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { aspect.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (!Enabled) return false; @@ -322,7 +321,7 @@ namespace osu.Game.Screens.Play box.FlashColour(Color4.White, 500, Easing.OutQuint); aspect.ScaleTo(1.2f, 2000, Easing.OutQuint); - bool result = base.OnClick(state); + bool result = base.OnClick(e); // for now, let's disable the skip button after the first press. // this will likely need to be contextual in the future (bound from external components). diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 2ca471c5c1..d4615b3016 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play { private const int bottom_bar_height = 5; - private static readonly Vector2 handle_size = new Vector2(14, 25); + private static readonly Vector2 handle_size = new Vector2(10, 18); private const float transition_duration = 200; @@ -31,8 +31,8 @@ namespace osu.Game.Screens.Play public Action OnSeek; - public override bool HandleKeyboardInput => AllowSeeking; - public override bool HandleMouseInput => AllowSeeking; + public override bool HandleNonPositionalInput => AllowSeeking; + public override bool HandlePositionalInput => AllowSeeking; private IClock audioClock; public IClock AudioClock { set { audioClock = info.AudioClock = value; } } @@ -135,6 +135,8 @@ namespace osu.Game.Screens.Play { bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, Easing.In); this.MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, Easing.In); + + info.Margin = new MarginPadding { Bottom = Height - (allowSeeking ? 0 : handle_size.Y) }; } protected override void PopIn() diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 8ffd04b35c..bc4c87e191 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -21,9 +21,6 @@ namespace osu.Game.Screens.Play public int ColumnCount => columns.Length; - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; - private int progress; public int Progress { diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index 040458e0eb..58c9cde9c7 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -24,6 +23,7 @@ using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions; +using osu.Framework.Localisation; namespace osu.Game.Screens.Ranking { @@ -328,7 +328,7 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationEngine localisation) + private void load(OsuColour colours) { title.Colour = artist.Colour = colours.BlueDarker; versionMapper.Colour = colours.Gray8; @@ -341,8 +341,8 @@ namespace osu.Game.Screens.Ranking versionMapper.Text = $"{beatmap.Version} - " + versionMapper.Text; } - title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); - artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); + title.Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)); + artist.Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)); } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b6cbaf45e9..7c9053d2f0 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -17,8 +17,7 @@ using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -52,8 +51,8 @@ namespace osu.Game.Screens.Select /// public Action SelectionChanged; - public override bool HandleKeyboardInput => AllowSelection; - public override bool HandleMouseInput => AllowSelection; + public override bool HandleNonPositionalInput => AllowSelection; + public override bool HandlePositionalInput => AllowSelection; /// /// Used to avoid firing null selections before the initial beatmaps have been loaded via . @@ -381,12 +380,12 @@ namespace osu.Game.Screens.Select public void ScrollToSelected() => scrollPositionCache.Invalidate(); - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { int direction = 0; bool skipDifficulties = false; - switch (args.Key) + switch (e.Key) { case Key.Up: direction = -1; @@ -405,7 +404,7 @@ namespace osu.Game.Screens.Select } if (direction == 0) - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); SelectNext(direction, skipDifficulties); return true; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index f1bd2b945f..3999adfede 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -307,10 +307,10 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Alpha = 0; InternalChild = textContainer = new FillFlowContainer { + Alpha = 0, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(spacing / 2), @@ -327,11 +327,6 @@ namespace osu.Game.Screens.Select TextSize = 14, }, }, - textFlow = new OsuTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, }, }; } @@ -342,7 +337,7 @@ namespace osu.Game.Screens.Select { if (string.IsNullOrEmpty(value)) { - this.FadeOut(transition_duration); + textContainer.FadeOut(transition_duration); return; } @@ -364,7 +359,7 @@ namespace osu.Game.Screens.Select textContainer.Add(textFlow = loaded); // fade in if we haven't yet. - this.FadeIn(transition_duration); + textContainer.FadeIn(transition_duration); }); } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index d26702fcf9..81455833e9 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Select ruleset.ValueChanged += _ => updateDisplay(); } - protected override bool BlockPassThroughMouse => false; + protected override bool BlockPositionalInput => false; protected override void PopIn() { @@ -89,6 +89,8 @@ namespace osu.Game.Screens.Select } } + public override bool IsPresent => base.IsPresent || Info == null; // Visibility is updated in the LoadComponentAsync callback + private BufferedWedgeInfo loadingInfo; private void updateDisplay() @@ -127,11 +129,12 @@ namespace osu.Game.Screens.Select public OsuSpriteText VersionLabel { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } + public BeatmapSetOnlineStatusPill StatusPill { get; private set; } public FillFlowContainer MapperContainer { get; private set; } public FillFlowContainer InfoLabelContainer { get; private set; } - private UnicodeBindableString titleBinding; - private UnicodeBindableString artistBinding; + private ILocalisedBindableString titleBinding; + private ILocalisedBindableString artistBinding; private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; @@ -143,7 +146,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation) + private void load(LocalisationManager localisation) { var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); @@ -152,8 +155,8 @@ namespace osu.Game.Screens.Select CacheDrawnFrameBuffer = true; RelativeSizeAxes = Axes.Both; - titleBinding = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); - artistBinding = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); + titleBinding = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title))); + artistBinding = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist))); Children = new Drawable[] { @@ -190,7 +193,7 @@ namespace osu.Game.Screens.Select }, new FillFlowContainer { - Name = "Top-aligned metadata", + Name = "Topleft-aligned metadata", Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Direction = FillDirection.Vertical, @@ -207,6 +210,24 @@ namespace osu.Game.Screens.Select } }, new FillFlowContainer + { + Name = "Topright-aligned metadata", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 14, Left = 10, Right = 18, Bottom = 20 }, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + StatusPill = new BeatmapSetOnlineStatusPill + { + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapInfo.Status, + } + } + }, + new FillFlowContainer { Name = "Centre-aligned metadata", Anchor = Anchor.CentreLeft, @@ -244,8 +265,13 @@ namespace osu.Game.Screens.Select } } }; - artistBinding.ValueChanged += value => setMetadata(metadata.Source); - artistBinding.TriggerChange(); + + titleBinding.BindValueChanged(value => setMetadata(metadata.Source)); + artistBinding.BindValueChanged(value => setMetadata(metadata.Source), true); + + // no difficulty means it can't have a status to show + if (beatmapInfo.Version == null) + StatusPill.Hide(); } private void setMetadata(string source) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 6071e163cf..109ac25cd1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -155,12 +155,12 @@ namespace osu.Game.Screens.Select.Carousel triangles.Colour = OsuColour.Gray(0.5f); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { if (Item.State == CarouselItemState.Selected) startRequested?.Invoke(beatmap); - return base.OnClick(state); + return base.OnClick(e); } protected override void ApplyState() diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 23f338b530..ec2622285f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -38,11 +38,8 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader(true)] - private void load(LocalisationEngine localisation, BeatmapManager manager, BeatmapSetOverlay beatmapOverlay, DialogOverlay overlay) + private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay, DialogOverlay overlay) { - if (localisation == null) - throw new ArgumentNullException(nameof(localisation)); - restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); dialogOverlay = overlay; if (beatmapOverlay != null) @@ -67,22 +64,39 @@ namespace osu.Game.Screens.Select.Carousel new OsuSpriteText { Font = @"Exo2.0-BoldItalic", - Current = localisation.GetUnicodePreference(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title), + Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), TextSize = 22, Shadow = true, }, new OsuSpriteText { Font = @"Exo2.0-SemiBoldItalic", - Current = localisation.GetUnicodePreference(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist), + Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), TextSize = 17, Shadow = true, }, - new FillFlowContainer + new FillFlowContainer { - Margin = new MarginPadding { Top = 5 }, + Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Children = ((CarouselBeatmapSet)Item).Beatmaps.Select(b => new FilterableDifficultyIcon(b)).ToList() + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 5 }, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapSet.Status + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = ((CarouselBeatmapSet)Item).Beatmaps.Select(b => new FilterableDifficultyIcon(b)).ToList() + }, + } } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 8a0052559e..e11b342efe 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -8,7 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics; using OpenTK; @@ -72,18 +72,18 @@ namespace osu.Game.Screens.Select.Carousel hoverLayer.Colour = colours.Blue.Opacity(0.1f); } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { sampleHover?.Play(); hoverLayer.FadeIn(100, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { hoverLayer.FadeOut(1000, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } public void SetMultiplicativeAlpha(float alpha) => borderContainer.Alpha = alpha; @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Select.Carousel }; } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { Item.State.Value = CarouselItemState.Selected; return true; diff --git a/osu.Game/Screens/Select/Filter/GroupMode.cs b/osu.Game/Screens/Select/Filter/GroupMode.cs index 6e57843dfc..b3bd73ee59 100644 --- a/osu.Game/Screens/Select/Filter/GroupMode.cs +++ b/osu.Game/Screens/Select/Filter/GroupMode.cs @@ -21,8 +21,8 @@ namespace osu.Game.Screens.Select.Filter DateAdded, [Description("Difficulty")] Difficulty, - [Description("Favorites")] - Favorites, + [Description("Favourites")] + Favourites, [Description("Length")] Length, [Description("My Maps")] diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 9ba8b085f3..fce7af1400 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -14,8 +14,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -72,8 +71,8 @@ namespace osu.Game.Screens.Select private readonly SearchTextBox searchTextBox; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => - base.ReceiveMouseInputAt(screenSpacePos) || groupTabs.ReceiveMouseInputAt(screenSpacePos) || sortTabs.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) || groupTabs.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); public FilterControl() { @@ -187,10 +186,10 @@ namespace osu.Game.Screens.Select private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnMouseMove(InputState state) => true; + protected override bool OnMouseMove(MouseMoveEvent e) => true; - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; } } diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index bc4d216f00..5fe1aa31ac 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -11,8 +11,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Select @@ -139,8 +138,8 @@ namespace osu.Game.Screens.Select updateModeLight(); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnClick(InputState state) => true; + protected override bool OnClick(ClickEvent e) => true; } } diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 1b0e3a1620..4f12082e08 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -8,8 +8,7 @@ using OpenTK.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; @@ -56,7 +55,7 @@ namespace osu.Game.Screens.Select private readonly Box box; private readonly Box light; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public FooterButton() { @@ -89,7 +88,7 @@ namespace osu.Game.Screens.Select public Action HoverLost; public Key? Hotkey; - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { Hovered?.Invoke(); light.ScaleTo(new Vector2(1, 2), Footer.TRANSITION_LENGTH, Easing.OutQuint); @@ -97,42 +96,42 @@ namespace osu.Game.Screens.Select return true; } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { HoverLost?.Invoke(); light.ScaleTo(new Vector2(1, 1), Footer.TRANSITION_LENGTH, Easing.OutQuint); light.FadeColour(DeselectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint); } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { box.FadeTo(0.3f, Footer.TRANSITION_LENGTH * 2, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { box.FadeOut(Footer.TRANSITION_LENGTH, Easing.OutQuint); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { box.ClearTransforms(); box.Alpha = 1; box.FadeOut(Footer.TRANSITION_LENGTH * 3, Easing.OutQuint); - return base.OnClick(state); + return base.OnClick(e); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (!args.Repeat && args.Key == Hotkey) + if (!e.Repeat && e.Key == Hotkey) { - OnClick(state); + Click(); return true; } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } } } diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index 95e9dde68e..241f6da3d1 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -251,16 +251,16 @@ namespace osu.Game.Screens.Select.Leaderboards } } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { background.FadeTo(0.5f, 300, Easing.OutQuint); - return base.OnHover(state); + return base.OnHover(e); } - protected override void OnHoverLost(InputState state) + protected override void OnHoverLost(HoverLostEvent e) { background.FadeTo(background_alpha, 200, Easing.OutQuint); - base.OnHoverLost(state); + base.OnHoverLost(e); } private class GlowingSpriteText : Container diff --git a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs index 307986a299..468b43e54f 100644 --- a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs +++ b/osu.Game/Screens/Select/Leaderboards/Placeholder.cs @@ -11,8 +11,6 @@ namespace osu.Game.Screens.Select.Leaderboards { protected const float TEXT_SIZE = 22; - public override bool HandleMouseInput => true; - protected Placeholder() : base(cp => cp.TextSize = TEXT_SIZE) { diff --git a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs index 19cba72f1f..6601c9df8b 100644 --- a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs @@ -3,8 +3,7 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using OpenTK; @@ -49,16 +48,16 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { icon.ScaleTo(0.8f, 4000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { icon.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } } } diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index d4cd882433..cd775419be 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -5,8 +5,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; @@ -53,39 +52,39 @@ namespace osu.Game.Screens.Select.Options public Key? HotKey; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(MouseDownEvent e) { flash.FadeTo(0.1f, 1000, Easing.OutQuint); - return base.OnMouseDown(state, args); + return base.OnMouseDown(e); } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + protected override bool OnMouseUp(MouseUpEvent e) { flash.FadeTo(0, 1000, Easing.OutQuint); - return base.OnMouseUp(state, args); + return base.OnMouseUp(e); } - protected override bool OnClick(InputState state) + protected override bool OnClick(ClickEvent e) { flash.ClearTransforms(); flash.Alpha = 0.9f; flash.FadeOut(800, Easing.OutExpo); - return base.OnClick(state); + return base.OnClick(e); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (!args.Repeat && args.Key == HotKey) + if (!e.Repeat && e.Key == HotKey) { - OnClick(state); + Click(); return true; } return false; } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public BeatmapOptionsButton() { diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 2c43b333aa..285410ce20 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Edit; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Skinning; @@ -65,10 +64,10 @@ namespace osu.Game.Screens.Select BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1); BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, null, Key.Number2); - BeatmapOptions.AddButton(@"Edit", @"Beatmap", FontAwesome.fa_pencil, colours.Yellow, () => + BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.fa_pencil, colours.Yellow, () => { ValidForResume = false; - Push(new Editor()); + Edit(); }, Key.Number3); if (dialogOverlay != null) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index efdf55e477..acf699aa24 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -13,8 +13,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; -using osu.Framework.Input.EventArgs; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -205,7 +204,7 @@ namespace osu.Game.Screens.Select Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); - BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); } if (this.beatmaps == null) @@ -224,9 +223,9 @@ namespace osu.Game.Screens.Select Carousel.LoadBeatmapSetsFromManager(this.beatmaps); } - public void Edit(BeatmapInfo beatmap) + public void Edit(BeatmapInfo beatmap = null) { - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); Push(new Editor()); } @@ -351,7 +350,7 @@ namespace osu.Game.Screens.Select } } - ensurePlayingSelected(preview); + if (IsCurrentScreen) ensurePlayingSelected(preview); UpdateBeatmap(Beatmap.Value); } @@ -536,7 +535,7 @@ namespace osu.Game.Screens.Select private void delete(BeatmapSetInfo beatmap) { - if (beatmap == null) return; + if (beatmap == null || beatmap.ID <= 0) return; dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } @@ -554,14 +553,14 @@ namespace osu.Game.Screens.Select return base.OnPressed(action); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override bool OnKeyDown(KeyDownEvent e) { - if (args.Repeat) return false; + if (e.Repeat) return false; - switch (args.Key) + switch (e.Key) { case Key.Delete: - if (state.Keyboard.ShiftPressed) + if (e.ShiftPressed) { if (!Beatmap.IsDefault) delete(Beatmap.Value.BeatmapSetInfo); @@ -571,7 +570,7 @@ namespace osu.Game.Screens.Select break; } - return base.OnKeyDown(state, args); + return base.OnKeyDown(e); } private class ResetScrollContainer : Container @@ -583,10 +582,10 @@ namespace osu.Game.Screens.Select this.onHoverAction = onHoverAction; } - protected override bool OnHover(InputState state) + protected override bool OnHover(HoverEvent e) { onHoverAction?.Invoke(); - return base.OnHover(state); + return base.OnHover(e); } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ce7edf8683..bd7ca22fa1 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Database; +using osu.Game.Graphics.Sprites; using OpenTK; namespace osu.Game.Skinning @@ -56,30 +57,39 @@ namespace osu.Game.Skinning case "Play/Great": componentName = "hit300"; break; + case "Play/osu/number-text": + return !hasFont(Configuration.HitCircleFont) ? null : new LegacySpriteText(Textures, Configuration.HitCircleFont) { Scale = new Vector2(0.96f) }; } - float ratio = 0.72f; // brings sizing roughly in-line with stable + var texture = GetTexture(componentName); - var texture = GetTexture($"{componentName}@2x"); if (texture == null) - { - ratio *= 2; - texture = GetTexture(componentName); - } + return null; - if (texture == null) return null; - - return new Sprite - { - Texture = texture, - Scale = new Vector2(ratio), - }; + return new Sprite { Texture = texture }; } - public override Texture GetTexture(string componentName) => Textures.Get(componentName); + public override Texture GetTexture(string componentName) + { + float ratio = 2; + + var texture = Textures.Get($"{componentName}@2x"); + if (texture == null) + { + ratio = 1; + texture = Textures.Get(componentName); + } + + if (texture != null) + texture.ScaleAdjust = ratio / 0.72f; // brings sizing roughly in-line with stable + + return texture; + } public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName); + private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; + protected class LegacySkinResourceStore : IResourceStore where T : INamedFileInfo { @@ -142,5 +152,40 @@ namespace osu.Game.Skinning #endregion } + + private class LegacySpriteText : OsuSpriteText + { + private readonly TextureStore textures; + private readonly string font; + + public LegacySpriteText(TextureStore textures, string font) + { + this.textures = textures; + this.font = font; + + Shadow = false; + UseFullGlyphHeight = false; + } + + protected override Texture GetTextureForCharacter(char c) + { + string textureName = $"{font}-{c}"; + + // Approximate value that brings character sizing roughly in-line with stable + float ratio = 36; + + var texture = textures.Get($"{textureName}@2x"); + if (texture == null) + { + ratio = 18; + texture = textures.Get(textureName); + } + + if (texture != null) + texture.ScaleAdjust = ratio; + + return texture; + } + } } } diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index d4f1c5c6f1..67a031fb50 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -19,6 +19,7 @@ namespace osu.Game.Skinning switch (section) { case Section.General: + { var pair = SplitKeyVal(line); switch (pair.Key) @@ -32,6 +33,20 @@ namespace osu.Game.Skinning } break; + } + case Section.Fonts: + { + var pair = SplitKeyVal(line); + + switch (pair.Key) + { + case "HitCirclePrefix": + skin.HitCircleFont = pair.Value; + break; + } + + break; + } } base.ParseLine(skin, section, line); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index ac59fcc7db..40f5c158cc 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -14,5 +14,7 @@ namespace osu.Game.Skinning public List ComboColours { get; set; } = new List(); public Dictionary CustomColours { get; set; } = new Dictionary(); + + public string HitCircleFont { get; set; } = "default"; } } diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index a40c3da82d..5195daee65 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -18,6 +18,11 @@ namespace osu.Game.Skinning public class SkinnableDrawable : SkinReloadableDrawable where T : Drawable { + /// + /// The displayed component. May or may not be a type- member. + /// + protected Drawable Drawable { get; private set; } + private readonly Func createDefault; private readonly string componentName; @@ -31,7 +36,8 @@ namespace osu.Game.Skinning /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// Whether a user-skin drawable should be limited to the size of our parent. - public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) : base(allowFallback) + public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) + : base(allowFallback) { componentName = name; createDefault = defaultImplementation; @@ -42,26 +48,27 @@ namespace osu.Game.Skinning protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - var drawable = skin.GetDrawableComponent(componentName); - if (drawable != null) + Drawable = skin.GetDrawableComponent(componentName); + + if (Drawable != null) { if (restrictSize) { - drawable.RelativeSizeAxes = Axes.Both; - drawable.Size = Vector2.One; - drawable.Scale = Vector2.One; - drawable.FillMode = FillMode.Fit; + Drawable.RelativeSizeAxes = Axes.Both; + Drawable.Size = Vector2.One; + Drawable.Scale = Vector2.One; + Drawable.FillMode = FillMode.Fit; } } else if (allowFallback) - drawable = createDefault(componentName); + Drawable = createDefault(componentName); - if (drawable != null) + if (Drawable != null) { - drawable.Origin = Anchor.Centre; - drawable.Anchor = Anchor.Centre; + Drawable.Origin = Anchor.Centre; + Drawable.Anchor = Anchor.Centre; - InternalChild = drawable; + InternalChild = Drawable; } else ClearInternal(); diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs new file mode 100644 index 0000000000..8b09417bed --- /dev/null +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -0,0 +1,40 @@ +// 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.Sprites; + +namespace osu.Game.Skinning +{ + public class SkinnableSpriteText : SkinnableDrawable, IHasText + { + public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) + : base(name, defaultImplementation, allowFallback, restrictSize) + { + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + if (Drawable is IHasText textDrawable) + textDrawable.Text = Text; + } + + private string text; + + public string Text + { + get => text; + set + { + if (text == value) + return; + text = value; + + if (Drawable is IHasText textDrawable) + textDrawable.Text = value; + } + } + } +} diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index 7de0756dbd..64b0a45fcd 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -16,13 +16,13 @@ namespace osu.Game.Storyboards public bool HasCommands => commands.Count > 0; private Cached startTimeBacking; - public double StartTime => startTimeBacking.IsValid ? startTimeBacking : (startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue); + public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue; private Cached endTimeBacking; - public double EndTime => endTimeBacking.IsValid ? endTimeBacking : (endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue); + public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue; - public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default(T); - public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default(T); + public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default; + public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default; public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue) { diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 37c198f370..ef03539998 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -18,8 +18,6 @@ namespace osu.Game.Storyboards.Drawables protected override Container Content => content; protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480); - public override bool HandleKeyboardInput => false; - public override bool HandleMouseInput => false; private bool passing = true; public bool Passing diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index cdec8c042f..4f469ae593 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -41,7 +41,7 @@ namespace osu.Game.Storyboards.Drawables { base.Update(); - // TODO: this logic will need to be consolidated with other game samples like hitsounds. + // TODO: this logic will need to be consolidated with other game samples like hit sounds. if (Time.Current < sample.Time) { // We've rewound before the start time of the sample diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs index 0ca7ff011f..479ed385e2 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs @@ -2,12 +2,11 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Input.States; +using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Screens.Compose; namespace osu.Game.Tests.Visual { @@ -56,9 +55,9 @@ namespace osu.Game.Tests.Visual Clock.ProcessFrame(); } - protected override bool OnScroll(InputState state) + protected override bool OnScroll(ScrollEvent e) { - if (state.Mouse.ScrollDelta.Y > 0) + if (e.ScrollDelta.Y > 0) Clock.SeekBackward(true); else Clock.SeekForward(true); diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestCase.cs index 2ab121fcc9..2d02509b58 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestCase.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Screens; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs new file mode 100644 index 0000000000..f893d8456b --- /dev/null +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs @@ -0,0 +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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit.Compose; + +namespace osu.Game.Tests.Visual +{ + [Cached(Type = typeof(IPlacementHandler))] + public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler + { + private readonly Container hitObjectContainer; + private PlacementBlueprint currentBlueprint; + + protected PlacementBlueprintTestCase() + { + Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; + + Add(hitObjectContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(new StopwatchClock()) + }); + } + + [BackgroundDependencyLoader] + private void load() + { + Add(currentBlueprint = CreateBlueprint()); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new StopwatchClock()); + + return dependencies; + } + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) + { + hitObjectContainer.Add(CreateHitObject(hitObject)); + + Remove(currentBlueprint); + Add(currentBlueprint = CreateBlueprint()); + } + + public void Delete(HitObject hitObject) + { + } + + protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); + protected abstract PlacementBlueprint CreateBlueprint(); + } +} diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs new file mode 100644 index 0000000000..b1df849a67 --- /dev/null +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs @@ -0,0 +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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Tests.Visual +{ + public abstract class SelectionBlueprintTestCase : OsuTestCase + { + private SelectionBlueprint blueprint; + + protected override Container Content => content ?? base.Content; + private readonly Container content; + + protected SelectionBlueprintTestCase() + { + base.Content.Add(content = new Container + { + Clock = new FramedClock(new StopwatchClock()), + RelativeSizeAxes = Axes.Both + }); + } + + [BackgroundDependencyLoader] + private void load() + { + base.Content.Add(blueprint = CreateBlueprint()); + blueprint.SelectionRequested += (_, __) => blueprint.Select(); + + AddStep("Select", () => blueprint.Select()); + AddStep("Deselect", () => blueprint.Deselect()); + } + + protected override bool OnClick(ClickEvent e) + { + blueprint.Deselect(); + return true; + } + + protected abstract SelectionBlueprint CreateBlueprint(); + } +} diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index f42df4023f..a5d8c03a67 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -83,8 +83,8 @@ namespace osu.Game.Users [JsonProperty(@"location")] public string Location; - [JsonProperty(@"lastvisit")] - public DateTimeOffset LastVisit; + [JsonProperty(@"last_visit")] + public DateTimeOffset? LastVisit; [JsonProperty(@"twitter")] public string Twitter; @@ -144,5 +144,14 @@ namespace osu.Game.Users public Badge[] Badges; public override string ToString() => Username; + + /// + /// A user instance for displaying locally created system messages. + /// + public static readonly User SYSTEM_USER = new User + { + Username = "system", + Id = 0 + }; } } diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index b28dd1fb73..c6e6d1e9d7 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using osu.Framework.Logging; using SharpRaven; @@ -35,6 +36,16 @@ namespace osu.Game.Utils if (exception != null) { + if (exception is IOException ioe) + { + // disk full exceptions, see https://stackoverflow.com/a/9294382 + const int hr_error_handle_disk_full = unchecked((int)0x80070027); + const int hr_error_disk_full = unchecked((int)0x80070070); + + if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) + return; + } + // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace)) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 83ab5534c6..c9461ea504 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,13 +14,13 @@ - - - + + + - + - + diff --git a/osu.TestProject.props b/osu.TestProject.props index 58de6ec030..456ecfd468 100644 --- a/osu.TestProject.props +++ b/osu.TestProject.props @@ -10,10 +10,6 @@ - - - - VisualTestRunner.cs diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 1f1b6a79b1..345400305c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -218,6 +218,7 @@ GMT QAT BNG + UI HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> @@ -665,6 +666,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste True True True + True True True True diff --git a/tools/cakebuild.csproj b/tools/cakebuild.csproj new file mode 100644 index 0000000000..8ccce35e26 --- /dev/null +++ b/tools/cakebuild.csproj @@ -0,0 +1,11 @@ + + + Exe + true + netcoreapp2.0 + + + + + + \ No newline at end of file