mirror of
https://github.com/ppy/osu.git
synced 2025-02-26 19:22:55 +08:00
Add free-hand drawing of sliders to the editor
This commit is contained in:
parent
926636cc03
commit
3f85aa79c5
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -44,6 +45,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private ISliderDrawingSettingsProvider drawingSettingsProvider { get; set; }
|
||||||
|
|
||||||
|
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder();
|
||||||
|
|
||||||
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
||||||
|
|
||||||
public SliderPlacementBlueprint()
|
public SliderPlacementBlueprint()
|
||||||
@ -73,6 +79,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
|
|
||||||
|
drawingSettingsProvider.Tolerance.BindValueChanged(e =>
|
||||||
|
{
|
||||||
|
if (bSplineBuilder.Tolerance != e.NewValue)
|
||||||
|
bSplineBuilder.Tolerance = e.NewValue;
|
||||||
|
updateSliderPathFromBSplineBuilder();
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -98,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
ApplyDefaultsToHitObject();
|
ApplyDefaultsToHitObject();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SliderPlacementState.Body:
|
case SliderPlacementState.ControlPoints:
|
||||||
updateCursor();
|
updateCursor();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -115,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
beginCurve();
|
beginCurve();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SliderPlacementState.Body:
|
case SliderPlacementState.ControlPoints:
|
||||||
if (canPlaceNewControlPoint(out var lastPoint))
|
if (canPlaceNewControlPoint(out var lastPoint))
|
||||||
{
|
{
|
||||||
// Place a new point by detatching the current cursor.
|
// Place a new point by detatching the current cursor.
|
||||||
@ -139,9 +152,62 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButton.Left)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case SliderPlacementState.Initial:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SliderPlacementState.ControlPoints:
|
||||||
|
if (HitObject.Path.ControlPoints.Count < 3)
|
||||||
|
{
|
||||||
|
var lastCp = HitObject.Path.ControlPoints.LastOrDefault();
|
||||||
|
if (lastCp != cursor)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bSplineBuilder.Clear();
|
||||||
|
bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position);
|
||||||
|
setState(SliderPlacementState.Drawing);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.OnDragStart(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
base.OnDrag(e);
|
||||||
|
|
||||||
|
bSplineBuilder.AddLinearPoint(ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position);
|
||||||
|
updateSliderPathFromBSplineBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSliderPathFromBSplineBuilder()
|
||||||
|
{
|
||||||
|
Scheduler.AddOnce(static self =>
|
||||||
|
{
|
||||||
|
var cps = self.bSplineBuilder.GetControlPoints();
|
||||||
|
self.HitObject.Path.ControlPoints.RemoveRange(1, self.HitObject.Path.ControlPoints.Count - 1);
|
||||||
|
self.HitObject.Path.ControlPoints.AddRange(cps.Select(v => new PathControlPoint(v)));
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
{
|
||||||
|
base.OnDragEnd(e);
|
||||||
|
|
||||||
|
if (state == SliderPlacementState.Drawing)
|
||||||
|
endCurve();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
{
|
{
|
||||||
if (state == SliderPlacementState.Body && e.Button == MouseButton.Right)
|
if (state == SliderPlacementState.ControlPoints && e.Button == MouseButton.Right)
|
||||||
endCurve();
|
endCurve();
|
||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
@ -149,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private void beginCurve()
|
private void beginCurve()
|
||||||
{
|
{
|
||||||
BeginPlacement(commitStart: true);
|
BeginPlacement(commitStart: true);
|
||||||
setState(SliderPlacementState.Body);
|
setState(SliderPlacementState.ControlPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endCurve()
|
private void endCurve()
|
||||||
@ -169,6 +235,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private void updatePathType()
|
private void updatePathType()
|
||||||
{
|
{
|
||||||
|
if (state == SliderPlacementState.Drawing)
|
||||||
|
{
|
||||||
|
segmentStart.Type = PathType.BSpline(3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (currentSegmentLength)
|
switch (currentSegmentLength)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
@ -201,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the cursor position.
|
// Update the cursor position.
|
||||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.Body ? SnapType.GlobalGrids : SnapType.All);
|
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All);
|
||||||
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
}
|
}
|
||||||
else if (cursor != null)
|
else if (cursor != null)
|
||||||
@ -248,7 +320,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private enum SliderPlacementState
|
private enum SliderPlacementState
|
||||||
{
|
{
|
||||||
Initial,
|
Initial,
|
||||||
Body,
|
ControlPoints,
|
||||||
|
Drawing,
|
||||||
|
DrawingFinalization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs
Normal file
12
osu.Game.Rulesets.Osu/Edit/ISliderDrawingSettingsProvider.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public interface ISliderDrawingSettingsProvider
|
||||||
|
{
|
||||||
|
BindableFloat Tolerance { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||||
|
|
||||||
|
[Cached(typeof(ISliderDrawingSettingsProvider))]
|
||||||
|
protected readonly OsuSliderDrawingSettingsProvider SliderDrawingSettingsProvider = new OsuSliderDrawingSettingsProvider();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -96,8 +99,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
RightToolbox.Add(new TransformToolboxGroup
|
RightToolbox.Add(new TransformToolboxGroup
|
||||||
{
|
{
|
||||||
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler
|
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddInternal(SliderDrawingSettingsProvider);
|
||||||
|
SliderDrawingSettingsProvider.AttachToToolbox(RightToolbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class OsuSliderDrawingSettingsProvider : Drawable, ISliderDrawingSettingsProvider
|
||||||
|
{
|
||||||
|
public BindableFloat Tolerance { get; } = new BindableFloat(0.1f)
|
||||||
|
{
|
||||||
|
MinValue = 0.05f,
|
||||||
|
MaxValue = 1f,
|
||||||
|
Precision = 0.01f
|
||||||
|
};
|
||||||
|
|
||||||
|
private BindableInt sliderTolerance = new BindableInt(10)
|
||||||
|
{
|
||||||
|
MinValue = 5,
|
||||||
|
MaxValue = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
private ExpandableSlider<int> toleranceSlider = null!;
|
||||||
|
|
||||||
|
private EditorToolboxGroup? toolboxGroup;
|
||||||
|
|
||||||
|
public OsuSliderDrawingSettingsProvider()
|
||||||
|
{
|
||||||
|
sliderTolerance.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
float newValue = v.NewValue / 100f;
|
||||||
|
if (!Precision.AlmostEquals(newValue, Tolerance.Value, 1e-7f))
|
||||||
|
Tolerance.Value = newValue;
|
||||||
|
});
|
||||||
|
Tolerance.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
int newValue = (int)Math.Round(v.NewValue * 100f);
|
||||||
|
if (sliderTolerance.Value != newValue)
|
||||||
|
sliderTolerance.Value = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer)
|
||||||
|
{
|
||||||
|
toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("drawing")
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
toleranceSlider = new ExpandableSlider<int>
|
||||||
|
{
|
||||||
|
Current = sliderTolerance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sliderTolerance.BindValueChanged(e =>
|
||||||
|
{
|
||||||
|
toleranceSlider.ContractedLabelText = $"Tolerance: {e.NewValue:N0}";
|
||||||
|
toleranceSlider.ExpandedLabelText = $"Tolerance: {e.NewValue:N0}";
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -292,13 +292,13 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
switch (type.SplineType)
|
switch (type.SplineType)
|
||||||
{
|
{
|
||||||
case SplineType.Linear:
|
case SplineType.Linear:
|
||||||
return PathApproximator.ApproximateLinear(subControlPoints);
|
return PathApproximator.LinearToPiecewiseLinear(subControlPoints);
|
||||||
|
|
||||||
case SplineType.PerfectCurve:
|
case SplineType.PerfectCurve:
|
||||||
if (subControlPoints.Length != 3)
|
if (subControlPoints.Length != 3)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
List<Vector2> subPath = PathApproximator.ApproximateCircularArc(subControlPoints);
|
List<Vector2> subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints);
|
||||||
|
|
||||||
// If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation.
|
// If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation.
|
||||||
if (subPath.Count == 0)
|
if (subPath.Count == 0)
|
||||||
@ -307,10 +307,10 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
return subPath;
|
return subPath;
|
||||||
|
|
||||||
case SplineType.Catmull:
|
case SplineType.Catmull:
|
||||||
return PathApproximator.ApproximateCatmull(subControlPoints);
|
return PathApproximator.CatmullToPiecewiseLinear(subControlPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PathApproximator.ApproximateBSpline(subControlPoints, type.Degree ?? subControlPoints.Length);
|
return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void calculateLength()
|
private void calculateLength()
|
||||||
|
Loading…
Reference in New Issue
Block a user