1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-16 09:17:48 +08:00
osu-lazer/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs

228 lines
7.5 KiB
C#
Raw Normal View History

// 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.
2022-06-17 15:37:17 +08:00
#nullable disable
using System;
using osu.Framework.Allocation;
2019-10-31 15:23:54 +08:00
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
2018-11-20 15:51:59 +08:00
using osuTK;
2019-10-31 15:23:54 +08:00
using osuTK.Graphics;
using osuTK.Input;
2018-11-07 15:08:56 +08:00
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
2019-12-11 19:14:16 +08:00
/// <summary>
/// A visualisation of a single <see cref="PathControlPoint"/> in an osu hit object with a path.
2019-12-11 19:14:16 +08:00
/// </summary>
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointPiece{T}"/> visualises.</typeparam>
2023-01-26 16:12:41 +08:00
public partial class PathControlPointPiece<T> : BlueprintPiece<T>, IHasTooltip
where T : OsuHitObject, IHasPath
{
public Action<PathControlPointPiece<T>, MouseButtonEvent> RequestSelection;
public Action<PathControlPoint> DragStarted;
public Action<DragEvent> DragInProgress;
public Action DragEnded;
public readonly BindableBool IsSelected = new BindableBool();
public readonly PathControlPoint ControlPoint;
private readonly T hitObject;
private readonly Circle circle;
2019-10-31 15:23:54 +08:00
private readonly Drawable markerRing;
[Resolved]
private OsuColour colours { get; set; }
private IBindable<Vector2> hitObjectPosition;
private IBindable<float> hitObjectScale;
private IBindable<int> stackHeight;
public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
{
this.hitObject = hitObject;
ControlPoint = controlPoint;
controlPoint.Changed += updateMarkerDisplay;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
InternalChildren = new[]
{
circle = new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(20),
},
markerRing = new CircularProgress
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(28),
Alpha = 0,
InnerRadius = 0.1f,
Progress = 1
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
hitObjectPosition.BindValueChanged(_ => updateMarkerDisplay());
2019-10-31 15:23:54 +08:00
hitObjectScale = hitObject.ScaleBindable.GetBoundCopy();
hitObjectScale.BindValueChanged(_ => updateMarkerDisplay());
stackHeight = hitObject.StackHeightBindable.GetBoundCopy();
stackHeight.BindValueChanged(_ => updateMarkerDisplay());
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
updateMarkerDisplay();
}
2019-10-31 15:23:54 +08:00
// The connecting path is excluded from positional input
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
protected override bool OnHover(HoverEvent e)
{
updateMarkerDisplay();
2019-12-09 23:08:38 +08:00
return false;
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateMarkerDisplay();
}
// Used to pair up mouse down/drag events with their corresponding mouse up events,
// to avoid deselecting the piece by accident when the mouse up corresponding to the mouse down/drag fires.
private bool keepSelection;
2019-10-31 16:13:10 +08:00
protected override bool OnMouseDown(MouseDownEvent e)
{
if (RequestSelection == null)
return false;
switch (e.Button)
{
case MouseButton.Left:
// if control is pressed, do not do anything as the user may be adding to current selection
// or dragging all currently selected control points.
// if it isn't and the user's intent is to deselect, deselection will happen on mouse up.
if (e.ControlPressed && IsSelected.Value)
return true;
RequestSelection.Invoke(this, e);
keepSelection = true;
return true;
case MouseButton.Right:
if (!IsSelected.Value)
RequestSelection.Invoke(this, e);
keepSelection = true;
return false; // Allow context menu to show
}
return false;
2019-10-31 16:13:10 +08:00
}
protected override void OnMouseUp(MouseUpEvent e)
{
base.OnMouseUp(e);
// ctrl+click deselects this piece, but only if this event
// wasn't immediately preceded by a matching mouse down or drag.
if (IsSelected.Value && e.ControlPressed && !keepSelection)
IsSelected.Value = false;
keepSelection = false;
}
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
protected override bool OnDragStart(DragStartEvent e)
{
2020-04-23 11:17:11 +08:00
if (RequestSelection == null)
return false;
if (e.Button == MouseButton.Left)
{
DragStarted?.Invoke(ControlPoint);
keepSelection = true;
return true;
}
return false;
}
protected override void OnDrag(DragEvent e) => DragInProgress?.Invoke(e);
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
/// <summary>
/// Updates the state of the circular control point marker.
/// </summary>
private void updateMarkerDisplay()
{
Position = hitObject.StackedPosition + ControlPoint.Position;
markerRing.Alpha = IsSelected.Value ? 1 : 0;
Color4 colour = getColourFromNodeType();
if (IsHovered || IsSelected.Value)
colour = colour.Lighten(1);
Colour = colour;
Scale = new Vector2(hitObject.Scale);
}
private Color4 getColourFromNodeType()
{
if (ControlPoint.Type is not PathType pathType)
return colours.Yellow;
2023-11-13 15:24:09 +08:00
switch (pathType.Type)
{
2023-11-13 15:24:09 +08:00
case SplineType.Catmull:
return colours.SeaFoam;
2023-11-13 15:24:09 +08:00
case SplineType.BSpline:
if (!pathType.Degree.HasValue)
return colours.PinkLighter;
int idx = Math.Clamp(pathType.Degree.Value, 0, 3);
return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx];
2023-11-13 15:24:09 +08:00
case SplineType.PerfectCurve:
return colours.PurpleDark;
default:
return colours.Red;
}
}
public LocalisableString TooltipText => ControlPoint.Type?.Description ?? string.Empty;
}
}