1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-21 22:12:53 +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 public partial class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene
{ {
private Slider slider; private Slider slider;
private PathControlPointVisualiser visualiser; private PathControlPointVisualiser<Slider> visualiser;
[SetUp] [SetUp]
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointPathType(3, null); 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, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre

View File

@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
} }
private void assertSelectionCount(int count) => 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) => private void assertSelected(int index) =>
AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected", 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) => private void moveMouseToRelativePosition(Vector2 relativePosition) =>
AddStep($"move mouse to {relativePosition}", () => AddStep($"move mouse to {relativePosition}", () =>
@ -202,12 +202,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
moveMouseToControlPoint(2); moveMouseToControlPoint(2);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); 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)); addMovementStep(new Vector2(450, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); 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)); assertControlPointPosition(2, new Vector2(450, 50));
assertControlPointType(2, PathType.PerfectCurve); assertControlPointType(2, PathType.PerfectCurve);
@ -236,12 +236,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
moveMouseToControlPoint(3); moveMouseToControlPoint(3);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); 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)); addMovementStep(new Vector2(550, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); 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. // 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. // 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 SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public new PathControlPointVisualiser<Slider> ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider) public TestSliderBlueprint(Slider slider)
: base(slider) : base(slider)

View File

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

View File

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

View File

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

View File

@ -8,34 +8,36 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Lines;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
/// <summary> /// <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> /// </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; public readonly PathControlPoint ControlPoint;
private readonly Path path; private readonly Path path;
private readonly Slider slider; private readonly T hitObject;
public int ControlPointIndex { get; set; } public int ControlPointIndex { get; set; }
private IBindable<Vector2> sliderPosition; private IBindable<Vector2> hitObjectPosition;
private IBindable<int> pathVersion; private IBindable<int> pathVersion;
public PathControlPointConnectionPiece(Slider slider, int controlPointIndex) public PathControlPointConnectionPiece(T hitObject, int controlPointIndex)
{ {
this.slider = slider; this.hitObject = hitObject;
ControlPointIndex = controlPointIndex; ControlPointIndex = controlPointIndex;
Origin = Anchor.Centre; Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
ControlPoint = slider.Path.ControlPoints[controlPointIndex]; ControlPoint = hitObject.Path.ControlPoints[controlPointIndex];
InternalChild = path = new SmoothPath InternalChild = path = new SmoothPath
{ {
@ -48,10 +50,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
base.LoadComplete(); base.LoadComplete();
sliderPosition = slider.PositionBindable.GetBoundCopy(); hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
sliderPosition.BindValueChanged(_ => updateConnectingPath()); hitObjectPosition.BindValueChanged(_ => updateConnectingPath());
pathVersion = slider.Path.Version.GetBoundCopy(); pathVersion = hitObject.Path.Version.GetBoundCopy();
pathVersion.BindValueChanged(_ => updateConnectingPath()); pathVersion.BindValueChanged(_ => updateConnectingPath());
updateConnectingPath(); updateConnectingPath();
@ -62,16 +64,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary> /// </summary>
private void updateConnectingPath() private void updateConnectingPath()
{ {
Position = slider.StackedPosition + ControlPoint.Position; Position = hitObject.StackedPosition + ControlPoint.Position;
path.ClearVertices(); path.ClearVertices();
int nextIndex = ControlPointIndex + 1; int nextIndex = ControlPointIndex + 1;
if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count) if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count)
return; return;
path.AddVertex(Vector2.Zero); 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); path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
} }

View File

@ -29,11 +29,12 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
/// <summary> /// <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> /// </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<PathControlPoint> DragStarted;
public Action<DragEvent> DragInProgress; 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 BindableBool IsSelected = new BindableBool();
public readonly PathControlPoint ControlPoint; public readonly PathControlPoint ControlPoint;
private readonly Slider slider; private readonly T hitObject;
private readonly Container marker; private readonly Container marker;
private readonly Drawable markerRing; private readonly Drawable markerRing;
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
private IBindable<Vector2> sliderPosition; private IBindable<Vector2> hitObjectPosition;
private IBindable<float> sliderScale; private IBindable<float> hitObjectScale;
[UsedImplicitly] [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; ControlPoint = controlPoint;
// we don't want to run the path type update on construction as it may inadvertently change the slider. // we don't want to run the path type update on construction as it may inadvertently change the hit object.
cachePoints(slider); 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. // 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. // this avoids inadvertently changing the hit object path type for batch operations.
sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() => hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
{ {
cachePoints(slider); cachePoints(hitObject);
updatePathType(); updatePathType();
})); }));
@ -120,11 +121,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
base.LoadComplete(); base.LoadComplete();
sliderPosition = slider.PositionBindable.GetBoundCopy(); hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); hitObjectPosition.BindValueChanged(_ => updateMarkerDisplay());
sliderScale = slider.ScaleBindable.GetBoundCopy(); hitObjectScale = hitObject.ScaleBindable.GetBoundCopy();
sliderScale.BindValueChanged(_ => updateMarkerDisplay()); hitObjectScale.BindValueChanged(_ => updateMarkerDisplay());
IsSelected.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(); 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> /// <summary>
/// Handles correction of invalid path types. /// Handles correction of invalid path types.
@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary> /// </summary>
private void updateMarkerDisplay() private void updateMarkerDisplay()
{ {
Position = slider.StackedPosition + ControlPoint.Position; Position = hitObject.StackedPosition + ControlPoint.Position;
markerRing.Alpha = IsSelected.Value ? 1 : 0; markerRing.Alpha = IsSelected.Value ? 1 : 0;
@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
colour = colour.Lighten(1); colour = colour.Lighten(1);
marker.Colour = colour; marker.Colour = colour;
marker.Scale = new Vector2(slider.Scale); marker.Scale = new Vector2(hitObject.Scale);
} }
private Color4 getColourFromNodeType() private Color4 getColourFromNodeType()

View File

@ -29,15 +29,15 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components 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. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
internal readonly Container<PathControlPointPiece> Pieces; internal readonly Container<PathControlPointPiece<T>> Pieces;
internal readonly Container<PathControlPointConnectionPiece> Connections; internal readonly Container<PathControlPointConnectionPiece<T>> Connections;
private readonly IBindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>(); private readonly IBindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
private readonly Slider slider; private readonly T hitObject;
private readonly bool allowSelection; private readonly bool allowSelection;
private InputManager inputManager; private InputManager inputManager;
@ -48,17 +48,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; } 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; this.allowSelection = allowSelection;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
Connections = new Container<PathControlPointConnectionPiece> { RelativeSizeAxes = Axes.Both }, Connections = new Container<PathControlPointConnectionPiece<T>> { RelativeSizeAxes = Axes.Both },
Pieces = new Container<PathControlPointPiece> { 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(); inputManager = GetContainingInputManager();
controlPoints.CollectionChanged += onControlPointsChanged; controlPoints.CollectionChanged += onControlPointsChanged;
controlPoints.BindTo(slider.Path.ControlPoints); controlPoints.BindTo(hitObject.Path.ControlPoints);
} }
/// <summary> /// <summary>
/// Selects the <see cref="PathControlPointPiece"/> corresponding to the given <paramref name="pathControlPoint"/>, /// Selects the <see cref="PathControlPointPiece{T}"/> corresponding to the given <paramref name="pathControlPoint"/>,
/// and deselects all other <see cref="PathControlPointPiece"/>s. /// and deselects all other <see cref="PathControlPointPiece{T}"/>s.
/// </summary> /// </summary>
public void SetSelectionTo(PathControlPoint pathControlPoint) public void SetSelectionTo(PathControlPoint pathControlPoint)
{ {
@ -124,8 +124,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return true; return true;
} }
private bool isSplittable(PathControlPointPiece p) => private bool isSplittable(PathControlPointPiece<T> p) =>
// A slider can only be split on control points which connect two different slider segments. // 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(); p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault();
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e) 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]; 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) if (allowSelection)
d.RequestSelection = selectionRequested; d.RequestSelection = selectionRequested;
@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
d.DragEnded = dragEnded; d.DragEnded = dragEnded;
})); }));
Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i)); Connections.Add(new PathControlPointConnectionPiece<T>(hitObject, e.NewStartingIndex + i));
} }
break; 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) if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
piece.IsSelected.Toggle(); piece.IsSelected.Toggle();
@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary> /// </summary>
/// <param name="piece">The control point piece that we want to change the path type of.</param> /// <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> /// <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); 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) private void dragStarted(PathControlPoint controlPoint)
{ {
dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray(); dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray();
dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray(); dragPathTypes = hitObject.Path.ControlPoints.Select(point => point.Type).ToArray();
draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint); draggedControlPointIndex = hitObject.Path.ControlPoints.IndexOf(controlPoint);
selectedControlPoints = new HashSet<PathControlPoint>(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint)); selectedControlPoints = new HashSet<PathControlPoint>(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint));
Debug.Assert(draggedControlPointIndex >= 0); Debug.Assert(draggedControlPointIndex >= 0);
@ -276,25 +276,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void dragInProgress(DragEvent e) private void dragInProgress(DragEvent e)
{ {
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray(); Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray();
var oldPosition = slider.Position; var oldPosition = hitObject.Position;
double oldStartTime = slider.StartTime; 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])); Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition); 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; hitObject.Position += movementDelta;
slider.StartTime = result?.Time ?? slider.StartTime; 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]; var controlPoint = hitObject.Path.ControlPoints[i];
// Since control points are relative to the position of the slider, all points that are _not_ selected // 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. // 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 // 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). // (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)); 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) 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. // 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++) for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position = oldControlPoints[i]; hitObject.Path.ControlPoints[i].Position = oldControlPoints[i];
slider.Position = oldPosition; hitObject.Position = oldPosition;
slider.StartTime = oldStartTime; hitObject.StartTime = oldStartTime;
// Snap the path length again to undo the invalid length. // Snap the path length again to undo the invalid length.
slider.SnapTo(snapProvider); hitObject.SnapTo(snapProvider);
return; return;
} }
// Maintain the path types in case they got defaulted to bezier at some point during the drag. // 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++) for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Type = dragPathTypes[i]; hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
} }
private void dragEnded() => changeHandler?.EndChange(); private void dragEnded() => changeHandler?.EndChange();

View File

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

View File

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

View File

@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move mouse to common point", () => 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); InputManager.MoveMouseTo(pos);
}); });
AddStep("right click", () => InputManager.Click(MouseButton.Right)); AddStep("right click", () => InputManager.Click(MouseButton.Right));

View File

@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move mouse to controlpoint", () => 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); InputManager.MoveMouseTo(pos);
}); });
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));