diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 164f465438..9ff6d10a49 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -24,7 +24,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Edit { - public class CatchHitObjectComposer : HitObjectComposer + public class CatchHitObjectComposer : DistancedHitObjectComposer { private const float distance_snap_radius = 50; @@ -42,6 +42,10 @@ namespace osu.Game.Rulesets.Catch.Edit [BackgroundDependencyLoader] private void load() { + // todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation. + RightSideToolboxContainer.Alpha = 0; + DistanceSpacingMultiplier.Disabled = true; + LayerBelowRuleset.Add(new PlayfieldBorder { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 50be13c4e0..2e55f86bb6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -107,30 +106,5 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { throw new System.NotImplementedException(); } - - public override float GetBeatSnapDistanceAt(HitObject referenceObject) - { - throw new System.NotImplementedException(); - } - - public override float DurationToDistance(HitObject referenceObject, double duration) - { - throw new System.NotImplementedException(); - } - - public override double DistanceToDuration(HitObject referenceObject, float distance) - { - throw new System.NotImplementedException(); - } - - public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) - { - throw new System.NotImplementedException(); - } - - public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) - { - throw new System.NotImplementedException(); - } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index c770e2d96f..2ba30c5f74 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -4,6 +4,7 @@ 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; @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - [Cached(typeof(IPositionSnapProvider))] + [Cached(typeof(IDistanceSnapProvider))] private readonly SnapProvider snapProvider = new SnapProvider(); private TestOsuDistanceSnapGrid grid; @@ -179,13 +180,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } } - private class SnapProvider : IPositionSnapProvider + private class SnapProvider : IDistanceSnapProvider { public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, null); public SnapResult SnapScreenSpacePositionToValidTime(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; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ae4141073e..5de614722f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public Action> RemoveControlPointsRequested; [Resolved(CanBeNull = true)] - private IPositionSnapProvider snapProvider { get; set; } + private IDistanceSnapProvider snapProvider { get; set; } public PathControlPointVisualiser(Slider slider, bool allowSelection) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index b868c9a7ee..73a4ea5434 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private int currentSegmentLength; [Resolved(CanBeNull = true)] - private HitObjectComposer composer { get; set; } + private IDistanceSnapProvider snapProvider { get; set; } public SliderPlacementBlueprint() : base(new Objects.Slider()) @@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; + HitObject.Path.ExpectedDistance.Value = snapProvider?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 6cf2a493a9..a019f2fb64 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } [Resolved(CanBeNull = true)] - private HitObjectComposer composer { get; set; } + private IDistanceSnapProvider snapProvider { get; set; } [Resolved(CanBeNull = true)] private IPlacementHandler placementHandler { get; set; } @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Move the control points from the insertion index onwards to make room for the insertion controlPoints.Insert(insertionIndex, pathControlPoint); - HitObject.SnapTo(composer); + HitObject.SnapTo(snapProvider); return pathControlPoint; } @@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Snap the slider to the current beat divisor before checking length validity. - HitObject.SnapTo(composer); + HitObject.SnapTo(snapProvider); // If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1e84ec80e1..bd26a99e51 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,7 +24,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuHitObjectComposer : HitObjectComposer + public class OsuHitObjectComposer : DistancedHitObjectComposer { public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index efbac5439c..70c60ab635 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit public class OsuSelectionHandler : EditorSelectionHandler { [Resolved(CanBeNull = true)] - private IPositionSnapProvider? positionSnapProvider { get; set; } + private IDistanceSnapProvider? snapProvider { get; set; } /// /// During a transform, the initial origin is stored so it can be used throughout the operation. @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Osu.Edit // Snap the slider's length to the current beat divisor // to calculate the final resulting duration / bounding box before the final checks. - slider.SnapTo(positionSnapProvider); + slider.SnapTo(snapProvider); //if sliderhead or sliderend end up outside playfield, revert scaling. Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Edit point.Position = oldControlPoints.Dequeue(); // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. - slider.SnapTo(positionSnapProvider); + slider.SnapTo(snapProvider); } private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 43f22e4e90..a2ee97210a 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -69,7 +70,7 @@ namespace osu.Game.Tests.Editing [TestCase(2)] public void TestSliderMultiplier(float multiplier) { - AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier); + AddStep($"set slider multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier); assertSnapDistance(100 * multiplier); } @@ -221,6 +222,8 @@ namespace osu.Game.Tests.Editing { public new EditorBeatmap EditorBeatmap => base.EditorBeatmap; + public new Bindable DistanceSpacingMultiplier => base.DistanceSpacingMultiplier; + public TestHitObjectComposer() : base(new OsuRuleset()) { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 0d9e06e471..b9cfa84a5d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; @@ -25,7 +26,7 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(EditorBeatmap))] private readonly EditorBeatmap editorBeatmap; - [Cached(typeof(IPositionSnapProvider))] + [Cached(typeof(IDistanceSnapProvider))] private readonly SnapProvider snapProvider = new SnapProvider(); public TestSceneDistanceSnapGrid() @@ -159,13 +160,15 @@ namespace osu.Game.Tests.Visual.Editing => (Vector2.Zero, 0); } - private class SnapProvider : IPositionSnapProvider + private class SnapProvider : IDistanceSnapProvider { public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, null); public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); + public IBindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); + public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index 145d738f60..ae1b691767 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -21,6 +21,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -86,6 +87,23 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Tool changed", () => hitObjectComposer.ChildrenOfType().First().CurrentTool is HitCircleCompositionTool); } + [Test] + public void TestDistanceSpacingHotkeys() + { + double originalSpacing = 0; + + AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.BeatmapInfo.DistanceSpacing); + + AddStep("hold ctrl", () => InputManager.PressKey(Key.LControl)); + AddStep("hold alt", () => InputManager.PressKey(Key.LAlt)); + + AddStep("scroll mouse 5 steps", () => InputManager.ScrollVerticalBy(5)); + AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5); + + AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); + AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl)); + } + public class EditorBeatmapContainer : Container { private readonly IWorkingBeatmap working; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs index f4920b4412..288c0cb140 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TestExpandingContainer container; private SettingsToolboxGroup toolboxGroup; - private ExpandableSlider slider1; + private ExpandableSlider> slider1; private ExpandableSlider slider2; [SetUp] @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 1, Children = new Drawable[] { - slider1 = new ExpandableSlider + slider1 = new ExpandableSlider> { Current = new BindableFloat { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index f90208d0c0..1a9703f478 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -109,7 +109,7 @@ namespace osu.Game.Beatmaps public bool SamplesMatchPlaybackRate { get; set; } = true; - public double DistanceSpacing { get; set; } + public double DistanceSpacing { get; set; } = 1.0; public int BeatDivisor { get; set; } diff --git a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs index 60e83f9c81..a05c0cfab0 100644 --- a/osu.Game/Graphics/UserInterface/ExpandableSlider.cs +++ b/osu.Game/Graphics/UserInterface/ExpandableSlider.cs @@ -70,6 +70,15 @@ namespace osu.Game.Graphics.UserInterface set => slider.Current = value; } + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + public BindableBool Expanded { get; } = new BindableBool(); public override bool HandlePositionalInput => true; diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs index 962572ca6e..83ea655601 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { Children = new Drawable[] { - new SettingsSlider + new SettingsSlider> { LabelText = SkinSettingsStrings.GameplayCursorSize, Current = config.GetBindable(OsuSetting.GameplayCursorSize), diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 8aeb440be1..c8a46162af 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -1,6 +1,8 @@ // 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 System.Globalization; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -9,8 +11,9 @@ namespace osu.Game.Overlays.Settings.Sections /// /// A slider intended to show a "size" multiplier number, where 1x is 1.0. /// - internal class SizeSlider : OsuSliderBar + public class SizeSlider : OsuSliderBar + where T : struct, IEquatable, IComparable, IConvertible, IFormattable { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x", NumberFormatInfo.CurrentInfo); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 7fc049915e..dd1b9cc2a0 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.CursorRotation, Current = config.GetBindable(OsuSetting.CursorRotation) }, - new SettingsSlider + new SettingsSlider> { LabelText = UserInterfaceStrings.MenuCursorSize, Current = config.GetBindable(OsuSetting.MenuCursorSize), diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs new file mode 100644 index 0000000000..0505f9ab0e --- /dev/null +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -0,0 +1,162 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings.Sections; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// Represents a for rulesets with the concept of distances between objects. + /// + /// The base type of supported objects. + [Cached(typeof(IDistanceSnapProvider))] + public abstract class DistancedHitObjectComposer : HitObjectComposer, IDistanceSnapProvider + where TObject : HitObject + { + protected Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1.0) + { + MinValue = 0.1, + MaxValue = 6.0, + Precision = 0.01, + }; + + IBindable IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier; + + protected ExpandingToolboxContainer RightSideToolboxContainer { get; private set; } + + private ExpandableSlider> distanceSpacingSlider; + private bool distanceSpacingScrollActive; + + protected DistancedHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(RightSideToolboxContainer = new ExpandingToolboxContainer + { + Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Child = new EditorToolboxGroup("snapping") + { + Child = distanceSpacingSlider = new ExpandableSlider> + { + Current = { BindTarget = DistanceSpacingMultiplier }, + KeyboardStep = 0.1f, + } + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!DistanceSpacingMultiplier.Disabled) + { + DistanceSpacingMultiplier.Value = EditorBeatmap.BeatmapInfo.DistanceSpacing; + DistanceSpacingMultiplier.BindValueChanged(v => + { + distanceSpacingSlider.ContractedLabelText = $"D. S. ({v.NewValue:0.##x})"; + distanceSpacingSlider.ExpandedLabelText = $"Distance Spacing ({v.NewValue:0.##x})"; + EditorBeatmap.BeatmapInfo.DistanceSpacing = v.NewValue; + }, true); + } + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!DistanceSpacingMultiplier.Disabled && e.ControlPressed && e.AltPressed && !e.Repeat) + { + RightSideToolboxContainer.Expanded.Value = true; + distanceSpacingScrollActive = true; + return true; + } + + return base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyUpEvent e) + { + if (!DistanceSpacingMultiplier.Disabled && distanceSpacingScrollActive && (!e.AltPressed || !e.ControlPressed)) + { + RightSideToolboxContainer.Expanded.Value = false; + distanceSpacingScrollActive = false; + } + } + + protected override bool OnScroll(ScrollEvent e) + { + if (distanceSpacingScrollActive) + { + DistanceSpacingMultiplier.Value += e.ScrollDelta.Y * (e.IsPrecise ? 0.01f : 0.1f); + return true; + } + + return base.OnScroll(e); + } + + public virtual float GetBeatSnapDistanceAt(HitObject referenceObject) + { + return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor); + } + + public virtual float DurationToDistance(HitObject referenceObject, double duration) + { + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); + return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceObject)); + } + + public virtual double DistanceToDuration(HitObject referenceObject, float distance) + { + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); + return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength; + } + + public virtual double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) + => BeatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime; + + public virtual float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) + { + double startTime = referenceObject.StartTime; + + double actualDuration = startTime + DistanceToDuration(referenceObject, distance); + + double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, startTime); + + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(startTime); + + // we don't want to exceed the actual duration and snap to a point in the future. + // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. + if (snappedEndTime > actualDuration + 1) + snappedEndTime -= beatLength; + + return DurationToDistance(referenceObject, snappedEndTime - startTime); + } + + protected class ExpandingToolboxContainer : ExpandingContainer + { + protected override double HoverExpansionDelay => 250; + + public ExpandingToolboxContainer() + : base(130, 250) + { + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Left = 10 }; + + FillFlow.Spacing = new Vector2(10); + } + } + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 39783cc8bb..a235a5bc60 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -381,44 +381,6 @@ namespace osu.Game.Rulesets.Edit return new SnapResult(screenSpacePosition, targetTime, playfield); } - public override float GetBeatSnapDistanceAt(HitObject referenceObject) - { - return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor); - } - - public override float DurationToDistance(HitObject referenceObject, double duration) - { - double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); - return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceObject)); - } - - public override double DistanceToDuration(HitObject referenceObject, float distance) - { - double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); - return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength; - } - - public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) - => BeatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime; - - public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) - { - double startTime = referenceObject.StartTime; - - double actualDuration = startTime + DistanceToDuration(referenceObject, distance); - - double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, startTime); - - double beatLength = BeatSnapProvider.GetBeatLengthAtTime(startTime); - - // we don't want to exceed the actual duration and snap to a point in the future. - // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. - if (snappedEndTime > actualDuration + 1) - snappedEndTime -= beatLength; - - return DurationToDistance(referenceObject, snappedEndTime - startTime); - } - #endregion private class LeftToolboxFlow : ExpandingButtonContainer @@ -471,16 +433,6 @@ namespace osu.Game.Rulesets.Edit public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, null); - public abstract float GetBeatSnapDistanceAt(HitObject referenceObject); - - public abstract float DurationToDistance(HitObject referenceObject, double duration); - - public abstract double DistanceToDuration(HitObject referenceObject, float distance); - - public abstract double GetSnappedDurationFromDistance(HitObject referenceObject, float distance); - - public abstract float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance); - #endregion } } diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs new file mode 100644 index 0000000000..c6e866561e --- /dev/null +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit +{ + public interface IDistanceSnapProvider : IPositionSnapProvider + { + /// + /// The spacing multiplier applied to beat snap distances. + /// + /// + IBindable DistanceSpacingMultiplier { get; } + + /// + /// Retrieves the distance between two points within a timing point that are one beat length apart. + /// + /// An object to be used as a reference point for this operation. + /// The distance between two points residing in the timing point that are one beat length apart. + float GetBeatSnapDistanceAt(HitObject referenceObject); + + /// + /// Converts a duration to a distance. + /// + /// An object to be used as a reference point for this operation. + /// The duration to convert. + /// A value that represents as a distance in the timing point. + float DurationToDistance(HitObject referenceObject, double duration); + + /// + /// Converts a distance to a duration. + /// + /// An object to be used as a reference point for this operation. + /// The distance to convert. + /// A value that represents as a duration in the timing point. + double DistanceToDuration(HitObject referenceObject, float distance); + + /// + /// Converts a distance to a snapped duration. + /// + /// An object to be used as a reference point for this operation. + /// The distance to convert. + /// A value that represents as a duration snapped to the closest beat of the timing point. + double GetSnappedDurationFromDistance(HitObject referenceObject, float distance); + + /// + /// Converts an unsnapped distance to a snapped distance. + /// The returned distance will always be floored (as to never exceed the provided . + /// + /// An object to be used as a reference point for this operation. + /// The distance to convert. + /// A value that represents snapped to the closest beat of the timing point. + float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance); + } +} diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 743a2f41fc..8a179ed424 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.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 osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Edit @@ -24,45 +23,5 @@ namespace osu.Game.Rulesets.Edit /// The screen-space position to be snapped. /// The position post-snapping. Time will always be null. SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition); - - /// - /// Retrieves the distance between two points within a timing point that are one beat length apart. - /// - /// An object to be used as a reference point for this operation. - /// The distance between two points residing in the timing point that are one beat length apart. - float GetBeatSnapDistanceAt(HitObject referenceObject); - - /// - /// Converts a duration to a distance. - /// - /// An object to be used as a reference point for this operation. - /// The duration to convert. - /// A value that represents as a distance in the timing point. - float DurationToDistance(HitObject referenceObject, double duration); - - /// - /// Converts a distance to a duration. - /// - /// An object to be used as a reference point for this operation. - /// The distance to convert. - /// A value that represents as a duration in the timing point. - double DistanceToDuration(HitObject referenceObject, float distance); - - /// - /// Converts a distance to a snapped duration. - /// - /// An object to be used as a reference point for this operation. - /// The distance to convert. - /// A value that represents as a duration snapped to the closest beat of the timing point. - double GetSnappedDurationFromDistance(HitObject referenceObject, float distance); - - /// - /// Converts an unsnapped distance to a snapped distance. - /// The returned distance will always be floored (as to never exceed the provided . - /// - /// An object to be used as a reference point for this operation. - /// The distance to convert. - /// A value that represents snapped to the closest beat of the timing point. - float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance); } } diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index ba614900c0..3100d26a55 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Objects /// /// Snaps the provided 's duration using the . /// - public static void SnapTo(this THitObject hitObject, IPositionSnapProvider? snapProvider) + public static void SnapTo(this THitObject hitObject, IDistanceSnapProvider? snapProvider) where THitObject : HitObject, IHasPath { hitObject.Path.ExpectedDistance.Value = snapProvider?.GetSnappedDistanceFromDistance(hitObject, (float)hitObject.Path.CalculatedDistance) ?? hitObject.Path.CalculatedDistance; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 05bf405f3c..5568c15514 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -44,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected OsuColour Colours { get; private set; } [Resolved] - protected IPositionSnapProvider SnapProvider { get; private set; } + protected IDistanceSnapProvider SnapProvider { get; private set; } [Resolved] private EditorBeatmap beatmap { get; set; } @@ -52,6 +53,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + private IBindable distanceSpacingMultiplier; + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly double? endTime; @@ -81,12 +84,15 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - beatDivisor.BindValueChanged(_ => updateSpacing(), true); + beatDivisor.BindValueChanged(_ => updateSpacing()); + + distanceSpacingMultiplier = SnapProvider.DistanceSpacingMultiplier.GetBoundCopy(); + distanceSpacingMultiplier.BindValueChanged(_ => updateSpacing(), true); } private void updateSpacing() { - DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject); + DistanceSpacing = (float)(SnapProvider.GetBeatSnapDistanceAt(ReferenceObject) * distanceSpacingMultiplier.Value); if (endTime == null) MaxIntervals = int.MaxValue; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 51cca4ceff..c2b2bdb861 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -15,7 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -316,15 +315,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; - - public float GetBeatSnapDistanceAt(HitObject referenceObject) => throw new NotImplementedException(); - - public float DurationToDistance(HitObject referenceObject, double duration) => throw new NotImplementedException(); - - public double DistanceToDuration(HitObject referenceObject, float distance) => throw new NotImplementedException(); - - public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => throw new NotImplementedException(); - - public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => throw new NotImplementedException(); } }