diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs index 954bb53dd5..dfb6ecfcae 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectObjectSnap.cs @@ -7,7 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Tests.Beatmaps; @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); AddStep("set distance spacing to 1", () => { - var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; distanceSpacing.Value = 1; }); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index 2632e556c6..702e2216ac 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -11,6 +11,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; @@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); AddStep("set distance spacing to 1", () => { - var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; + var distanceSpacing = (BindableDouble)Editor.ChildrenOfType().Single().DistanceSpacingMultiplier; distanceSpacing.Value = 1; }); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a52c916ca0..bd26a99e51 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,13 +24,8 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - [Cached(typeof(IDistanceSnapProvider))] - public class OsuHitObjectComposer : HitObjectComposer, IDistanceSnapProvider + public class OsuHitObjectComposer : DistancedHitObjectComposer { - private OsuToolboxComposite osuToolboxComposite; - - public IBindable DistanceSpacingMultiplier => osuToolboxComposite.DistanceSpacing; - public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) { @@ -79,8 +74,6 @@ namespace osu.Game.Rulesets.Osu.Edit } }); - AddInternal(osuToolboxComposite = new OsuToolboxComposite()); - selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); @@ -135,9 +128,6 @@ namespace osu.Game.Rulesets.Osu.Edit } } - public override float GetBeatSnapDistanceAt(HitObject referenceObject) - => (float)(base.GetBeatSnapDistanceAt(referenceObject) * DistanceSpacingMultiplier.Value); - public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) { if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult)) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuToolboxComposite.cs b/osu.Game.Rulesets.Osu/Edit/OsuToolboxComposite.cs deleted file mode 100644 index 17232db509..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/OsuToolboxComposite.cs +++ /dev/null @@ -1,120 +0,0 @@ -// 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.Graphics.Containers; -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.Edit; -using osu.Game.Screens.Edit; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Edit -{ - /// - /// A toolbox composite for osu!-specific controls. - /// - // todo: once catch supports distance spacing, the control here should move out to a base "DistancingRulesetToolboxComposite" class or something better. - public class OsuToolboxComposite : CompositeDrawable - { - private ExpandingToolboxContainer expandingContainer; - private ExpandableSlider> distanceSpacingSlider; - - private readonly Bindable distanceSpacing = new BindableDouble(1.0) - { - MinValue = 0.1, - MaxValue = 6.0, - Precision = 0.01, - }; - - public IBindable DistanceSpacing => distanceSpacing; - - private bool distanceSpacingScrollActive; - - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = expandingContainer = new ExpandingToolboxContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Child = new EditorToolboxGroup("snapping") - { - Child = distanceSpacingSlider = new ExpandableSlider> - { - Current = { BindTarget = distanceSpacing }, - KeyboardStep = 0.1f, - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - distanceSpacing.Value = editorBeatmap.BeatmapInfo.DistanceSpacing; - distanceSpacing.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 (e.ControlPressed && e.AltPressed && !e.Repeat) - { - expandingContainer.Expanded.Value = true; - distanceSpacingScrollActive = true; - return true; - } - - return base.OnKeyDown(e); - } - - protected override void OnKeyUp(KeyUpEvent e) - { - if (distanceSpacingScrollActive && (!e.AltPressed || !e.ControlPressed)) - { - expandingContainer.Expanded.Value = false; - distanceSpacingScrollActive = false; - } - } - - protected override bool OnScroll(ScrollEvent e) - { - if (distanceSpacingScrollActive) - { - distanceSpacing.Value += e.ScrollDelta.Y * (e.IsPrecise ? 0.01f : 0.1f); - return true; - } - - return base.OnScroll(e); - } - - private 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/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs new file mode 100644 index 0000000000..a3624f6d77 --- /dev/null +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -0,0 +1,158 @@ +// 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 + { + 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(); + + 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 (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 (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 * DistanceSpacingMultiplier.Value / 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 8d2bd1e5d1..9fc332d726 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -383,44 +383,6 @@ namespace osu.Game.Rulesets.Edit return new SnapResult(screenSpacePosition, targetTime, playfield); } - public override float GetBeatSnapDistanceAt(HitObject referenceObject) - { - return (float)(100 * referenceObject.DifficultyControlPoint.SliderVelocity * EditorBeatmap.Difficulty.SliderMultiplier / 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 @@ -473,16 +435,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 } }