diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 9ba2cdeaec..f7951fcc5d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -45,12 +44,7 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => Playfield.TotalColumns; - public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) - { - throw new NotImplementedException(); - } - - public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { var hoc = Playfield.GetColumn(0).HitObjectContainer; @@ -69,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Edit drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); - return (targetPosition, targetTime); + return new SnapResult(targetPosition, targetTime); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index f95f76b405..0d0be2953b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -174,9 +174,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class SnapProvider : IPositionSnapProvider { - public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0); - - public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index abbef0772f..834bf1892f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -162,11 +162,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (ControlPoint == slider.Path.ControlPoints[0]) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account - (Vector2 snappedPosition, double snappedTime) = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition) ?? (e.MousePosition, slider.StartTime); - Vector2 movementDelta = snappedPosition - slider.Position; + var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition); + Vector2 movementDelta = (result?.ScreenSpacePosition ?? e.MousePosition) - slider.Position; slider.Position += movementDelta; - slider.StartTime = snappedTime; + slider.StartTime = result?.Time ?? slider.StartTime; // 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 < slider.Path.ControlPoints.Count; i++) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 0e5e88c47a..8190cf5f89 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -153,9 +153,7 @@ namespace osu.Game.Tests.Visual.Editing private class SnapProvider : IPositionSnapProvider { - public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0); - - public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => 10; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 82e8fc8b10..7e9bb850af 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -245,7 +245,8 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.PlacementObject.Value = hitObject; - hitObject.StartTime = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).time; + if (SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).Time is double time) + hitObject.StartTime = time; } public void EndPlacement(HitObject hitObject, bool commit) @@ -264,11 +265,13 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => - distanceSnapGrid?.GetSnappedPosition(position) ?? (position, 0); + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null); - public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) - => SnapPositionToValidTime(drawableRulesetWrapper.Playfield.ToLocalSpace(screenSpacePosition)); + (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); + return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time); + } public override float GetBeatSnapDistanceAt(double referenceTime) { @@ -326,9 +329,7 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; - public abstract (Vector2 position, double time) SnapPositionToValidTime(Vector2 position); - - public abstract (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 93cb605132..d95800f403 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -8,13 +8,11 @@ namespace osu.Game.Rulesets.Edit public interface IPositionSnapProvider { /// - /// Given a position (local to the provider), find a valid time snap + /// Given a position, find a valid time snap. /// - /// The local position to be snapped. + /// The screen-space position to be snapped. /// The time and position post-snapping. - (Vector2 position, double time) SnapPositionToValidTime(Vector2 position); - - (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); + SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); /// /// Retrieves the distance between two points within a timing point that are one beat length apart. @@ -55,4 +53,23 @@ namespace osu.Game.Rulesets.Edit /// A value that represents snapped to the closest beat of the timing point. float GetSnappedDistanceFromDistance(double referenceTime, float distance); } + + public class SnapResult + { + /// + /// The screen space position, potentially altered for snapping. + /// + public Vector2 ScreenSpacePosition; + + /// + /// The resultant time for snapping, if a value could be attained. + /// + public double? Time; + + public SnapResult(Vector2 screenSpacePosition, double? time) + { + ScreenSpacePosition = screenSpacePosition; + Time = time; + } + } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d1cae6b3cd..e38df3d812 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -405,16 +405,19 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; // Retrieve a snapped position. - (Vector2 snappedPosition, double snappedTime) = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); + var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); // Move the hitobjects. - if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition)))) + if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) return true; - // Apply the start time at the newly snapped-to position - double offset = snappedTime - draggedObject.StartTime; - foreach (HitObject obj in selectionHandler.SelectedHitObjects) - obj.StartTime += offset; + if (result.Time.HasValue) + { + // Apply the start time at the newly snapped-to position + double offset = result.Time.Value - draggedObject.StartTime; + foreach (HitObject obj in selectionHandler.SelectedHitObjects) + obj.StartTime += offset; + } return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index bb6094ebe8..e1a4bca1d6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition(Vector2 screenSpacePosition) { - Vector2 snappedPlayfieldPosition = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition).position; + Vector2 snappedPlayfieldPosition = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition).ScreenSpacePosition; currentPlacement.UpdatePosition(ToScreenSpace(snappedPlayfieldPosition)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 1006da28df..ec2b11c0cf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -181,11 +181,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } - public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => - (position, beatSnapProvider.SnapTime(getTimeFromPosition(position))); - - public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 position) => - (position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position)))); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 position) => + new SnapResult(position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position)))); private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b5eae26f98..03e05b75c5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -275,32 +275,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled?.Invoke(e); - var time = timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).time; - - switch (hitObject) + if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time) { - case IHasRepeats repeatHitObject: - // find the number of repeats which can fit in the requested time. - var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); + switch (hitObject) + { + case IHasRepeats repeatHitObject: + // find the number of repeats which can fit in the requested time. + var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); + var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); - if (proposedCount == repeatHitObject.RepeatCount) - return; + if (proposedCount == repeatHitObject.RepeatCount) + return; - repeatHitObject.RepeatCount = proposedCount; - break; + repeatHitObject.RepeatCount = proposedCount; + break; - case IHasEndTime endTimeHitObject: - var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); + case IHasEndTime endTimeHitObject: + var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime) - return; + if (endTimeHitObject.EndTime == snappedTime) + return; - endTimeHitObject.EndTime = snappedTime; - break; + endTimeHitObject.EndTime = snappedTime; + break; + } + + beatmap.UpdateHitObject(hitObject); } - - beatmap.UpdateHitObject(hitObject); } protected override void OnDragEnd(DragEndEvent e)