1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-08 09:42:55 +08:00

Merge pull request #31743 from bdach/fix-limit-distance-snap-to-current

Avoid moving already placed objects temporally when "limit distance snap to current time" is active
This commit is contained in:
Dean Herbert 2025-02-03 17:48:24 +09:00 committed by GitHub
commit 56000ddb37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 59 additions and 29 deletions

View File

@ -2,10 +2,13 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -20,12 +23,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
[Resolved] [Resolved]
private OsuHitObjectComposer? composer { get; set; } private OsuHitObjectComposer? composer { get; set; }
[Resolved]
private EditorClock? editorClock { get; set; }
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
public HitCirclePlacementBlueprint() public HitCirclePlacementBlueprint()
: base(new HitCircle()) : base(new HitCircle())
{ {
InternalChild = circlePiece = new HitCirclePiece(); InternalChild = circlePiece = new HitCirclePiece();
} }
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -53,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime) public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
{ {
var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime); var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime);
result ??= composer?.TrySnapToDistanceGrid(screenSpacePosition); result ??= composer?.TrySnapToDistanceGrid(screenSpacePosition, limitedDistanceSnap.Value && editorClock != null ? editorClock.CurrentTime : null);
if (composer?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? screenSpacePosition, result?.Time ?? fallbackTime) is SnapResult gridSnapResult) if (composer?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? screenSpacePosition, result?.Time ?? fallbackTime) is SnapResult gridSnapResult)
result = gridSnapResult; result = gridSnapResult;
result ??= new SnapResult(screenSpacePosition, fallbackTime); result ??= new SnapResult(screenSpacePosition, fallbackTime);

View File

@ -21,6 +21,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -55,6 +56,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IDistanceSnapProvider distanceSnapProvider { get; set; } private IDistanceSnapProvider distanceSnapProvider { get; set; }
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
public PathControlPointVisualiser(T hitObject, bool allowSelection) public PathControlPointVisualiser(T hitObject, bool allowSelection)
{ {
this.hitObject = hitObject; this.hitObject = hitObject;
@ -69,6 +72,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}; };
} }
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -437,7 +446,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
Vector2 newHeadPosition = Parent!.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); Vector2 newHeadPosition = Parent!.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
var result = positionSnapProvider?.TrySnapToNearbyObjects(newHeadPosition, oldStartTime); var result = positionSnapProvider?.TrySnapToNearbyObjects(newHeadPosition, oldStartTime);
result ??= positionSnapProvider?.TrySnapToDistanceGrid(newHeadPosition); result ??= positionSnapProvider?.TrySnapToDistanceGrid(newHeadPosition, limitedDistanceSnap.Value ? oldStartTime : null);
if (positionSnapProvider?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? newHeadPosition, result?.Time ?? oldStartTime) is SnapResult gridSnapResult) if (positionSnapProvider?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? newHeadPosition, result?.Time ?? oldStartTime) is SnapResult gridSnapResult)
result = gridSnapResult; result = gridSnapResult;
result ??= new SnapResult(newHeadPosition, oldStartTime); result ??= new SnapResult(newHeadPosition, oldStartTime);

View File

@ -5,10 +5,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -49,6 +51,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
[Resolved] [Resolved]
private FreehandSliderToolboxGroup? freehandToolboxGroup { get; set; } private FreehandSliderToolboxGroup? freehandToolboxGroup { get; set; }
[Resolved]
private EditorClock? editorClock { get; set; }
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 }; private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength; protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
@ -63,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OsuConfigManager config)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -74,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}; };
state = SliderPlacementState.Initial; state = SliderPlacementState.Initial;
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -109,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime) public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
{ {
var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime); var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime);
result ??= composer?.TrySnapToDistanceGrid(screenSpacePosition); result ??= composer?.TrySnapToDistanceGrid(screenSpacePosition, limitedDistanceSnap.Value && editorClock != null ? editorClock.CurrentTime : null);
if (composer?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? screenSpacePosition, result?.Time ?? fallbackTime) is SnapResult gridSnapResult) if (composer?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? screenSpacePosition, result?.Time ?? fallbackTime) is SnapResult gridSnapResult)
result = gridSnapResult; result = gridSnapResult;
result ??= new SnapResult(screenSpacePosition, fallbackTime); result ??= new SnapResult(screenSpacePosition, fallbackTime);

View File

@ -3,7 +3,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
@ -17,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public partial class OsuBlueprintContainer : ComposeBlueprintContainer public partial class OsuBlueprintContainer : ComposeBlueprintContainer
{ {
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
public new OsuHitObjectComposer Composer => (OsuHitObjectComposer)base.Composer; public new OsuHitObjectComposer Composer => (OsuHitObjectComposer)base.Composer;
public OsuBlueprintContainer(OsuHitObjectComposer composer) public OsuBlueprintContainer(OsuHitObjectComposer composer)
@ -24,6 +29,12 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
} }
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
}
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler(); protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler();
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject) public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
@ -58,15 +69,15 @@ namespace osu.Game.Rulesets.Osu.Edit
// The final movement position, relative to movementBlueprintOriginalPosition. // The final movement position, relative to movementBlueprintOriginalPosition.
Vector2 movePosition = blueprints.First().originalSnapPositions.First() + distanceTravelled; Vector2 movePosition = blueprints.First().originalSnapPositions.First() + distanceTravelled;
var referenceBlueprint = blueprints.First().blueprint;
// Retrieve a snapped position. // Retrieve a snapped position.
var result = Composer.TrySnapToNearbyObjects(movePosition); var result = Composer.TrySnapToNearbyObjects(movePosition);
result ??= Composer.TrySnapToDistanceGrid(movePosition); result ??= Composer.TrySnapToDistanceGrid(movePosition, limitedDistanceSnap.Value ? referenceBlueprint.Item.StartTime : null);
if (Composer.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? movePosition, result?.Time) is SnapResult gridSnapResult) if (Composer.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? movePosition, result?.Time) is SnapResult gridSnapResult)
result = gridSnapResult; result = gridSnapResult;
result ??= new SnapResult(movePosition, null); result ??= new SnapResult(movePosition, null);
var referenceBlueprint = blueprints.First().blueprint;
bool moved = SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(referenceBlueprint, result.ScreenSpacePosition - referenceBlueprint.ScreenSpaceSelectionPoint)); bool moved = SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(referenceBlueprint, result.ScreenSpacePosition - referenceBlueprint.ScreenSpaceSelectionPoint));
if (moved) if (moved)
ApplySnapResultTime(result, referenceBlueprint.Item.StartTime); ApplySnapResultTime(result, referenceBlueprint.Item.StartTime);

View File

@ -250,13 +250,13 @@ namespace osu.Game.Rulesets.Osu.Edit
} }
[CanBeNull] [CanBeNull]
public SnapResult TrySnapToDistanceGrid(Vector2 screenSpacePosition) public SnapResult TrySnapToDistanceGrid(Vector2 screenSpacePosition, double? fixedTime = null)
{ {
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True || distanceSnapGrid == null) if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True || distanceSnapGrid == null)
return null; return null;
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition); var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition), fixedTime);
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, playfield); return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, playfield);
} }

View File

@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Editing
} }
} }
public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition) public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition, double? fixedTime = null)
=> (Vector2.Zero, 0); => (Vector2.Zero, 0);
} }

View File

@ -16,9 +16,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
public abstract partial class CircularDistanceSnapGrid : DistanceSnapGrid public abstract partial class CircularDistanceSnapGrid : DistanceSnapGrid
{ {
[Resolved]
private EditorClock editorClock { get; set; } = null!;
protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null) protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
: base(referenceObject, startPosition, startTime, endTime) : base(referenceObject, startPosition, startTime, endTime)
{ {
@ -76,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double? fixedTime = null)
{ {
if (MaxIntervals == 0) if (MaxIntervals == 0)
return (StartPosition, StartTime); return (StartPosition, StartTime);
@ -100,8 +97,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (travelLength < DistanceBetweenTicks) if (travelLength < DistanceBetweenTicks)
travelLength = DistanceBetweenTicks; travelLength = DistanceBetweenTicks;
float snappedDistance = LimitedDistanceSnap.Value float snappedDistance = fixedTime != null
? SnapProvider.DurationToDistance(ReferenceObject, editorClock.CurrentTime - ReferenceObject.GetEndTime()) ? SnapProvider.DurationToDistance(ReferenceObject, fixedTime.Value - ReferenceObject.GetEndTime())
// When interacting with the resolved snap provider, the distance spacing multiplier should first be removed // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed
// to allow for snapping at a non-multiplied ratio. // to allow for snapping at a non-multiplied ratio.
: SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier, DistanceSnapTarget.End); : SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier, DistanceSnapTarget.End);

View File

@ -10,7 +10,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -61,18 +60,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
[Resolved] [Resolved]
private BindableBeatDivisor beatDivisor { get; set; } private BindableBeatDivisor beatDivisor { get; set; }
/// <summary>
/// When enabled, distance snap should only snap to the current time (as per the editor clock).
/// This is to emulate stable behaviour.
/// </summary>
protected Bindable<bool> LimitedDistanceSnap { get; private set; }
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
LimitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
}
private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
protected readonly HitObject ReferenceObject; protected readonly HitObject ReferenceObject;
@ -143,8 +130,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// Snaps a position to this grid. /// Snaps a position to this grid.
/// </summary> /// </summary>
/// <param name="position">The original position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param> /// <param name="position">The original position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param>
/// <param name="fixedTime">
/// Whether the snap operation should be temporally constrained to a particular time instant,
/// thus fixing the possible positions to a set distance from the <see cref="ReferenceObject"/>.
/// </param>
/// <returns>A tuple containing the snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/> and the respective time value.</returns> /// <returns>A tuple containing the snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/> and the respective time value.</returns>
public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position); public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double? fixedTime = null);
/// <summary> /// <summary>
/// Retrieves the applicable colour for a beat index. /// Retrieves the applicable colour for a beat index.