1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 06:42:54 +08:00

Merge branch 'master' into sliderticks

This commit is contained in:
Bartłomiej Dach 2022-05-05 14:38:09 +02:00
commit 8b4e4b48d1
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
32 changed files with 112 additions and 87 deletions

View File

@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Catch.Edit
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
}); });
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition)
{ {
var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition); var result = base.FindSnappedPositionAndTime(screenSpacePosition);
result.ScreenSpacePosition.X = screenSpacePosition.X; result.ScreenSpacePosition.X = screenSpacePosition.X;
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult && if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&

View File

@ -97,12 +97,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
set => InternalChild = value; set => InternalChild = value;
} }
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) public override SnapResult FindSnappedPosition(Vector2 screenSpacePosition)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }

View File

@ -56,9 +56,9 @@ namespace osu.Game.Rulesets.Mania.Edit
protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) =>
Playfield.GetColumnByPosition(screenSpacePosition); Playfield.GetColumnByPosition(screenSpacePosition);
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition)
{ {
var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition); var result = base.FindSnappedPositionAndTime(screenSpacePosition);
switch (ScrollingInfo.Direction.Value) switch (ScrollingInfo.Direction.Value)
{ {
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Edit
} }
else else
{ {
var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); var result = FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
if (result.Time is double time) if (result.Time is double time)
beatSnapGrid.SelectionTimeRange = (time, time); beatSnapGrid.SelectionTimeRange = (time, time);
else else

View File

@ -182,10 +182,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private class SnapProvider : IDistanceSnapProvider private class SnapProvider : IDistanceSnapProvider
{ {
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => public SnapResult FindSnappedPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null); new SnapResult(screenSpacePosition, null);
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public IBindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1); public IBindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1);
@ -195,9 +195,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public double DistanceToDuration(HitObject referenceObject, float distance) => distance; public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0; public double FindSnappedDuration(HitObject referenceObject, float distance) => 0;
public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0; public float FindSnappedDistance(HitObject referenceObject, float distance) => 0;
} }
} }
} }

View File

@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
// Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account // Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account
Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
var result = snapProvider?.SnapScreenSpacePositionToValidTime(newHeadPosition); var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition);
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position; Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position;

View File

@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updateSlider() private void updateSlider()
{ {
HitObject.Path.ExpectedDistance.Value = snapProvider?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; HitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
bodyPiece.UpdateFrom(HitObject); bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle); headCirclePiece.UpdateFrom(HitObject.HeadCircle);

View File

@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Osu.Edit
} }
} }
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) public override SnapResult FindSnappedPosition(Vector2 screenSpacePosition)
{ {
if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult)) if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
return snapResult; return snapResult;
@ -131,9 +131,9 @@ namespace osu.Game.Rulesets.Osu.Edit
return new SnapResult(screenSpacePosition, null); return new SnapResult(screenSpacePosition, null);
} }
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition)
{ {
var positionSnap = SnapScreenSpacePositionToValidPosition(screenSpacePosition); var positionSnap = FindSnappedPosition(screenSpacePosition);
if (positionSnap.ScreenSpacePosition != screenSpacePosition) if (positionSnap.ScreenSpacePosition != screenSpacePosition)
return positionSnap; return positionSnap;
@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Edit
return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition)); return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition));
} }
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition); return base.FindSnappedPositionAndTime(screenSpacePosition);
} }
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult) private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => @"AL"; public override string Acronym => @"AL";
public override string Description => @"Don't use the same key twice in a row!"; public override string Description => @"Don't use the same key twice in a row!";
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => FontAwesome.Solid.Keyboard; public override IconUsage? Icon => FontAwesome.Solid.Keyboard;

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModAutoplay : ModAutoplay public class OsuModAutoplay : ModAutoplay
{ {
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods) public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModCinema : ModCinema<OsuHitObject> public class OsuModCinema : ModCinema<OsuHitObject>
{ {
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods) public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
{ {
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised) }).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray();
/// <summary> /// <summary>
/// How early before a hitobject's start time to trigger a hit. /// How early before a hitobject's start time to trigger a hit.

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -23,9 +22,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => @"Strict Tracking"; public override string Name => @"Strict Tracking";
public override string Acronym => @"ST"; public override string Acronym => @"ST";
public override IconUsage? Icon => FontAwesome.Solid.PenFancy;
public override ModType Type => ModType.DifficultyIncrease; public override ModType Type => ModType.DifficultyIncrease;
public override string Description => @"Follow circles just got serious..."; public override string Description => @"Once you start a slider, follow precisely or get a miss.";
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTarget) }; public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTarget) };

View File

@ -213,10 +213,10 @@ namespace osu.Game.Tests.Editing
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(new HitObject(), distance) == expectedDuration); => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(new HitObject(), distance) == expectedDuration);
private void assertSnappedDuration(float distance, double expectedDuration) private void assertSnappedDuration(float distance, double expectedDuration)
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(new HitObject(), distance) == expectedDuration); => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.FindSnappedDuration(new HitObject(), distance) == expectedDuration);
private void assertSnappedDistance(float distance, float expectedDistance) private void assertSnappedDistance(float distance, float expectedDistance)
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(new HitObject(), distance) == expectedDistance); => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.FindSnappedDistance(new HitObject(), distance) == expectedDistance);
private class TestHitObjectComposer : OsuHitObjectComposer private class TestHitObjectComposer : OsuHitObjectComposer
{ {

View File

@ -162,10 +162,10 @@ namespace osu.Game.Tests.Visual.Editing
private class SnapProvider : IDistanceSnapProvider private class SnapProvider : IDistanceSnapProvider
{ {
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => public SnapResult FindSnappedPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null); new SnapResult(screenSpacePosition, null);
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public IBindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1); public IBindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1);
@ -175,9 +175,9 @@ namespace osu.Game.Tests.Visual.Editing
public double DistanceToDuration(HitObject referenceObject, float distance) => distance; public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0; public double FindSnappedDuration(HitObject referenceObject, float distance) => 0;
public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0; public float FindSnappedDistance(HitObject referenceObject, float distance) => 0;
} }
} }
} }

View File

@ -1,21 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable enable
using System; using System;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.BeatmapSet.Scores;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Scoring; using osu.Game.Scoring;
using Realms; using Realms;
#nullable enable
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
/// <summary> /// <summary>
@ -109,6 +111,16 @@ namespace osu.Game.Beatmaps
public bool SamplesMatchPlaybackRate { get; set; } = true; public bool SamplesMatchPlaybackRate { get; set; } = true;
/// <summary>
/// The ratio of distance travelled per time unit.
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
/// </summary>
/// <remarks>
/// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap
/// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider.
///
/// This is only a hint property, used by the editor in <see cref="IDistanceSnapProvider"/> implementations. It does not directly affect the beatmap or gameplay.
/// </remarks>
public double DistanceSpacing { get; set; } = 1.0; public double DistanceSpacing { get; set; } = 1.0;
public int BeatDivisor { get; set; } public int BeatDivisor { get; set; }

View File

@ -153,7 +153,7 @@ namespace osu.Game.Beatmaps
} }
}; };
cacheDownloadRequest.PerformAsync(); Task.Run(() => cacheDownloadRequest.PerformAsync());
} }
private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo) private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo)

View File

@ -59,6 +59,9 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious), new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious),
new KeyBinding(InputKey.Down, GlobalAction.SelectNext), new KeyBinding(InputKey.Down, GlobalAction.SelectNext),
new KeyBinding(InputKey.Left, GlobalAction.SelectPreviousGroup),
new KeyBinding(InputKey.Right, GlobalAction.SelectNextGroup),
new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Space, GlobalAction.Select),
new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select),
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
@ -105,7 +108,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.F1, GlobalAction.ToggleModSelection), new KeyBinding(InputKey.F1, GlobalAction.ToggleModSelection),
new KeyBinding(InputKey.F2, GlobalAction.SelectNextRandom), new KeyBinding(InputKey.F2, GlobalAction.SelectNextRandom),
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom),
new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions) new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions),
}; };
public IEnumerable<KeyBinding> AudioControlKeyBindings => new[] public IEnumerable<KeyBinding> AudioControlKeyBindings => new[]
@ -309,5 +312,11 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorDecreaseDistanceSpacing))] [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorDecreaseDistanceSpacing))]
EditorDecreaseDistanceSpacing, EditorDecreaseDistanceSpacing,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectPreviousGroup))]
SelectPreviousGroup,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectNextGroup))]
SelectNextGroup,
} }
} }

View File

@ -129,6 +129,16 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString SelectNext => new TranslatableString(getKey(@"select_next"), @"Next selection"); public static LocalisableString SelectNext => new TranslatableString(getKey(@"select_next"), @"Next selection");
/// <summary>
/// "Select previous group"
/// </summary>
public static LocalisableString SelectPreviousGroup => new TranslatableString(getKey(@"select_previous_group"), @"Select previous group");
/// <summary>
/// "Select next group"
/// </summary>
public static LocalisableString SelectNextGroup => new TranslatableString(getKey(@"select_next_group"), @"Select next group");
/// <summary> /// <summary>
/// "Home" /// "Home"
/// </summary> /// </summary>

View File

@ -372,12 +372,12 @@ namespace osu.Game.Overlays.Volume
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.SelectPrevious: case GlobalAction.SelectPreviousGroup:
State = SelectionState.Selected; State = SelectionState.Selected;
adjust(1, false); adjust(1, false);
return true; return true;
case GlobalAction.SelectNext: case GlobalAction.SelectNextGroup:
State = SelectionState.Selected; State = SelectionState.Selected;
adjust(-1, false); adjust(-1, false);
return true; return true;

View File

@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Edit
/// Represents a <see cref="HitObjectComposer{TObject}"/> for rulesets with the concept of distances between objects. /// Represents a <see cref="HitObjectComposer{TObject}"/> for rulesets with the concept of distances between objects.
/// </summary> /// </summary>
/// <typeparam name="TObject">The base type of supported objects.</typeparam> /// <typeparam name="TObject">The base type of supported objects.</typeparam>
[Cached(typeof(IDistanceSnapProvider))]
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
{ {
@ -146,10 +145,10 @@ namespace osu.Game.Rulesets.Edit
return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength; return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength;
} }
public virtual double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) public virtual double FindSnappedDuration(HitObject referenceObject, float distance)
=> BeatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime; => BeatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime;
public virtual float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) public virtual float FindSnappedDistance(HitObject referenceObject, float distance)
{ {
double startTime = referenceObject.StartTime; double startTime = referenceObject.StartTime;

View File

@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Edit
/// Responsible for providing snapping and generally gluing components together. /// Responsible for providing snapping and generally gluing components together.
/// </summary> /// </summary>
/// <typeparam name="TObject">The base type of supported objects.</typeparam> /// <typeparam name="TObject">The base type of supported objects.</typeparam>
[Cached(Type = typeof(IPlacementHandler))]
public abstract class HitObjectComposer<TObject> : HitObjectComposer, IPlacementHandler public abstract class HitObjectComposer<TObject> : HitObjectComposer, IPlacementHandler
where TObject : HitObject where TObject : HitObject
{ {
@ -362,7 +361,7 @@ namespace osu.Game.Rulesets.Edit
/// <returns>The most relevant <see cref="Playfield"/>.</returns> /// <returns>The most relevant <see cref="Playfield"/>.</returns>
protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield; protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield;
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition)
{ {
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition); var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
double? targetTime = null; double? targetTime = null;
@ -388,8 +387,7 @@ namespace osu.Game.Rulesets.Edit
/// A non-generic definition of a HitObject composer class. /// A non-generic definition of a HitObject composer class.
/// Generally used to access certain methods without requiring a generic type for <see cref="HitObjectComposer{T}" />. /// Generally used to access certain methods without requiring a generic type for <see cref="HitObjectComposer{T}" />.
/// </summary> /// </summary>
[Cached(typeof(HitObjectComposer))] [Cached]
[Cached(typeof(IPositionSnapProvider))]
public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
{ {
protected HitObjectComposer() protected HitObjectComposer()
@ -416,9 +414,9 @@ namespace osu.Game.Rulesets.Edit
#region IPositionSnapProvider #region IPositionSnapProvider
public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition);
public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => public virtual SnapResult FindSnappedPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null); new SnapResult(screenSpacePosition, null);
#endregion #endregion

View File

@ -1,16 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
{ {
/// <summary>
/// A snap provider which given a reference hit object and proposed distance from it, offers a more correct duration or distance value.
/// </summary>
[Cached]
public interface IDistanceSnapProvider : IPositionSnapProvider public interface IDistanceSnapProvider : IPositionSnapProvider
{ {
/// <summary> /// <summary>
/// The spacing multiplier applied to beat snap distances. /// A multiplier which changes the ratio of distance travelled per time unit.
/// </summary> /// </summary>
/// <seealso cref="BeatmapInfo.DistanceSpacing"/> /// <seealso cref="BeatmapInfo.DistanceSpacing"/>
IBindable<double> DistanceSpacingMultiplier { get; } IBindable<double> DistanceSpacingMultiplier { get; }
@ -23,7 +28,7 @@ namespace osu.Game.Rulesets.Edit
float GetBeatSnapDistanceAt(HitObject referenceObject); float GetBeatSnapDistanceAt(HitObject referenceObject);
/// <summary> /// <summary>
/// Converts a duration to a distance. /// Converts a duration to a distance without applying any snapping.
/// </summary> /// </summary>
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="duration">The duration to convert.</param> /// <param name="duration">The duration to convert.</param>
@ -31,7 +36,7 @@ namespace osu.Game.Rulesets.Edit
float DurationToDistance(HitObject referenceObject, double duration); float DurationToDistance(HitObject referenceObject, double duration);
/// <summary> /// <summary>
/// Converts a distance to a duration. /// Converts a distance to a duration without applying any snapping.
/// </summary> /// </summary>
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
@ -39,20 +44,22 @@ namespace osu.Game.Rulesets.Edit
double DistanceToDuration(HitObject referenceObject, float distance); double DistanceToDuration(HitObject referenceObject, float distance);
/// <summary> /// <summary>
/// Converts a distance to a snapped duration. /// Given a distance from the provided hit object, find the valid snapped duration.
/// </summary> /// </summary>
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns> /// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns>
double GetSnappedDurationFromDistance(HitObject referenceObject, float distance); double FindSnappedDuration(HitObject referenceObject, float distance);
/// <summary> /// <summary>
/// Converts an unsnapped distance to a snapped distance. /// Given a distance from the provided hit object, find the valid snapped distance.
/// The returned distance will always be floored (as to never exceed the provided <paramref name="distance"/>.
/// </summary> /// </summary>
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns> /// <returns>
float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance); /// A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.
/// The distance will always be less than or equal to the provided <paramref name="distance"/>.
/// </returns>
float FindSnappedDistance(HitObject referenceObject, float distance);
} }
} }

View File

@ -1,27 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
{ {
/// <summary>
/// A snap provider which given a proposed position for a hit object, potentially offers a more correct position and time value inferred from the context of the beatmap.
/// Provided values are inferred in an isolated context, without consideration of other nearby hit objects.
/// </summary>
[Cached]
public interface IPositionSnapProvider public interface IPositionSnapProvider
{ {
/// <summary> /// <summary>
/// Given a position, find a valid time and position snap. /// Given a position, find a valid time and position snap.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This call should be equivalent to running <see cref="SnapScreenSpacePositionToValidPosition"/> with any additional logic that can be performed without the time immutability restriction. /// This call should be equivalent to running <see cref="FindSnappedPosition"/> with any additional logic that can be performed without the time immutability restriction.
/// </remarks> /// </remarks>
/// <param name="screenSpacePosition">The screen-space position to be snapped.</param> /// <param name="screenSpacePosition">The screen-space position to be snapped.</param>
/// <returns>The time and position post-snapping.</returns> /// <returns>The time and position post-snapping.</returns>
SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition);
/// <summary> /// <summary>
/// Given a position, find a value position snap, restricting time to its input value. /// Given a position, find a valid position snap, without changing the time value.
/// </summary> /// </summary>
/// <param name="screenSpacePosition">The screen-space position to be snapped.</param> /// <param name="screenSpacePosition">The screen-space position to be snapped.</param>
/// <returns>The position post-snapping. Time will always be null.</returns> /// <returns>The position post-snapping. Time will always be null.</returns>
SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition); SnapResult FindSnappedPosition(Vector2 screenSpacePosition);
} }
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Objects
public static void SnapTo<THitObject>(this THitObject hitObject, IDistanceSnapProvider? snapProvider) public static void SnapTo<THitObject>(this THitObject hitObject, IDistanceSnapProvider? snapProvider)
where THitObject : HitObject, IHasPath where THitObject : HitObject, IHasPath
{ {
hitObject.Path.ExpectedDistance.Value = snapProvider?.GetSnappedDistanceFromDistance(hitObject, (float)hitObject.Path.CalculatedDistance) ?? hitObject.Path.CalculatedDistance; hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance) ?? hitObject.Path.CalculatedDistance;
} }
/// <summary> /// <summary>

View File

@ -486,7 +486,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Vector2 originalPosition = movementBlueprintOriginalPositions[i]; Vector2 originalPosition = movementBlueprintOriginalPositions[i];
var testPosition = originalPosition + distanceTravelled; var testPosition = originalPosition + distanceTravelled;
var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); var positionalResult = snapProvider.FindSnappedPosition(testPosition);
if (positionalResult.ScreenSpacePosition == testPosition) continue; if (positionalResult.ScreenSpacePosition == testPosition) continue;
@ -505,7 +505,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled;
// Retrieve a snapped position. // Retrieve a snapped position.
var result = snapProvider?.SnapScreenSpacePositionToValidTime(movePosition); var result = snapProvider?.FindSnappedPositionAndTime(movePosition);
if (result == null) if (result == null)
{ {

View File

@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 normalisedDirection = direction * new Vector2(1f / distance);
Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius; Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius;
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(ReferenceObject, (snappedPosition - StartPosition).Length)); return (snappedPosition, StartTime + SnapProvider.FindSnappedDuration(ReferenceObject, (snappedPosition - StartPosition).Length));
} }
} }
} }

View File

@ -214,7 +214,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementPosition() private void updatePlacementPosition()
{ {
var snapResult = Composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); var snapResult = Composer.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
// if no time was found from positional snapping, we should still quantize to the beat. // if no time was found from positional snapping, we should still quantize to the beat.
snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null); snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null);

View File

@ -19,7 +19,6 @@ using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
[Cached(typeof(IPositionSnapProvider))]
[Cached] [Cached]
public class Timeline : ZoomableScrollContainer, IPositionSnapProvider public class Timeline : ZoomableScrollContainer, IPositionSnapProvider
{ {
@ -307,10 +306,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
/// </summary> /// </summary>
public double VisibleRange => track.Length / Zoom; public double VisibleRange => track.Length / Zoom;
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => public SnapResult FindSnappedPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null); new SnapResult(screenSpacePosition, null);
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
private double getTimeFromPosition(Vector2 localPosition) => private double getTimeFromPosition(Vector2 localPosition) =>

View File

@ -382,7 +382,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
OnDragHandled?.Invoke(e); OnDragHandled?.Invoke(e);
if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time) if (timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time)
{ {
switch (hitObject) switch (hitObject)
{ {

View File

@ -1,10 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Screens.Edit.Compose namespace osu.Game.Screens.Edit.Compose
{ {
[Cached]
public interface IPlacementHandler public interface IPlacementHandler
{ {
/// <summary> /// <summary>

View File

@ -604,34 +604,20 @@ namespace osu.Game.Screens.Select
public void ScrollToSelected(bool immediate = false) => public void ScrollToSelected(bool immediate = false) =>
pendingScrollOperation = immediate ? PendingScrollOperation.Immediate : PendingScrollOperation.Standard; pendingScrollOperation = immediate ? PendingScrollOperation.Immediate : PendingScrollOperation.Standard;
#region Key / button selection logic #region Button selection logic
protected override bool OnKeyDown(KeyDownEvent e)
{
switch (e.Key)
{
case Key.Left:
SelectNext(-1);
return true;
case Key.Right:
SelectNext();
return true;
}
return false;
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.SelectNext: case GlobalAction.SelectNext:
SelectNext(1, false); case GlobalAction.SelectNextGroup:
SelectNext(1, e.Action == GlobalAction.SelectNextGroup);
return true; return true;
case GlobalAction.SelectPrevious: case GlobalAction.SelectPrevious:
SelectNext(-1, false); case GlobalAction.SelectPreviousGroup:
SelectNext(-1, e.Action == GlobalAction.SelectPreviousGroup);
return true; return true;
} }

View File

@ -14,7 +14,6 @@ using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[Cached(Type = typeof(IPlacementHandler))]
public abstract class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler public abstract class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler
{ {
protected readonly Container HitObjectContainer; protected readonly Container HitObjectContainer;