1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 22:07:25 +08:00

made PathControlPointVisualiser generic

This commit is contained in:
OliBomby 2022-11-03 12:25:23 +01:00
parent 9f4bb3e0ca
commit 10b5900710
12 changed files with 103 additions and 100 deletions

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public partial class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene
{
private Slider slider;
private PathControlPointVisualiser visualiser;
private PathControlPointVisualiser<Slider> visualiser;
[SetUp]
public void Setup() => Schedule(() =>
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointPathType(3, null);
}
private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection)
private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser<Slider>(slider, allowSelection)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre

View File

@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
private void assertSelectionCount(int count) =>
AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == count);
AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == count);
private void assertSelected(int index) =>
AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected",
() => this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
() => this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
private void moveMouseToRelativePosition(Vector2 relativePosition) =>
AddStep($"move mouse to {relativePosition}", () =>
@ -202,12 +202,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
moveMouseToControlPoint(2);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
addMovementStep(new Vector2(450, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
assertControlPointPosition(2, new Vector2(450, 50));
assertControlPointType(2, PathType.PerfectCurve);
@ -236,12 +236,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
moveMouseToControlPoint(3);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
addMovementStep(new Vector2(550, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
// note: if the head is part of the selection being moved, the entire slider is moved.
// the unselected nodes will therefore change position relative to the slider head.
@ -354,7 +354,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public new PathControlPointVisualiser<Slider> ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
: base(slider)

View File

@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public new PathControlPointVisualiser<Slider> ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
: base(slider)

View File

@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[Test]
public void TestMovingUnsnappedSliderNodesSnaps()
{
PathControlPointPiece sliderEnd = null;
PathControlPointPiece<Slider> sliderEnd = null;
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("select slider end", () =>
{
sliderEnd = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
sliderEnd = this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre);
});
AddStep("move slider end", () =>
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to new point location", () =>
{
var firstPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
var firstPiece = this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
var pos = slider.Path.PositionAt(0.25d) + slider.Position;
InputManager.MoveMouseTo(firstPiece.Parent.ToScreenSpace(pos));
});
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to second control point", () =>
{
var secondPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
var secondPiece = this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
InputManager.MoveMouseTo(secondPiece);
});
AddStep("quick delete", () =>

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First();
private Slider? slider;
private PathControlPointVisualiser? visualiser;
private PathControlPointVisualiser<Slider>? visualiser;
private const double split_gap = 100;
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select added slider", () =>
{
EditorBeatmap.SelectedHitObjects.Add(slider);
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser<Slider>>().First();
});
moveMouseToControlPoint(2);
@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select added slider", () =>
{
EditorBeatmap.SelectedHitObjects.Add(slider);
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser<Slider>>().First();
});
moveMouseToControlPoint(2);
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select added slider", () =>
{
EditorBeatmap.SelectedHitObjects.Add(slider);
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser<Slider>>().First();
});
moveMouseToControlPoint(2);

View File

@ -8,34 +8,36 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
/// <summary>
/// A visualisation of the line between two <see cref="PathControlPointPiece"/>s.
/// A visualisation of the line between two <see cref="PathControlPointPiece{T}"/>s.
/// </summary>
public partial class PathControlPointConnectionPiece : CompositeDrawable
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointConnectionPiece{T}"/> visualises.</typeparam>
public partial class PathControlPointConnectionPiece<T> : CompositeDrawable where T : OsuHitObject, IHasPath
{
public readonly PathControlPoint ControlPoint;
private readonly Path path;
private readonly Slider slider;
private readonly T hitObject;
public int ControlPointIndex { get; set; }
private IBindable<Vector2> sliderPosition;
private IBindable<Vector2> hitObjectPosition;
private IBindable<int> pathVersion;
public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
public PathControlPointConnectionPiece(T hitObject, int controlPointIndex)
{
this.slider = slider;
this.hitObject = hitObject;
ControlPointIndex = controlPointIndex;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
ControlPoint = slider.Path.ControlPoints[controlPointIndex];
ControlPoint = hitObject.Path.ControlPoints[controlPointIndex];
InternalChild = path = new SmoothPath
{
@ -48,10 +50,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
base.LoadComplete();
sliderPosition = slider.PositionBindable.GetBoundCopy();
sliderPosition.BindValueChanged(_ => updateConnectingPath());
hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
hitObjectPosition.BindValueChanged(_ => updateConnectingPath());
pathVersion = slider.Path.Version.GetBoundCopy();
pathVersion = hitObject.Path.Version.GetBoundCopy();
pathVersion.BindValueChanged(_ => updateConnectingPath());
updateConnectingPath();
@ -62,16 +64,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary>
private void updateConnectingPath()
{
Position = slider.StackedPosition + ControlPoint.Position;
Position = hitObject.StackedPosition + ControlPoint.Position;
path.ClearVertices();
int nextIndex = ControlPointIndex + 1;
if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count)
if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count)
return;
path.AddVertex(Vector2.Zero);
path.AddVertex(slider.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
}

View File

@ -29,11 +29,12 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
/// <summary>
/// A visualisation of a single <see cref="PathControlPoint"/> in a <see cref="Slider"/>.
/// A visualisation of a single <see cref="PathControlPoint"/> in an osu hit object with a path.
/// </summary>
public partial class PathControlPointPiece : BlueprintPiece<Slider>, IHasTooltip
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointPiece{T}"/> visualises.</typeparam>
public partial class PathControlPointPiece<T> : BlueprintPiece<T>, IHasTooltip where T : OsuHitObject, IHasPath
{
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
public Action<PathControlPointPiece<T>, MouseButtonEvent> RequestSelection;
public Action<PathControlPoint> DragStarted;
public Action<DragEvent> DragInProgress;
@ -44,34 +45,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public readonly BindableBool IsSelected = new BindableBool();
public readonly PathControlPoint ControlPoint;
private readonly Slider slider;
private readonly T hitObject;
private readonly Container marker;
private readonly Drawable markerRing;
[Resolved]
private OsuColour colours { get; set; }
private IBindable<Vector2> sliderPosition;
private IBindable<float> sliderScale;
private IBindable<Vector2> hitObjectPosition;
private IBindable<float> hitObjectScale;
[UsedImplicitly]
private readonly IBindable<int> sliderVersion;
private readonly IBindable<int> hitObjectVersion;
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
{
this.slider = slider;
this.hitObject = hitObject;
ControlPoint = controlPoint;
// we don't want to run the path type update on construction as it may inadvertently change the slider.
cachePoints(slider);
// we don't want to run the path type update on construction as it may inadvertently change the hit object.
cachePoints(hitObject);
sliderVersion = slider.Path.Version.GetBoundCopy();
hitObjectVersion = hitObject.Path.Version.GetBoundCopy();
// schedule ensure that updates are only applied after all operations from a single frame are applied.
// this avoids inadvertently changing the slider path type for batch operations.
sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
// this avoids inadvertently changing the hit object path type for batch operations.
hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
{
cachePoints(slider);
cachePoints(hitObject);
updatePathType();
}));
@ -120,11 +121,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
base.LoadComplete();
sliderPosition = slider.PositionBindable.GetBoundCopy();
sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
hitObjectPosition.BindValueChanged(_ => updateMarkerDisplay());
sliderScale = slider.ScaleBindable.GetBoundCopy();
sliderScale.BindValueChanged(_ => updateMarkerDisplay());
hitObjectScale = hitObject.ScaleBindable.GetBoundCopy();
hitObjectScale.BindValueChanged(_ => updateMarkerDisplay());
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
@ -212,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint);
/// <summary>
/// Handles correction of invalid path types.
@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary>
private void updateMarkerDisplay()
{
Position = slider.StackedPosition + ControlPoint.Position;
Position = hitObject.StackedPosition + ControlPoint.Position;
markerRing.Alpha = IsSelected.Value ? 1 : 0;
@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
colour = colour.Lighten(1);
marker.Colour = colour;
marker.Scale = new Vector2(slider.Scale);
marker.Scale = new Vector2(hitObject.Scale);
}
private Color4 getColourFromNodeType()

View File

@ -29,15 +29,15 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu where T : OsuHitObject, IHasPath
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
internal readonly Container<PathControlPointPiece> Pieces;
internal readonly Container<PathControlPointConnectionPiece> Connections;
internal readonly Container<PathControlPointPiece<T>> Pieces;
internal readonly Container<PathControlPointConnectionPiece<T>> Connections;
private readonly IBindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
private readonly Slider slider;
private readonly T hitObject;
private readonly bool allowSelection;
private InputManager inputManager;
@ -48,17 +48,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; }
public PathControlPointVisualiser(Slider slider, bool allowSelection)
public PathControlPointVisualiser(T hitObject, bool allowSelection)
{
this.slider = slider;
this.hitObject = hitObject;
this.allowSelection = allowSelection;
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
Connections = new Container<PathControlPointConnectionPiece> { RelativeSizeAxes = Axes.Both },
Pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both }
Connections = new Container<PathControlPointConnectionPiece<T>> { RelativeSizeAxes = Axes.Both },
Pieces = new Container<PathControlPointPiece<T>> { RelativeSizeAxes = Axes.Both }
};
}
@ -69,12 +69,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
inputManager = GetContainingInputManager();
controlPoints.CollectionChanged += onControlPointsChanged;
controlPoints.BindTo(slider.Path.ControlPoints);
controlPoints.BindTo(hitObject.Path.ControlPoints);
}
/// <summary>
/// Selects the <see cref="PathControlPointPiece"/> corresponding to the given <paramref name="pathControlPoint"/>,
/// and deselects all other <see cref="PathControlPointPiece"/>s.
/// Selects the <see cref="PathControlPointPiece{T}"/> corresponding to the given <paramref name="pathControlPoint"/>,
/// and deselects all other <see cref="PathControlPointPiece{T}"/>s.
/// </summary>
public void SetSelectionTo(PathControlPoint pathControlPoint)
{
@ -124,8 +124,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return true;
}
private bool isSplittable(PathControlPointPiece p) =>
// A slider can only be split on control points which connect two different slider segments.
private bool isSplittable(PathControlPointPiece<T> p) =>
// A hit object can only be split on control points which connect two different path segments.
p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault();
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
var point = (PathControlPoint)e.NewItems[i];
Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
Pieces.Add(new PathControlPointPiece<T>(hitObject, point).With(d =>
{
if (allowSelection)
d.RequestSelection = selectionRequested;
@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
d.DragEnded = dragEnded;
}));
Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i));
Connections.Add(new PathControlPointConnectionPiece<T>(hitObject, e.NewStartingIndex + i));
}
break;
@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
}
private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e)
private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
piece.IsSelected.Toggle();
@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary>
/// <param name="piece">The control point piece that we want to change the path type of.</param>
/// <param name="type">The path type we want to assign to the given control point piece.</param>
private void updatePathType(PathControlPointPiece piece, PathType? type)
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
{
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
@ -264,9 +264,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void dragStarted(PathControlPoint controlPoint)
{
dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray();
dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray();
draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint);
dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray();
dragPathTypes = hitObject.Path.ControlPoints.Select(point => point.Type).ToArray();
draggedControlPointIndex = hitObject.Path.ControlPoints.IndexOf(controlPoint);
selectedControlPoints = new HashSet<PathControlPoint>(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint));
Debug.Assert(draggedControlPointIndex >= 0);
@ -276,25 +276,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void dragInProgress(DragEvent e)
{
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
var oldPosition = slider.Position;
double oldStartTime = slider.StartTime;
Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray();
var oldPosition = hitObject.Position;
double oldStartTime = hitObject.StartTime;
if (selectedControlPoints.Contains(slider.Path.ControlPoints[0]))
if (selectedControlPoints.Contains(hitObject.Path.ControlPoints[0]))
{
// Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account
// Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account
Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition);
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position;
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position;
slider.Position += movementDelta;
slider.StartTime = result?.Time ?? slider.StartTime;
hitObject.Position += movementDelta;
hitObject.StartTime = result?.Time ?? hitObject.StartTime;
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++)
{
var controlPoint = slider.Path.ControlPoints[i];
// Since control points are relative to the position of the slider, all points that are _not_ selected
var controlPoint = hitObject.Path.ControlPoints[i];
// Since control points are relative to the position of the hit object, all points that are _not_ selected
// need to be offset _back_ by the delta corresponding to the movement of the head point.
// All other selected control points (if any) will move together with the head point
// (and so they will not move at all, relative to each other).
@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition));
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position;
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
for (int i = 0; i < controlPoints.Count; ++i)
{
@ -317,23 +317,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}
// Snap the path to the current beat divisor before checking length validity.
slider.SnapTo(snapProvider);
hitObject.SnapTo(snapProvider);
if (!slider.Path.HasValidLength)
if (!hitObject.Path.HasValidLength)
{
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position = oldControlPoints[i];
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
hitObject.Path.ControlPoints[i].Position = oldControlPoints[i];
slider.Position = oldPosition;
slider.StartTime = oldStartTime;
hitObject.Position = oldPosition;
hitObject.StartTime = oldStartTime;
// Snap the path length again to undo the invalid length.
slider.SnapTo(snapProvider);
hitObject.SnapTo(snapProvider);
return;
}
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Type = dragPathTypes[i];
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
}
private void dragEnded() => changeHandler?.EndChange();

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private SliderBodyPiece bodyPiece;
private HitCirclePiece headCirclePiece;
private HitCirclePiece tailCirclePiece;
private PathControlPointVisualiser controlPointVisualiser;
private PathControlPointVisualiser<Slider> controlPointVisualiser;
private InputManager inputManager;
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
bodyPiece = new SliderBodyPiece(),
headCirclePiece = new HitCirclePiece(),
tailCirclePiece = new HitCirclePiece(),
controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
controlPointVisualiser = new PathControlPointVisualiser<Slider>(HitObject, false)
};
setState(SliderPlacementState.Initial);

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderCircleOverlay TailOverlay { get; private set; }
[CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
protected PathControlPointVisualiser<Slider> ControlPointVisualiser { get; private set; }
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; }
@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
if (ControlPointVisualiser == null)
{
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser<Slider>(HitObject, true)
{
RemoveControlPointsRequested = removeControlPoints,
SplitControlPointsRequested = splitControlPoints

View File

@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move mouse to common point", () =>
{
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("right click", () => InputManager.Click(MouseButton.Right));

View File

@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move mouse to controlpoint", () =>
{
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));