2019-10-11 14:27:23 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2022-06-17 15:37:17 +08:00
#nullable disable
2019-10-11 14:27:23 +08:00
using System ;
2022-10-26 12:08:19 +08:00
using System.Linq ;
2019-10-11 14:27:23 +08:00
using NUnit.Framework ;
using osu.Framework.Allocation ;
using osu.Framework.Graphics ;
2019-10-11 16:11:37 +08:00
using osu.Framework.Graphics.Containers ;
2023-08-19 21:08:30 +08:00
using osu.Framework.Graphics.Cursor ;
2019-10-11 14:27:23 +08:00
using osu.Framework.Graphics.Shapes ;
2022-10-26 12:08:19 +08:00
using osu.Framework.Graphics.UserInterface ;
2022-05-05 17:41:25 +08:00
using osu.Framework.Input ;
2022-10-26 12:08:19 +08:00
using osu.Framework.Testing ;
2020-01-09 12:43:44 +08:00
using osu.Framework.Utils ;
2019-10-11 14:27:23 +08:00
using osu.Game.Beatmaps.ControlPoints ;
2024-08-04 22:37:54 +08:00
using osu.Game.Graphics.Cursor ;
2022-10-13 15:57:37 +08:00
using osu.Game.Overlays ;
2019-10-25 16:25:46 +08:00
using osu.Game.Rulesets.Edit ;
2019-10-11 14:27:23 +08:00
using osu.Game.Rulesets.Osu.Beatmaps ;
2019-10-11 16:13:28 +08:00
using osu.Game.Rulesets.Osu.Edit ;
2019-10-11 14:27:23 +08:00
using osu.Game.Rulesets.Osu.Objects ;
using osu.Game.Screens.Edit ;
using osu.Game.Tests.Visual ;
using osuTK ;
using osuTK.Graphics ;
2020-09-25 17:48:04 +08:00
namespace osu.Game.Rulesets.Osu.Tests.Editor
2019-10-11 14:27:23 +08:00
{
2020-03-23 09:01:33 +08:00
public partial class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene
2019-10-11 14:27:23 +08:00
{
2022-05-05 17:41:25 +08:00
private const float beat_length = 100 ;
2022-05-05 17:32:37 +08:00
2019-10-11 14:27:23 +08:00
private static readonly Vector2 grid_position = new Vector2 ( 512 , 384 ) ;
2019-12-27 18:46:33 +08:00
[Cached(typeof(EditorBeatmap))]
2022-05-05 17:32:37 +08:00
[Cached(typeof(IBeatSnapProvider))]
2019-12-27 18:39:30 +08:00
private readonly EditorBeatmap editorBeatmap ;
2019-10-11 14:27:23 +08:00
2022-10-13 15:57:37 +08:00
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider ( OverlayColourScheme . Aquamarine ) ;
2022-05-05 17:32:37 +08:00
[Cached]
private readonly EditorClock editorClock ;
2019-10-11 14:27:23 +08:00
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor ( ) ;
2023-10-19 17:20:10 +08:00
private readonly TestHitObjectComposer composer = new TestHitObjectComposer
2022-05-05 17:32:37 +08:00
{
// Just used for the snap implementation, so let's hide from vision.
AlwaysPresent = true ,
Alpha = 0 ,
} ;
2019-10-25 16:25:46 +08:00
2022-05-05 16:00:36 +08:00
private OsuDistanceSnapGrid grid ;
2022-10-26 12:08:19 +08:00
private SnappingCursorContainer cursor ;
2019-10-11 14:27:23 +08:00
2019-10-17 14:32:02 +08:00
public TestSceneOsuDistanceSnapGrid ( )
2019-10-11 14:27:23 +08:00
{
2022-01-12 22:17:35 +08:00
editorBeatmap = new EditorBeatmap ( new OsuBeatmap
{
BeatmapInfo =
{
Ruleset = new OsuRuleset ( ) . RulesetInfo
}
} ) ;
2022-05-05 17:32:37 +08:00
base . Content . Children = new Drawable [ ]
{
2022-08-17 14:40:01 +08:00
editorClock = new EditorClock ( editorBeatmap ) ,
2023-10-19 17:20:10 +08:00
new PopoverContainer { Child = composer } ,
2022-05-05 17:32:37 +08:00
Content
} ;
2019-11-06 15:20:13 +08:00
}
2023-10-19 17:20:10 +08:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent )
{
var dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
dependencies . CacheAs ( composer . DistanceSnapProvider ) ;
2024-08-04 22:37:54 +08:00
dependencies . Cache ( new OsuContextMenuContainer ( ) ) ;
2023-10-19 17:20:10 +08:00
return dependencies ;
}
2023-08-19 21:08:30 +08:00
protected override Container < Drawable > Content { get ; } = new PopoverContainer { RelativeSizeAxes = Axes . Both } ;
2022-05-05 17:32:37 +08:00
2019-11-06 15:20:13 +08:00
[SetUp]
public void Setup ( ) = > Schedule ( ( ) = >
{
2021-10-02 11:34:29 +08:00
editorBeatmap . Difficulty . SliderMultiplier = 1 ;
2019-11-06 15:20:13 +08:00
editorBeatmap . ControlPointInfo . Clear ( ) ;
editorBeatmap . ControlPointInfo . Add ( 0 , new TimingControlPoint { BeatLength = beat_length } ) ;
2023-10-19 17:20:10 +08:00
composer . DistanceSnapProvider . DistanceSpacingMultiplier . Value = 1 ;
2019-10-11 16:11:37 +08:00
2019-10-25 16:25:46 +08:00
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = Color4 . SlateGray
} ,
2022-10-26 12:08:19 +08:00
cursor = new SnappingCursorContainer { GetSnapPosition = v = > grid . GetSnappedPosition ( grid . ToLocalSpace ( v ) ) . position } ,
2022-05-05 16:00:36 +08:00
grid = new OsuDistanceSnapGrid ( new HitCircle { Position = grid_position } ) ,
2019-10-25 16:25:46 +08:00
} ;
2019-10-11 14:27:23 +08:00
} ) ;
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestBeatDivisor ( int divisor )
{
AddStep ( $"set beat divisor = {divisor}" , ( ) = > beatDivisor . Value = divisor ) ;
2019-10-11 16:11:37 +08:00
}
2022-05-05 16:42:50 +08:00
[TestCase(1.0f)]
[TestCase(2.0f)]
[TestCase(0.5f)]
public void TestDistanceSpacing ( float multiplier )
{
2023-10-19 17:20:10 +08:00
AddStep ( $"set distance spacing = {multiplier}" , ( ) = > composer . DistanceSnapProvider . DistanceSpacingMultiplier . Value = multiplier ) ;
2022-05-05 16:42:50 +08:00
}
2019-10-11 16:11:37 +08:00
[Test]
public void TestCursorInCentre ( )
{
AddStep ( "move mouse to centre" , ( ) = > InputManager . MoveMouseTo ( grid . ToScreenSpace ( grid_position ) ) ) ;
2022-05-12 12:04:17 +08:00
assertSnappedDistance ( beat_length ) ;
}
[Test]
public void TestCursorAlmostInCentre ( )
{
AddStep ( "move mouse to almost centre" , ( ) = > InputManager . MoveMouseTo ( grid . ToScreenSpace ( grid_position ) + new Vector2 ( 1 ) ) ) ;
assertSnappedDistance ( beat_length ) ;
2019-10-11 16:11:37 +08:00
}
[Test]
public void TestCursorBeforeMovementPoint ( )
{
2022-05-05 17:41:25 +08:00
AddStep ( "move mouse to just before movement point" , ( ) = > InputManager . MoveMouseTo ( grid . ToScreenSpace ( grid_position + new Vector2 ( beat_length , 0 ) * 1.45f ) ) ) ;
assertSnappedDistance ( beat_length ) ;
2019-10-11 16:11:37 +08:00
}
[Test]
public void TestCursorAfterMovementPoint ( )
{
2022-05-05 17:41:25 +08:00
AddStep ( "move mouse to just after movement point" , ( ) = > InputManager . MoveMouseTo ( grid . ToScreenSpace ( grid_position + new Vector2 ( beat_length , 0 ) * 1.55f ) ) ) ;
assertSnappedDistance ( beat_length * 2 ) ;
}
[TestCase(0.5f, beat_length * 2)]
[TestCase(1, beat_length * 2)]
[TestCase(1.5f, beat_length * 1.5f)]
[TestCase(2f, beat_length * 2)]
public void TestDistanceSpacingAdjust ( float multiplier , float expectedDistance )
{
2023-10-19 17:20:10 +08:00
AddStep ( $"Set distance spacing to {multiplier}" , ( ) = > composer . DistanceSnapProvider . DistanceSpacingMultiplier . Value = multiplier ) ;
2022-05-05 17:41:25 +08:00
AddStep ( "move mouse to point" , ( ) = > InputManager . MoveMouseTo ( grid . ToScreenSpace ( grid_position + new Vector2 ( beat_length , 0 ) * 2 ) ) ) ;
assertSnappedDistance ( expectedDistance ) ;
2019-10-11 16:11:37 +08:00
}
2022-10-26 12:08:19 +08:00
[Test]
public void TestReferenceObjectNotOnSnapGrid ( )
{
AddStep ( "create grid" , ( ) = >
{
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = Color4 . SlateGray
} ,
cursor = new SnappingCursorContainer { GetSnapPosition = v = > grid . GetSnappedPosition ( grid . ToLocalSpace ( v ) ) . position } ,
grid = new OsuDistanceSnapGrid ( new HitCircle
{
Position = grid_position ,
// This is important. It sets the reference object to a point in time that isn't on the current snap divisor's grid.
// We are testing that the grid's display is offset correctly.
StartTime = 40 ,
} ) ,
} ;
} ) ;
AddStep ( "move mouse to point" , ( ) = > InputManager . MoveMouseTo ( grid . ToScreenSpace ( grid_position + new Vector2 ( beat_length , 0 ) * 2 ) ) ) ;
AddAssert ( "Ensure cursor is on a grid line" , ( ) = >
{
2023-06-20 00:42:30 +08:00
return grid . ChildrenOfType < CircularProgress > ( ) . Any ( ring = >
{
// the grid rings are actually slightly _larger_ than the snapping radii.
// this is done such that the snapping radius falls right in the middle of each grid ring thickness-wise,
// but it does however complicate the following calculations slightly.
// we want to calculate the coordinates of the rightmost point on the grid line, which is in the exact middle of the ring thickness-wise.
// for the X component, we take the entire width of the ring, minus one half of the inner radius (since we want the middle of the line on the right side).
// for the Y component, we just take 0.5f.
var rightMiddleOfGridLine = ring . ToScreenSpace ( ring . DrawSize * new Vector2 ( 1 - ring . InnerRadius / 2 , 0.5f ) ) ;
return Precision . AlmostEquals ( rightMiddleOfGridLine . X , grid . ToScreenSpace ( cursor . LastSnappedPosition ) . X ) ;
} ) ;
2022-10-26 12:08:19 +08:00
} ) ;
}
2019-11-06 15:20:13 +08:00
[Test]
public void TestLimitedDistance ( )
{
AddStep ( "create limited grid" , ( ) = >
{
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = Color4 . SlateGray
} ,
2022-10-26 12:08:19 +08:00
cursor = new SnappingCursorContainer { GetSnapPosition = v = > grid . GetSnappedPosition ( grid . ToLocalSpace ( v ) ) . position } ,
2022-05-05 16:00:36 +08:00
grid = new OsuDistanceSnapGrid ( new HitCircle { Position = grid_position } , new HitCircle { StartTime = 200 } ) ,
2019-11-06 15:20:13 +08:00
} ;
} ) ;
2022-05-05 17:41:25 +08:00
AddStep ( "move mouse outside grid" , ( ) = > InputManager . MoveMouseTo ( grid . ToScreenSpace ( grid_position + new Vector2 ( beat_length , 0 ) * 3f ) ) ) ;
2022-05-06 17:44:25 +08:00
assertSnappedDistance ( beat_length ) ;
2019-11-06 15:20:13 +08:00
}
2019-10-11 16:11:37 +08:00
private void assertSnappedDistance ( float expectedDistance ) = > AddAssert ( $"snap distance = {expectedDistance}" , ( ) = >
{
2019-10-25 11:34:49 +08:00
Vector2 snappedPosition = grid . GetSnappedPosition ( grid . ToLocalSpace ( InputManager . CurrentState . Mouse . Position ) ) . position ;
2019-10-11 16:11:37 +08:00
2019-10-25 11:34:49 +08:00
return Precision . AlmostEquals ( expectedDistance , Vector2 . Distance ( snappedPosition , grid_position ) ) ;
2019-10-11 16:11:37 +08:00
} ) ;
private partial class SnappingCursorContainer : CompositeDrawable
{
public Func < Vector2 , Vector2 > GetSnapPosition ;
2022-10-26 12:08:19 +08:00
public Vector2 LastSnappedPosition { get ; private set ; }
2019-10-11 16:11:37 +08:00
private readonly Drawable cursor ;
2022-05-05 17:41:25 +08:00
private InputManager inputManager ;
public override bool HandlePositionalInput = > true ;
2019-10-11 16:11:37 +08:00
public SnappingCursorContainer ( )
{
RelativeSizeAxes = Axes . Both ;
InternalChild = cursor = new Circle
{
Origin = Anchor . Centre ,
Size = new Vector2 ( 50 ) ,
Colour = Color4 . Red
} ;
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2022-05-05 17:41:25 +08:00
inputManager = GetContainingInputManager ( ) ;
2019-10-11 16:11:37 +08:00
}
2022-05-05 17:41:25 +08:00
protected override void Update ( )
2019-10-11 16:11:37 +08:00
{
2022-05-05 17:41:25 +08:00
base . Update ( ) ;
2022-10-26 12:08:19 +08:00
cursor . Position = LastSnappedPosition = GetSnapPosition . Invoke ( inputManager . CurrentState . Mouse . Position ) ;
2019-10-11 16:11:37 +08:00
}
}
2023-10-19 17:20:10 +08:00
private partial class TestHitObjectComposer : OsuHitObjectComposer
{
public new IDistanceSnapProvider DistanceSnapProvider = > base . DistanceSnapProvider ;
public TestHitObjectComposer ( )
: base ( new OsuRuleset ( ) )
{
}
}
2019-10-11 14:27:23 +08:00
}
}