2023-12-20 07:05:32 +08:00
|
|
|
// 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.
|
|
|
|
|
2023-12-20 08:48:42 +08:00
|
|
|
using System.Linq;
|
2023-12-20 07:05:32 +08:00
|
|
|
using osu.Framework.Allocation;
|
2023-12-20 08:48:42 +08:00
|
|
|
using osu.Framework.Caching;
|
2023-12-20 07:05:32 +08:00
|
|
|
using osu.Framework.Extensions.Color4Extensions;
|
|
|
|
using osu.Framework.Input;
|
|
|
|
using osu.Framework.Input.Events;
|
|
|
|
using osu.Framework.Utils;
|
|
|
|
using osu.Game.Graphics;
|
2023-12-20 08:48:42 +08:00
|
|
|
using osu.Game.Rulesets.Objects;
|
2023-12-20 07:05:32 +08:00
|
|
|
using osu.Game.Rulesets.Osu.Objects;
|
|
|
|
using osu.Game.Screens.Edit;
|
|
|
|
using osuTK;
|
|
|
|
using osuTK.Graphics;
|
|
|
|
using osuTK.Input;
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|
|
|
{
|
|
|
|
public partial class SliderTailPiece : SliderCircleOverlay
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Whether this is currently being dragged.
|
|
|
|
/// </summary>
|
|
|
|
private bool isDragging;
|
|
|
|
|
|
|
|
private InputManager inputManager = null!;
|
|
|
|
|
2023-12-20 08:48:42 +08:00
|
|
|
private readonly Cached<SliderPath> fullPathCache = new Cached<SliderPath>();
|
|
|
|
|
2023-12-20 07:05:32 +08:00
|
|
|
[Resolved(CanBeNull = true)]
|
|
|
|
private EditorBeatmap? editorBeatmap { get; set; }
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private OsuColour colours { get; set; } = null!;
|
|
|
|
|
|
|
|
public SliderTailPiece(Slider slider, SliderPosition position)
|
|
|
|
: base(slider, position)
|
|
|
|
{
|
2023-12-20 08:48:42 +08:00
|
|
|
Slider.Path.ControlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate();
|
2023-12-20 07:05:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
|
|
|
inputManager = GetContainingInputManager();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos);
|
|
|
|
|
|
|
|
protected override bool OnHover(HoverEvent e)
|
|
|
|
{
|
|
|
|
updateCirclePieceColour();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnHoverLost(HoverLostEvent e)
|
|
|
|
{
|
|
|
|
updateCirclePieceColour();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateCirclePieceColour()
|
|
|
|
{
|
|
|
|
Color4 colour = colours.Yellow;
|
|
|
|
|
|
|
|
if (IsHovered)
|
|
|
|
colour = colour.Lighten(1);
|
|
|
|
|
|
|
|
CirclePiece.Colour = colour;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override bool OnDragStart(DragStartEvent e)
|
|
|
|
{
|
|
|
|
if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
isDragging = true;
|
|
|
|
editorBeatmap?.BeginChange();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnDrag(DragEvent e)
|
|
|
|
{
|
2023-12-20 08:48:42 +08:00
|
|
|
double oldDistance = Slider.Path.Distance;
|
|
|
|
double proposedDistance = findClosestPathDistance(e);
|
2023-12-20 07:05:32 +08:00
|
|
|
|
|
|
|
proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance);
|
|
|
|
proposedDistance = MathHelper.Clamp(proposedDistance,
|
2023-12-20 08:48:42 +08:00
|
|
|
0.1 * oldDistance / Slider.SliderVelocityMultiplier,
|
|
|
|
10 * oldDistance / Slider.SliderVelocityMultiplier);
|
2023-12-20 07:05:32 +08:00
|
|
|
|
2023-12-20 08:48:42 +08:00
|
|
|
if (Precision.AlmostEquals(proposedDistance, oldDistance))
|
2023-12-20 07:05:32 +08:00
|
|
|
return;
|
|
|
|
|
2023-12-20 08:48:42 +08:00
|
|
|
Slider.SliderVelocityMultiplier *= proposedDistance / oldDistance;
|
2023-12-20 07:05:32 +08:00
|
|
|
Slider.Path.ExpectedDistance.Value = proposedDistance;
|
|
|
|
editorBeatmap?.Update(Slider);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnDragEnd(DragEndEvent e)
|
|
|
|
{
|
|
|
|
if (isDragging)
|
|
|
|
{
|
|
|
|
editorBeatmap?.EndChange();
|
|
|
|
}
|
|
|
|
}
|
2023-12-20 08:48:42 +08:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Finds the expected distance value for which the slider end is closest to the mouse position.
|
|
|
|
/// </summary>
|
|
|
|
private double findClosestPathDistance(DragEvent e)
|
|
|
|
{
|
|
|
|
const double step1 = 10;
|
|
|
|
const double step2 = 0.1;
|
|
|
|
|
|
|
|
var desiredPosition = e.MousePosition - Slider.Position;
|
|
|
|
|
|
|
|
if (!fullPathCache.IsValid)
|
|
|
|
fullPathCache.Value = new SliderPath(Slider.Path.ControlPoints.ToArray());
|
|
|
|
|
|
|
|
// Do a linear search to find the closest point on the path to the mouse position.
|
|
|
|
double bestValue = 0;
|
|
|
|
double minDistance = double.MaxValue;
|
|
|
|
|
|
|
|
for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1)
|
|
|
|
{
|
|
|
|
double t = d / fullPathCache.Value.CalculatedDistance;
|
|
|
|
float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
|
|
|
|
|
|
|
|
if (dist >= minDistance) continue;
|
|
|
|
|
|
|
|
minDistance = dist;
|
|
|
|
bestValue = d;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do another linear search to fine-tune the result.
|
|
|
|
for (double d = bestValue - step1; d <= bestValue + step1; d += step2)
|
|
|
|
{
|
|
|
|
double t = d / fullPathCache.Value.CalculatedDistance;
|
|
|
|
float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
|
|
|
|
|
|
|
|
if (dist >= minDistance) continue;
|
|
|
|
|
|
|
|
minDistance = dist;
|
|
|
|
bestValue = d;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bestValue;
|
|
|
|
}
|
2023-12-20 07:05:32 +08:00
|
|
|
}
|
|
|
|
}
|