1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 10:52:53 +08:00

added slider splitting option

This commit is contained in:
OliBomby 2022-08-18 01:29:03 +02:00
parent 4627e4f53b
commit cb6339a20b
2 changed files with 111 additions and 7 deletions

View File

@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private InputManager inputManager;
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
public Action<List<PathControlPoint>> SplitControlPointsRequested;
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; }
@ -104,6 +105,28 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return true;
}
// The slider can only be split on control points which connect two different slider segments.
private bool splittable(PathControlPointPiece p) => p.ControlPoint.Type.HasValue && p != Pieces[0] && p != Pieces[^1];
private bool splitSelected()
{
List<PathControlPoint> toSplit = Pieces.Where(p => p.IsSelected.Value && splittable(p)).Select(p => p.ControlPoint).ToList();
// Ensure that there are any points to be split
if (toSplit.Count == 0)
return false;
changeHandler?.BeginChange();
SplitControlPointsRequested?.Invoke(toSplit);
changeHandler?.EndChange();
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
foreach (var piece in Pieces)
piece.IsSelected.Value = false;
return true;
}
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@ -322,6 +345,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (count == 0)
return null;
var splittablePieces = selectedPieces.Where(splittable).ToList();
int splittableCount = splittablePieces.Count;
List<MenuItem> items = new List<MenuItem>();
if (!selectedPieces.Contains(Pieces[0]))
@ -333,14 +359,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
items.Add(createMenuItemForPathType(PathType.Bezier));
items.Add(createMenuItemForPathType(PathType.Catmull));
return new MenuItem[]
var menuItems = new MenuItem[splittableCount > 0 ? 3 : 2];
int i = 0;
menuItems[i++] = new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive,
() => DeleteSelected());
if (splittableCount > 0)
{
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => DeleteSelected()),
new OsuMenuItem("Curve type")
{
Items = items
}
menuItems[i++] = new OsuMenuItem($"Split {"control point".ToQuantity(splittableCount, splittableCount > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}",
MenuItemType.Destructive, () => splitSelected());
}
menuItems[i] = new OsuMenuItem("Curve type")
{
Items = items
};
return menuItems;
}
}

View File

@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
@ -111,7 +112,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
{
RemoveControlPointsRequested = removeControlPoints
RemoveControlPointsRequested = removeControlPoints,
SplitControlPointsRequested = splitControlPoints
});
base.OnSelected();
@ -249,6 +251,72 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
HitObject.Position += first;
}
private void splitControlPoints(List<PathControlPoint> toSplit)
{
// Ensure that there are any points to be split
if (toSplit.Count == 0)
return;
foreach (var c in toSplit)
{
if (c == controlPoints[0] || c == controlPoints[^1] || c.Type is null)
continue;
// Split off the section of slider before this control point so the remaining control points to split are in the latter part of the slider.
var splitControlPoints = controlPoints.TakeWhile(current => current != c).ToList();
if (splitControlPoints.Count == 0)
continue;
foreach (var current in splitControlPoints)
{
controlPoints.Remove(current);
}
splitControlPoints.Add(c);
// Turn the control points which were split off into a new slider.
var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone();
samplePoint.Time = HitObject.StartTime;
var difficultyPoint = (DifficultyControlPoint)HitObject.DifficultyControlPoint.DeepClone();
difficultyPoint.Time = HitObject.StartTime;
var newSlider = new Slider
{
StartTime = HitObject.StartTime,
Position = HitObject.Position + splitControlPoints[0].Position,
NewCombo = HitObject.NewCombo,
SampleControlPoint = samplePoint,
DifficultyControlPoint = difficultyPoint,
Samples = HitObject.Samples.Select(s => s.With()).ToList(),
RepeatCount = HitObject.RepeatCount,
NodeSamples = HitObject.NodeSamples.Select(n => (IList<HitSampleInfo>)n.Select(s => s.With()).ToList()).ToList(),
Path = new SliderPath(splitControlPoints.Select(o => new PathControlPoint(o.Position - splitControlPoints[0].Position, o == splitControlPoints[^1] ? null : o.Type)).ToArray())
};
editorBeatmap.Add(newSlider);
HitObject.NewCombo = false;
HitObject.Path.ExpectedDistance.Value -= newSlider.Path.CalculatedDistance;
HitObject.StartTime += newSlider.SpanDuration;
// In case the remainder of the slider has no length left over, give it length anyways so we don't get a 0 length slider.
if (HitObject.Path.ExpectedDistance.Value <= Precision.DOUBLE_EPSILON)
{
HitObject.Path.ExpectedDistance.Value = null;
}
}
editorBeatmap.SelectedHitObjects.Clear();
// The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
Vector2 first = controlPoints[0].Position;
foreach (var c in controlPoints)
c.Position -= first;
HitObject.Position += first;
}
private void convertToStream()
{
if (editorBeatmap == null || beatDivisor == null)