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:
parent
4627e4f53b
commit
cb6339a20b
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user