1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 05:22:54 +08:00

Merge branch 'master' into fix-toolbox-expansion

This commit is contained in:
Salman Ahmed 2022-05-06 17:54:14 +03:00 committed by GitHub
commit f222affe88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 224 additions and 113 deletions

View File

@ -4,15 +4,13 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -25,19 +23,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene
{ {
private const double beat_length = 100; private const float beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384); private static readonly Vector2 grid_position = new Vector2(512, 384);
[Cached(typeof(EditorBeatmap))] [Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap; private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly EditorClock editorClock;
[Cached] [Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
[Cached(typeof(IDistanceSnapProvider))] [Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider(); private readonly OsuHitObjectComposer snapProvider = new OsuHitObjectComposer(new OsuRuleset())
{
// Just used for the snap implementation, so let's hide from vision.
AlwaysPresent = true,
Alpha = 0,
};
private TestOsuDistanceSnapGrid grid; private OsuDistanceSnapGrid grid;
public TestSceneOsuDistanceSnapGrid() public TestSceneOsuDistanceSnapGrid()
{ {
@ -48,14 +56,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
Ruleset = new OsuRuleset().RulesetInfo Ruleset = new OsuRuleset().RulesetInfo
} }
}); });
editorClock = new EditorClock(editorBeatmap);
base.Content.Children = new Drawable[]
{
snapProvider,
Content
};
} }
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
[SetUp] [SetUp]
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
{ {
editorBeatmap.Difficulty.SliderMultiplier = 1; editorBeatmap.Difficulty.SliderMultiplier = 1;
editorBeatmap.ControlPointInfo.Clear(); editorBeatmap.ControlPointInfo.Clear();
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
snapProvider.DistanceSpacingMultiplier.Value = 1;
Children = new Drawable[] Children = new Drawable[]
{ {
@ -64,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray Colour = Color4.SlateGray
}, },
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
}; };
}); });
@ -82,25 +101,45 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor); AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor);
} }
[TestCase(1.0f)]
[TestCase(2.0f)]
[TestCase(0.5f)]
public void TestDistanceSpacing(float multiplier)
{
AddStep($"set distance spacing = {multiplier}", () => snapProvider.DistanceSpacingMultiplier.Value = multiplier);
}
[Test] [Test]
public void TestCursorInCentre() public void TestCursorInCentre()
{ {
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position)));
assertSnappedDistance((float)beat_length); assertSnappedDistance(0);
} }
[Test] [Test]
public void TestCursorBeforeMovementPoint() public void TestCursorBeforeMovementPoint()
{ {
AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f))); AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 1.45f)));
assertSnappedDistance((float)beat_length); assertSnappedDistance(beat_length);
} }
[Test] [Test]
public void TestCursorAfterMovementPoint() public void TestCursorAfterMovementPoint()
{ {
AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f))); AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 1.55f)));
assertSnappedDistance((float)beat_length * 2); 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)
{
AddStep($"Set distance spacing to {multiplier}", () => snapProvider.DistanceSpacingMultiplier.Value = multiplier);
AddStep("move mouse to point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 2)));
assertSnappedDistance(expectedDistance);
} }
[Test] [Test]
@ -115,13 +154,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray Colour = Color4.SlateGray
}, },
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }), grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
}; };
}); });
AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f))); AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 3f)));
assertSnappedDistance((float)beat_length * 2); assertSnappedDistance(beat_length);
} }
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
@ -137,6 +176,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private readonly Drawable cursor; private readonly Drawable cursor;
private InputManager inputManager;
public override bool HandlePositionalInput => true;
public SnappingCursorContainer() public SnappingCursorContainer()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -153,51 +196,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
base.LoadComplete(); base.LoadComplete();
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); inputManager = GetContainingInputManager();
} }
protected override bool OnMouseMove(MouseMoveEvent e) protected override void Update()
{ {
base.OnMouseMove(e); base.Update();
cursor.Position = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
updatePosition(e.ScreenSpaceMousePosition);
return true;
} }
private void updatePosition(Vector2 screenSpacePosition)
{
cursor.Position = GetSnapPosition.Invoke(screenSpacePosition);
}
}
private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
public TestOsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject = null)
: base(hitObject, nextHitObject)
{
}
}
private class SnapProvider : IDistanceSnapProvider
{
public SnapResult FindSnappedPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null);
public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public IBindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1);
public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length;
public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
public double FindSnappedDuration(HitObject referenceObject, float distance) => 0;
public float FindSnappedDistance(HitObject referenceObject, float distance) => 0;
} }
} }
} }

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{ {
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
: base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime) : base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime - 1)
{ {
Masking = true; Masking = true;
} }

View File

@ -21,8 +21,12 @@ namespace osu.Game.Tests.Visual.Editing
public class TestSceneDistanceSnapGrid : EditorClockTestScene public class TestSceneDistanceSnapGrid : EditorClockTestScene
{ {
private const double beat_length = 100; private const double beat_length = 100;
private const int beat_snap_distance = 10;
private static readonly Vector2 grid_position = new Vector2(512, 384); private static readonly Vector2 grid_position = new Vector2(512, 384);
private TestDistanceSnapGrid grid;
[Cached(typeof(EditorBeatmap))] [Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap; private readonly EditorBeatmap editorBeatmap;
@ -39,6 +43,7 @@ namespace osu.Game.Tests.Visual.Editing
} }
}); });
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
editorBeatmap.Difficulty.SliderMultiplier = 1;
} }
[SetUp] [SetUp]
@ -51,7 +56,7 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray Colour = Color4.SlateGray
}, },
new TestDistanceSnapGrid() grid = new TestDistanceSnapGrid()
}; };
}); });
@ -68,9 +73,22 @@ namespace osu.Game.Tests.Visual.Editing
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor); AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
} }
[Test] [TestCase(1.0)]
public void TestLimitedDistance() [TestCase(2.0)]
[TestCase(0.5)]
public void TestDistanceSpacing(double multiplier)
{ {
AddStep($"set distance spacing = {multiplier}", () => snapProvider.DistanceSpacingMultiplier.Value = multiplier);
AddAssert("distance spacing matches multiplier", () => grid.DistanceBetweenTicks == beat_snap_distance * multiplier);
}
[TestCase(1.0)]
[TestCase(2.0)]
[TestCase(0.5)]
public void TestLimitedDistance(double multiplier)
{
const int end_time = 100;
AddStep("create limited grid", () => AddStep("create limited grid", () =>
{ {
Children = new Drawable[] Children = new Drawable[]
@ -80,14 +98,19 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray Colour = Color4.SlateGray
}, },
new TestDistanceSnapGrid(100) grid = new TestDistanceSnapGrid(end_time)
}; };
}); });
AddStep($"set distance spacing = {multiplier}", () => snapProvider.DistanceSpacingMultiplier.Value = multiplier);
AddStep("check correct interval count", () => Assert.That((end_time / grid.DistanceBetweenTicks) * multiplier, Is.EqualTo(grid.MaxIntervals)));
} }
private class TestDistanceSnapGrid : DistanceSnapGrid private class TestDistanceSnapGrid : DistanceSnapGrid
{ {
public new float DistanceSpacing => base.DistanceSpacing; public new float DistanceBetweenTicks => base.DistanceBetweenTicks;
public new int MaxIntervals => base.MaxIntervals;
public TestDistanceSnapGrid(double? endTime = null) public TestDistanceSnapGrid(double? endTime = null)
: base(new HitObject(), grid_position, 0, endTime) : base(new HitObject(), grid_position, 0, endTime)
@ -105,7 +128,7 @@ namespace osu.Game.Tests.Visual.Editing
int indexFromPlacement = 0; int indexFromPlacement = 0;
for (float s = StartPosition.X + DistanceSpacing; s <= DrawWidth && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++) for (float s = StartPosition.X + DistanceBetweenTicks; s <= DrawWidth && indexFromPlacement < MaxIntervals; s += DistanceBetweenTicks, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
@ -118,7 +141,7 @@ namespace osu.Game.Tests.Visual.Editing
indexFromPlacement = 0; indexFromPlacement = 0;
for (float s = StartPosition.X - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++) for (float s = StartPosition.X - DistanceBetweenTicks; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceBetweenTicks, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
@ -131,7 +154,7 @@ namespace osu.Game.Tests.Visual.Editing
indexFromPlacement = 0; indexFromPlacement = 0;
for (float s = StartPosition.Y + DistanceSpacing; s <= DrawHeight && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++) for (float s = StartPosition.Y + DistanceBetweenTicks; s <= DrawHeight && indexFromPlacement < MaxIntervals; s += DistanceBetweenTicks, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
@ -144,7 +167,7 @@ namespace osu.Game.Tests.Visual.Editing
indexFromPlacement = 0; indexFromPlacement = 0;
for (float s = StartPosition.Y - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++) for (float s = StartPosition.Y - DistanceBetweenTicks; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceBetweenTicks, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
@ -167,9 +190,11 @@ namespace osu.Game.Tests.Visual.Editing
public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public IBindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1); public Bindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1);
public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10; IBindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
public float GetBeatSnapDistanceAt(HitObject referenceObject) => beat_snap_distance;
public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Edit
public abstract class DistancedHitObjectComposer<TObject> : HitObjectComposer<TObject>, IDistanceSnapProvider, IScrollBindingHandler<GlobalAction> public abstract class DistancedHitObjectComposer<TObject> : HitObjectComposer<TObject>, IDistanceSnapProvider, IScrollBindingHandler<GlobalAction>
where TObject : HitObject where TObject : HitObject
{ {
protected Bindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1.0) public Bindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1.0)
{ {
MinValue = 0.1, MinValue = 0.1,
MaxValue = 6.0, MaxValue = 6.0,

View File

@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Edit
{ {
/// <summary> /// <summary>
/// A multiplier which changes the ratio of distance travelled per time unit. /// A multiplier which changes the ratio of distance travelled per time unit.
/// Importantly, this is provided for manual usage, and not multiplied into any of the methods exposed by this interface.
/// </summary> /// </summary>
/// <seealso cref="BeatmapInfo.DistanceSpacing"/> /// <seealso cref="BeatmapInfo.DistanceSpacing"/>
IBindable<double> DistanceSpacingMultiplier { get; } IBindable<double> DistanceSpacingMultiplier { get; }

View File

@ -30,14 +30,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
Position = StartPosition, Position = StartPosition,
Width = crosshair_thickness, Width = crosshair_thickness,
EdgeSmoothness = new Vector2(1), EdgeSmoothness = new Vector2(1),
Height = Math.Min(crosshair_max_size, DistanceSpacing * 2), Height = Math.Min(crosshair_max_size, DistanceBetweenTicks * 2),
}, },
new Box new Box
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Position = StartPosition, Position = StartPosition,
EdgeSmoothness = new Vector2(1), EdgeSmoothness = new Vector2(1),
Width = Math.Min(crosshair_max_size, DistanceSpacing * 2), Width = Math.Min(crosshair_max_size, DistanceBetweenTicks * 2),
Height = crosshair_thickness, Height = crosshair_thickness,
} }
}); });
@ -45,19 +45,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
float dx = Math.Max(StartPosition.X, DrawWidth - StartPosition.X); float dx = Math.Max(StartPosition.X, DrawWidth - StartPosition.X);
float dy = Math.Max(StartPosition.Y, DrawHeight - StartPosition.Y); float dy = Math.Max(StartPosition.Y, DrawHeight - StartPosition.Y);
float maxDistance = new Vector2(dx, dy).Length; float maxDistance = new Vector2(dx, dy).Length;
int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing)); int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceBetweenTicks));
for (int i = 0; i < requiredCircles; i++) for (int i = 0; i < requiredCircles; i++)
{ {
float radius = (i + 1) * DistanceSpacing * 2; float diameter = (i + 1) * DistanceBetweenTicks * 2;
AddInternal(new CircularProgress AddInternal(new CircularProgress
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Position = StartPosition, Position = StartPosition,
Current = { Value = 1 }, Current = { Value = 1 },
Size = new Vector2(radius), Size = new Vector2(diameter),
InnerRadius = 4 * 1f / radius, InnerRadius = 4 * 1f / diameter,
Colour = GetColourForIndexFromPlacement(i) Colour = GetColourForIndexFromPlacement(i)
}); });
} }
@ -68,19 +68,37 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (MaxIntervals == 0) if (MaxIntervals == 0)
return (StartPosition, StartTime); return (StartPosition, StartTime);
Vector2 direction = position - StartPosition; // This grid implementation factors in the user's distance spacing specification,
if (direction == Vector2.Zero) // which is usually not considered by an `IDistanceSnapProvider`.
direction = new Vector2(0.001f, 0.001f); float distanceSpacingMultiplier = (float)DistanceSpacingMultiplier.Value;
float distance = direction.Length; Vector2 travelVector = (position - StartPosition);
float radius = DistanceSpacing; if (travelVector == Vector2.Zero)
int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals); return (StartPosition, StartTime);
Vector2 normalisedDirection = direction * new Vector2(1f / distance); float travelLength = travelVector.Length;
Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius;
return (snappedPosition, StartTime + SnapProvider.FindSnappedDuration(ReferenceObject, (snappedPosition - StartPosition).Length)); // FindSnappedDistance will always round down, but we want to potentially round upwards.
travelLength += DistanceBetweenTicks / 2;
// When interacting with the resolved snap provider, the distance spacing multiplier should first be removed
// to allow for snapping at a non-multiplied ratio.
float snappedDistance = SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier);
double snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance);
if (snappedTime > LatestEndTime)
{
double tickLength = Beatmap.GetBeatLengthAtTime(StartTime);
snappedDistance = SnapProvider.DurationToDistance(ReferenceObject, MaxIntervals * tickLength);
snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance);
}
// The multiplier can then be reapplied to the final position.
Vector2 snappedPosition = StartPosition + travelVector.Normalized() * snappedDistance * distanceSpacingMultiplier;
return (snappedPosition, snappedTime);
} }
} }
} }

View File

@ -23,7 +23,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary> /// <summary>
/// The spacing between each tick of the beat snapping grid. /// The spacing between each tick of the beat snapping grid.
/// </summary> /// </summary>
protected float DistanceSpacing { get; private set; } protected float DistanceBetweenTicks { get; private set; }
protected IBindable<double> DistanceSpacingMultiplier { get; private set; }
/// <summary> /// <summary>
/// The maximum number of distance snapping intervals allowed. /// The maximum number of distance snapping intervals allowed.
@ -32,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary> /// <summary>
/// The position which the grid should start. /// The position which the grid should start.
/// The first beat snapping tick is located at <see cref="StartPosition"/> + <see cref="DistanceSpacing"/> away from this point. /// The first beat snapping tick is located at <see cref="StartPosition"/> + <see cref="DistanceBetweenTicks"/> away from this point.
/// </summary> /// </summary>
protected readonly Vector2 StartPosition; protected readonly Vector2 StartPosition;
@ -41,6 +43,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
protected readonly double StartTime; protected readonly double StartTime;
protected readonly double? LatestEndTime;
[Resolved] [Resolved]
protected OsuColour Colours { get; private set; } protected OsuColour Colours { get; private set; }
@ -48,15 +52,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected IDistanceSnapProvider SnapProvider { get; private set; } protected IDistanceSnapProvider SnapProvider { get; private set; }
[Resolved] [Resolved]
private EditorBeatmap beatmap { get; set; } protected EditorBeatmap Beatmap { get; private set; }
[Resolved] [Resolved]
private BindableBeatDivisor beatDivisor { get; set; } private BindableBeatDivisor beatDivisor { get; set; }
private IBindable<double> distanceSpacingMultiplier;
private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
private readonly double? endTime;
protected readonly HitObject ReferenceObject; protected readonly HitObject ReferenceObject;
@ -70,7 +71,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected DistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null) protected DistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
{ {
ReferenceObject = referenceObject; ReferenceObject = referenceObject;
this.endTime = endTime; LatestEndTime = endTime;
StartPosition = startPosition; StartPosition = startPosition;
StartTime = startTime; StartTime = startTime;
@ -86,22 +87,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
beatDivisor.BindValueChanged(_ => updateSpacing()); beatDivisor.BindValueChanged(_ => updateSpacing());
distanceSpacingMultiplier = SnapProvider.DistanceSpacingMultiplier.GetBoundCopy(); DistanceSpacingMultiplier = SnapProvider.DistanceSpacingMultiplier.GetBoundCopy();
distanceSpacingMultiplier.BindValueChanged(_ => updateSpacing(), true); DistanceSpacingMultiplier.BindValueChanged(_ => updateSpacing(), true);
} }
private void updateSpacing() private void updateSpacing()
{ {
DistanceSpacing = (float)(SnapProvider.GetBeatSnapDistanceAt(ReferenceObject) * distanceSpacingMultiplier.Value); float distanceSpacingMultiplier = (float)DistanceSpacingMultiplier.Value;
float beatSnapDistance = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject);
if (endTime == null) DistanceBetweenTicks = beatSnapDistance * distanceSpacingMultiplier;
if (LatestEndTime == null)
MaxIntervals = int.MaxValue; MaxIntervals = int.MaxValue;
else else
{ MaxIntervals = (int)((LatestEndTime.Value - StartTime) / SnapProvider.DistanceToDuration(ReferenceObject, beatSnapDistance));
// +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors
double maxDuration = endTime.Value - StartTime + 1;
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(ReferenceObject, DistanceSpacing));
}
gridCache.Invalidate(); gridCache.Invalidate();
} }
@ -137,7 +137,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <returns>The applicable colour.</returns> /// <returns>The applicable colour.</returns>
protected ColourInfo GetColourForIndexFromPlacement(int placementIndex) protected ColourInfo GetColourForIndexFromPlacement(int placementIndex)
{ {
var timingPoint = beatmap.ControlPointInfo.TimingPointAt(StartTime); var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime);
double beatLength = timingPoint.BeatLength / beatDivisor.Value; double beatLength = timingPoint.BeatLength / beatDivisor.Value;
int beatIndex = (int)Math.Round((StartTime - timingPoint.Time) / beatLength); int beatIndex = (int)Math.Round((StartTime - timingPoint.Time) / beatLength);

View File

@ -9,7 +9,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osuTK.Graphics; using osu.Framework.Layout;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
@ -41,17 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
InternalChild = Box = CreateBox(); InternalChild = Box = CreateBox();
} }
protected virtual Drawable CreateBox() => new Container protected virtual Drawable CreateBox() => new BoxWithBorders();
{
Masking = true,
BorderColour = Color4.White,
BorderThickness = SelectionBox.BORDER_RADIUS,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f
}
};
private RectangleF? dragRectangle; private RectangleF? dragRectangle;
@ -111,5 +102,75 @@ namespace osu.Game.Screens.Edit.Compose.Components
public override void Show() => State = Visibility.Visible; public override void Show() => State = Visibility.Visible;
public event Action<Visibility> StateChanged; public event Action<Visibility> StateChanged;
public class BoxWithBorders : CompositeDrawable
{
private readonly LayoutValue cache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
public BoxWithBorders()
{
AddLayout(cache);
}
protected override void Update()
{
base.Update();
if (!cache.IsValid)
{
createContent();
cache.Validate();
}
}
private void createContent()
{
if (DrawSize == Vector2.Zero)
{
ClearInternal();
return;
}
// Make lines the same width independent of display resolution.
float lineThickness = DrawWidth > 0
? DrawWidth / ScreenSpaceDrawQuad.Width * 2
: DrawHeight / ScreenSpaceDrawQuad.Height * 2;
Padding = new MarginPadding(-lineThickness / 2);
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.X,
Height = lineThickness,
},
new Box
{
RelativeSizeAxes = Axes.X,
Height = lineThickness,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
new Box
{
RelativeSizeAxes = Axes.Y,
Width = lineThickness,
},
new Box
{
RelativeSizeAxes = Axes.Y,
Width = lineThickness,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f
}
};
}
}
} }
} }