diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 164f465438..19595de3b1 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Edit private readonly Bindable distanceSnapToggle = new Bindable(); + protected override bool SupportsDistanceSpacing => false; + private InputManager inputManager; public CatchHitObjectComposer(CatchRuleset ruleset) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 2baec95c94..5752f9f014 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Edit private ManiaBeatSnapGrid beatSnapGrid; private InputManager inputManager; + protected override bool SupportsDistanceSpacing => false; + public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1e84ec80e1..e7eabdc748 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuHitObjectComposer : HitObjectComposer { + protected override bool SupportsDistanceSpacing => true; + public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) { diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 161799c980..af9bc6c3a3 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoHitObjectComposer : HitObjectComposer { + protected override bool SupportsDistanceSpacing => false; + public TaikoHitObjectComposer(TaikoRuleset ruleset) : base(ruleset) { 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/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 39783cc8bb..528ba2fb8b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,8 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -56,6 +58,26 @@ namespace osu.Game.Rulesets.Edit [Resolved] protected IBeatSnapProvider BeatSnapProvider { get; private set; } + /// + /// Whether this composer supports a "distance spacing" multiplier for distance snap grids. + /// + /// + /// Setting this to displays a "distance spacing" slider and allows this composer to configure the value of . + /// + protected abstract bool SupportsDistanceSpacing { get; } + + private readonly BindableFloat distanceSpacing = new BindableFloat + { + Default = 1.0f, + MinValue = 0.1f, + MaxValue = 6.0f, + Precision = 0.01f, + }; + + public override IBindable DistanceSpacingMultiplier => distanceSpacing; + + private SnappingToolboxContainer snappingToolboxContainer; + protected ComposeBlueprintContainer BlueprintContainer { get; private set; } private DrawableEditorRulesetWrapper drawableRulesetWrapper; @@ -117,6 +139,8 @@ namespace osu.Game.Rulesets.Edit }, new LeftToolboxFlow { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, Children = new Drawable[] { new EditorToolboxGroup("toolbox (1-9)") @@ -132,11 +156,41 @@ namespace osu.Game.Rulesets.Edit Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - } + }, } }, }; + distanceSpacing.Value = (float)EditorBeatmap.BeatmapInfo.DistanceSpacing; + + if (SupportsDistanceSpacing) + { + ExpandableSlider distanceSpacingSlider; + + AddInternal(snappingToolboxContainer = new SnappingToolboxContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Child = new EditorToolboxGroup("snapping") + { + Child = distanceSpacingSlider = new ExpandableSlider + { + Current = { BindTarget = distanceSpacing }, + KeyboardStep = 0.01f, + } + } + }); + + 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); + } + else + distanceSpacing.Disabled = true; + toolboxCollection.Items = CompositionTools .Prepend(new SelectTool()) .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) @@ -211,8 +265,17 @@ namespace osu.Game.Rulesets.Edit #region Tool selection logic + private bool distanceSpacingScrollActive; + protected override bool OnKeyDown(KeyDownEvent e) { + if (SupportsDistanceSpacing && e.AltPressed && e.Key == Key.D && !e.Repeat) + { + snappingToolboxContainer.Expanded.Value = true; + distanceSpacingScrollActive = true; + return true; + } + if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; @@ -242,6 +305,28 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } + protected override void OnKeyUp(KeyUpEvent e) + { + if (distanceSpacingScrollActive && (e.Key == Key.AltLeft || e.Key == Key.AltRight || e.Key == Key.D)) + { + snappingToolboxContainer.Expanded.Value = false; + distanceSpacingScrollActive = false; + } + + base.OnKeyUp(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (distanceSpacingScrollActive) + { + distanceSpacing.Value += e.ScrollDelta.Y * (e.ShiftPressed || e.IsPrecise ? 0.01f : 0.1f); + return true; + } + + return base.OnScroll(e); + } + private bool checkLeftToggleFromKey(Key key, out int index) { if (key < Key.Number1 || key > Key.Number9) @@ -383,7 +468,7 @@ namespace osu.Game.Rulesets.Edit public override float GetBeatSnapDistanceAt(HitObject referenceObject) { - return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor); + return (float)(100 * referenceObject.DifficultyControlPoint.SliderVelocity * EditorBeatmap.Difficulty.SliderMultiplier * distanceSpacing.Value / BeatSnapProvider.BeatDivisor); } public override float DurationToDistance(HitObject referenceObject, double duration) @@ -432,6 +517,18 @@ namespace osu.Game.Rulesets.Edit FillFlow.Spacing = new Vector2(10); } } + + private class SnappingToolboxContainer : ExpandingContainer + { + public SnappingToolboxContainer() + : base(130, 250) + { + RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding { Left = 10 }; + + FillFlow.Spacing = new Vector2(10); + } + } } ///