diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index 368166157d..3c3c5cb939 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -4,15 +4,13 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; +using osu.Framework.Input; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -25,19 +23,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { 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); [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + private readonly EditorClock editorClock; + [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); [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() { @@ -48,14 +56,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Ruleset = new OsuRuleset().RulesetInfo } }); + + editorClock = new EditorClock(editorBeatmap); + + base.Content.Children = new Drawable[] + { + snapProvider, + Content + }; } + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + [SetUp] public void Setup() => Schedule(() => { editorBeatmap.Difficulty.SliderMultiplier = 1; editorBeatmap.ControlPointInfo.Clear(); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); + snapProvider.DistanceSpacingMultiplier.Value = 1; Children = new Drawable[] { @@ -64,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor RelativeSizeAxes = Axes.Both, 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 } }; }); @@ -82,25 +101,45 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor 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] public void TestCursorInCentre() { AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); - assertSnappedDistance((float)beat_length); + assertSnappedDistance(0); } [Test] 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))); - assertSnappedDistance((float)beat_length); + AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 1.45f))); + assertSnappedDistance(beat_length); } [Test] 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))); - assertSnappedDistance((float)beat_length * 2); + 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) + { + 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] @@ -115,13 +154,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor RelativeSizeAxes = Axes.Both, 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 } }; }); - AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f))); - assertSnappedDistance((float)beat_length * 2); + AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 3f))); + assertSnappedDistance(beat_length); } 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 InputManager inputManager; + + public override bool HandlePositionalInput => true; + public SnappingCursorContainer() { RelativeSizeAxes = Axes.Both; @@ -153,51 +196,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { base.LoadComplete(); - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + inputManager = GetContainingInputManager(); } - protected override bool OnMouseMove(MouseMoveEvent e) + protected override void Update() { - base.OnMouseMove(e); - - updatePosition(e.ScreenSpaceMousePosition); - return true; + base.Update(); + cursor.Position = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position); } - - 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 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; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 8a561f962a..b11929c1e8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { 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; } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 38c0808a71..3aa3481cbf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -21,8 +21,12 @@ namespace osu.Game.Tests.Visual.Editing public class TestSceneDistanceSnapGrid : EditorClockTestScene { private const double beat_length = 100; + private const int beat_snap_distance = 10; + private static readonly Vector2 grid_position = new Vector2(512, 384); + private TestDistanceSnapGrid grid; + [Cached(typeof(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.Difficulty.SliderMultiplier = 1; } [SetUp] @@ -51,7 +56,7 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, 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); } - [Test] - public void TestLimitedDistance() + [TestCase(1.0)] + [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", () => { Children = new Drawable[] @@ -80,14 +98,19 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, 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 { - public new float DistanceSpacing => base.DistanceSpacing; + public new float DistanceBetweenTicks => base.DistanceBetweenTicks; + + public new int MaxIntervals => base.MaxIntervals; public TestDistanceSnapGrid(double? endTime = null) : base(new HitObject(), grid_position, 0, endTime) @@ -105,7 +128,7 @@ namespace osu.Game.Tests.Visual.Editing 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 { @@ -118,7 +141,7 @@ namespace osu.Game.Tests.Visual.Editing 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 { @@ -131,7 +154,7 @@ namespace osu.Game.Tests.Visual.Editing 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 { @@ -144,7 +167,7 @@ namespace osu.Game.Tests.Visual.Editing 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 { @@ -167,9 +190,11 @@ namespace osu.Game.Tests.Visual.Editing public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); - public IBindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); + public Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); - public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10; + IBindable IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier; + + public float GetBeatSnapDistanceAt(HitObject referenceObject) => beat_snap_distance; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index bbcb702bd8..5e6d9dbe34 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Edit public abstract class DistancedHitObjectComposer : HitObjectComposer, IDistanceSnapProvider, IScrollBindingHandler where TObject : HitObject { - protected Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1.0) + public Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1.0) { MinValue = 0.1, MaxValue = 6.0, diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 8c599f8596..b12e1437dc 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Edit { /// /// 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. /// /// IBindable DistanceSpacingMultiplier { get; } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 50d5f0389a..91fad08aff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -30,14 +30,14 @@ namespace osu.Game.Screens.Edit.Compose.Components Position = StartPosition, Width = crosshair_thickness, EdgeSmoothness = new Vector2(1), - Height = Math.Min(crosshair_max_size, DistanceSpacing * 2), + Height = Math.Min(crosshair_max_size, DistanceBetweenTicks * 2), }, new Box { Origin = Anchor.Centre, Position = StartPosition, EdgeSmoothness = new Vector2(1), - Width = Math.Min(crosshair_max_size, DistanceSpacing * 2), + Width = Math.Min(crosshair_max_size, DistanceBetweenTicks * 2), Height = crosshair_thickness, } }); @@ -45,19 +45,19 @@ namespace osu.Game.Screens.Edit.Compose.Components float dx = Math.Max(StartPosition.X, DrawWidth - StartPosition.X); float dy = Math.Max(StartPosition.Y, DrawHeight - StartPosition.Y); 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++) { - float radius = (i + 1) * DistanceSpacing * 2; + float diameter = (i + 1) * DistanceBetweenTicks * 2; AddInternal(new CircularProgress { Origin = Anchor.Centre, Position = StartPosition, Current = { Value = 1 }, - Size = new Vector2(radius), - InnerRadius = 4 * 1f / radius, + Size = new Vector2(diameter), + InnerRadius = 4 * 1f / diameter, Colour = GetColourForIndexFromPlacement(i) }); } @@ -68,19 +68,37 @@ namespace osu.Game.Screens.Edit.Compose.Components if (MaxIntervals == 0) return (StartPosition, StartTime); - Vector2 direction = position - StartPosition; - if (direction == Vector2.Zero) - direction = new Vector2(0.001f, 0.001f); + // This grid implementation factors in the user's distance spacing specification, + // which is usually not considered by an `IDistanceSnapProvider`. + float distanceSpacingMultiplier = (float)DistanceSpacingMultiplier.Value; - float distance = direction.Length; + Vector2 travelVector = (position - StartPosition); - float radius = DistanceSpacing; - int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals); + if (travelVector == Vector2.Zero) + return (StartPosition, StartTime); - Vector2 normalisedDirection = direction * new Vector2(1f / distance); - Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius; + float travelLength = travelVector.Length; - 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); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 5568c15514..2f39db06d4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -23,7 +23,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The spacing between each tick of the beat snapping grid. /// - protected float DistanceSpacing { get; private set; } + protected float DistanceBetweenTicks { get; private set; } + + protected IBindable DistanceSpacingMultiplier { get; private set; } /// /// The maximum number of distance snapping intervals allowed. @@ -32,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The position which the grid should start. - /// The first beat snapping tick is located at + away from this point. + /// The first beat snapping tick is located at + away from this point. /// protected readonly Vector2 StartPosition; @@ -41,6 +43,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected readonly double StartTime; + protected readonly double? LatestEndTime; + [Resolved] protected OsuColour Colours { get; private set; } @@ -48,15 +52,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected IDistanceSnapProvider SnapProvider { get; private set; } [Resolved] - private EditorBeatmap beatmap { get; set; } + protected EditorBeatmap Beatmap { get; private set; } [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - private IBindable distanceSpacingMultiplier; - private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - private readonly double? endTime; 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) { ReferenceObject = referenceObject; - this.endTime = endTime; + LatestEndTime = endTime; StartPosition = startPosition; StartTime = startTime; @@ -86,22 +87,21 @@ namespace osu.Game.Screens.Edit.Compose.Components beatDivisor.BindValueChanged(_ => updateSpacing()); - distanceSpacingMultiplier = SnapProvider.DistanceSpacingMultiplier.GetBoundCopy(); - distanceSpacingMultiplier.BindValueChanged(_ => updateSpacing(), true); + DistanceSpacingMultiplier = SnapProvider.DistanceSpacingMultiplier.GetBoundCopy(); + DistanceSpacingMultiplier.BindValueChanged(_ => updateSpacing(), true); } 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; else - { - // +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)); - } + MaxIntervals = (int)((LatestEndTime.Value - StartTime) / SnapProvider.DistanceToDuration(ReferenceObject, beatSnapDistance)); gridCache.Invalidate(); } @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The applicable colour. protected ColourInfo GetColourForIndexFromPlacement(int placementIndex) { - var timingPoint = beatmap.ControlPointInfo.TimingPointAt(StartTime); + var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); double beatLength = timingPoint.BeatLength / beatDivisor.Value; int beatIndex = (int)Math.Round((StartTime - timingPoint.Time) / beatLength); diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index eaee2cd1e2..ecbac82db0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -9,7 +9,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osuTK.Graphics; +using osu.Framework.Layout; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { @@ -41,17 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components InternalChild = Box = CreateBox(); } - protected virtual Drawable CreateBox() => new Container - { - Masking = true, - BorderColour = Color4.White, - BorderThickness = SelectionBox.BORDER_RADIUS, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - } - }; + protected virtual Drawable CreateBox() => new BoxWithBorders(); private RectangleF? dragRectangle; @@ -111,5 +102,75 @@ namespace osu.Game.Screens.Edit.Compose.Components public override void Show() => State = Visibility.Visible; public event Action 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 + } + }; + } + } } }