mirror of
https://github.com/ppy/osu
synced 2025-01-12 00:59:35 +00:00
Use draggable handle for length adjust
This commit is contained in:
parent
956bdbca50
commit
3926af1053
@ -3,7 +3,6 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -165,23 +164,35 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAdjustDistance()
|
||||
public void TestAdjustLength()
|
||||
{
|
||||
AddStep("start adjust length",
|
||||
() => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value());
|
||||
moveMouseToControlPoint(1);
|
||||
AddStep("end adjust length", () => InputManager.Click(MouseButton.Right));
|
||||
AddStep("move mouse to drag marker", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0);
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
AddStep("start drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move mouse to control point 1", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + slider.Path.ControlPoints[1].Position + new Vector2(60, 0);
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("expected distance halved",
|
||||
() => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
|
||||
|
||||
AddStep("start adjust length",
|
||||
() => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value());
|
||||
AddStep("move mouse beyond last control point", () =>
|
||||
AddStep("move mouse to drag marker", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0);
|
||||
Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0);
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
AddStep("end adjust length", () => InputManager.Click(MouseButton.Right));
|
||||
AddStep("start drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move mouse beyond last control point", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(100, 0);
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("expected distance is calculated distance",
|
||||
() => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
|
||||
|
||||
|
@ -1,24 +1,47 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
public partial class SliderCircleOverlay : CompositeDrawable
|
||||
{
|
||||
protected readonly HitCirclePiece CirclePiece;
|
||||
protected readonly Slider Slider;
|
||||
public RectangleF VisibleQuad
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = CirclePiece.ScreenSpaceDrawQuad.AABBFloat;
|
||||
|
||||
private readonly HitCircleOverlapMarker marker;
|
||||
if (endDragMarkerContainer == null) return result;
|
||||
|
||||
var size = result.Size * 1.4f;
|
||||
var location = result.TopLeft - result.Size * 0.2f;
|
||||
return new RectangleF(location, size);
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly HitCirclePiece CirclePiece;
|
||||
|
||||
private readonly Slider slider;
|
||||
private readonly SliderPosition position;
|
||||
private readonly HitCircleOverlapMarker marker;
|
||||
private readonly Container? endDragMarkerContainer;
|
||||
|
||||
public SliderCircleOverlay(Slider slider, SliderPosition position)
|
||||
{
|
||||
Slider = slider;
|
||||
this.slider = slider;
|
||||
this.position = position;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
@ -26,27 +49,121 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
marker = new HitCircleOverlapMarker(),
|
||||
CirclePiece = new HitCirclePiece(),
|
||||
};
|
||||
|
||||
if (position == SliderPosition.End)
|
||||
{
|
||||
AddInternal(endDragMarkerContainer = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding(-2.5f),
|
||||
Child = EndDragMarker = new SliderEndDragMarker()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public SliderEndDragMarker? EndDragMarker { get; }
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle :
|
||||
Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat!;
|
||||
var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle :
|
||||
slider.RepeatCount % 2 == 0 ? slider.TailCircle : slider.LastRepeat!;
|
||||
|
||||
CirclePiece.UpdateFrom(circle);
|
||||
marker.UpdateFrom(circle);
|
||||
|
||||
if (endDragMarkerContainer != null)
|
||||
{
|
||||
endDragMarkerContainer.Position = circle.Position;
|
||||
endDragMarkerContainer.Scale = CirclePiece.Scale * 1.2f;
|
||||
var diff = slider.Path.PositionAt(1) - slider.Path.PositionAt(0.99f);
|
||||
endDragMarkerContainer.Rotation = float.RadiansToDegrees(MathF.Atan2(diff.Y, diff.X));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Hide()
|
||||
{
|
||||
CirclePiece.Hide();
|
||||
endDragMarkerContainer?.Hide();
|
||||
}
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
CirclePiece.Show();
|
||||
endDragMarkerContainer?.Show();
|
||||
}
|
||||
|
||||
public partial class SliderEndDragMarker : SmoothPath
|
||||
{
|
||||
public Action<DragStartEvent>? StartDrag { get; set; }
|
||||
public Action<DragEvent>? Drag { get; set; }
|
||||
public Action? EndDrag { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var path = PathApproximator.CircularArcToPiecewiseLinear([
|
||||
new Vector2(0, OsuHitObject.OBJECT_RADIUS),
|
||||
new Vector2(OsuHitObject.OBJECT_RADIUS, 0),
|
||||
new Vector2(0, -OsuHitObject.OBJECT_RADIUS)
|
||||
]);
|
||||
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.CentreLeft;
|
||||
PathRadius = 5;
|
||||
Vertices = path;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
updateState();
|
||||
StartDrag?.Invoke(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
updateState();
|
||||
base.OnDrag(e);
|
||||
Drag?.Invoke(e);
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
updateState();
|
||||
EndDrag?.Invoke();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[Resolved(CanBeNull = true)]
|
||||
private BindableBeatDivisor beatDivisor { get; set; }
|
||||
|
||||
public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
|
||||
public override Quad SelectionQuad
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = BodyPiece.ScreenSpaceDrawQuad.AABBFloat;
|
||||
|
||||
result = RectangleF.Union(result, HeadOverlay.VisibleQuad);
|
||||
result = RectangleF.Union(result, TailOverlay.VisibleQuad);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
@ -63,7 +74,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
// Cached slider path which ignored the expected distance value.
|
||||
private readonly Cached<SliderPath> fullPathCache = new Cached<SliderPath>();
|
||||
private bool isAdjustingLength;
|
||||
|
||||
public SliderSelectionBlueprint(Slider slider)
|
||||
: base(slider)
|
||||
@ -79,6 +89,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
|
||||
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
|
||||
};
|
||||
|
||||
TailOverlay.EndDragMarker!.StartDrag += startAdjustingLength;
|
||||
TailOverlay.EndDragMarker.Drag += adjustLength;
|
||||
TailOverlay.EndDragMarker.EndDrag += endAdjustLength;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -141,9 +155,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
base.OnDeselected();
|
||||
|
||||
if (isAdjustingLength)
|
||||
endAdjustLength();
|
||||
|
||||
updateVisualDefinition();
|
||||
BodyPiece.RecyclePath();
|
||||
}
|
||||
@ -173,12 +184,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (isAdjustingLength)
|
||||
{
|
||||
endAdjustLength();
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (e.Button)
|
||||
{
|
||||
case MouseButton.Right:
|
||||
@ -202,18 +207,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
return false;
|
||||
}
|
||||
|
||||
private Vector2 lengthAdjustMouseOffset;
|
||||
|
||||
private void startAdjustingLength(DragStartEvent e)
|
||||
{
|
||||
lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1);
|
||||
changeHandler?.BeginChange();
|
||||
}
|
||||
|
||||
private void endAdjustLength()
|
||||
{
|
||||
trimExcessControlPoints(HitObject.Path);
|
||||
isAdjustingLength = false;
|
||||
changeHandler?.EndChange();
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
private void adjustLength(MouseEvent e)
|
||||
{
|
||||
if (!isAdjustingLength)
|
||||
return base.OnMouseMove(e);
|
||||
|
||||
double oldDistance = HitObject.Path.Distance;
|
||||
double proposedDistance = findClosestPathDistance(e);
|
||||
|
||||
@ -223,13 +232,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
10 * oldDistance / HitObject.SliderVelocityMultiplier);
|
||||
|
||||
if (Precision.AlmostEquals(proposedDistance, oldDistance))
|
||||
return false;
|
||||
return;
|
||||
|
||||
HitObject.SliderVelocityMultiplier *= proposedDistance / oldDistance;
|
||||
HitObject.Path.ExpectedDistance.Value = proposedDistance;
|
||||
editorBeatmap?.Update(HitObject);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -262,12 +269,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
/// <summary>
|
||||
/// Finds the expected distance value for which the slider end is closest to the mouse position.
|
||||
/// </summary>
|
||||
private double findClosestPathDistance(MouseMoveEvent e)
|
||||
private double findClosestPathDistance(MouseEvent e)
|
||||
{
|
||||
const double step1 = 10;
|
||||
const double step2 = 0.1;
|
||||
|
||||
var desiredPosition = e.MousePosition - HitObject.Position;
|
||||
var desiredPosition = ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position - lengthAdjustMouseOffset;
|
||||
|
||||
if (!fullPathCache.IsValid)
|
||||
fullPathCache.Value = new SliderPath(HitObject.Path.ControlPoints.ToArray());
|
||||
@ -525,11 +532,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
addControlPoint(rightClickPosition);
|
||||
changeHandler?.EndChange();
|
||||
}),
|
||||
new OsuMenuItem("Adjust length", MenuItemType.Standard, () =>
|
||||
{
|
||||
isAdjustingLength = true;
|
||||
changeHandler?.BeginChange();
|
||||
}),
|
||||
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
|
||||
};
|
||||
|
||||
@ -544,9 +546,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
if (isAdjustingLength)
|
||||
return true;
|
||||
|
||||
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user