2019-01-24 16:43:03 +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.
2018-10-29 13:07:06 +08:00
2019-10-24 18:02:59 +08:00
using System ;
2021-03-22 22:59:06 +08:00
using System.Collections.Generic ;
2021-04-01 02:09:56 +08:00
using System.Linq ;
2018-10-29 13:07:06 +08:00
using osu.Framework.Allocation ;
2019-10-31 15:23:54 +08:00
using osu.Framework.Bindables ;
2020-04-13 20:59:23 +08:00
using osu.Framework.Extensions.Color4Extensions ;
2018-10-29 13:07:06 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2021-03-19 15:58:08 +08:00
using osu.Framework.Graphics.Cursor ;
2021-04-01 02:09:56 +08:00
using osu.Framework.Graphics.Primitives ;
2018-10-29 13:07:06 +08:00
using osu.Framework.Graphics.Shapes ;
using osu.Framework.Input.Events ;
2021-04-01 02:09:56 +08:00
using osu.Framework.Utils ;
2018-10-29 13:07:06 +08:00
using osu.Game.Graphics ;
2019-11-07 13:00:12 +08:00
using osu.Game.Rulesets.Edit ;
2019-12-09 19:49:59 +08:00
using osu.Game.Rulesets.Objects ;
2021-03-19 13:00:26 +08:00
using osu.Game.Rulesets.Objects.Types ;
2018-10-29 13:07:06 +08:00
using osu.Game.Rulesets.Osu.Objects ;
2020-04-09 21:00:56 +08:00
using osu.Game.Screens.Edit ;
2018-11-20 15:51:59 +08:00
using osuTK ;
2019-10-31 15:23:54 +08:00
using osuTK.Graphics ;
2019-11-12 17:35:28 +08:00
using osuTK.Input ;
2018-10-29 13:07:06 +08:00
2018-11-07 15:08:56 +08:00
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
2018-10-29 13:07:06 +08:00
{
2019-12-11 19:14:16 +08:00
/// <summary>
/// A visualisation of a single <see cref="PathControlPoint"/> in a <see cref="Slider"/>.
/// </summary>
2021-03-19 15:58:08 +08:00
public class PathControlPointPiece : BlueprintPiece < Slider > , IHasTooltip
2018-10-29 13:07:06 +08:00
{
2019-12-09 19:49:59 +08:00
public Action < PathControlPointPiece , MouseButtonEvent > RequestSelection ;
2021-03-24 09:56:32 +08:00
public List < PathControlPoint > PointsInSegment ;
2019-10-24 18:02:59 +08:00
2019-11-03 17:41:29 +08:00
public readonly BindableBool IsSelected = new BindableBool ( ) ;
2019-12-09 19:49:59 +08:00
public readonly PathControlPoint ControlPoint ;
2018-10-29 13:07:06 +08:00
2019-10-31 15:23:54 +08:00
private readonly Slider slider ;
private readonly Container marker ;
private readonly Drawable markerRing ;
2018-10-29 13:07:06 +08:00
2020-04-09 21:00:56 +08:00
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get ; set ; }
2019-11-07 13:00:12 +08:00
[Resolved(CanBeNull = true)]
2020-05-20 16:48:43 +08:00
private IPositionSnapProvider snapProvider { get ; set ; }
2019-11-07 13:00:12 +08:00
2018-10-29 13:07:06 +08:00
[Resolved]
private OsuColour colours { get ; set ; }
2021-04-01 02:09:56 +08:00
private IBindable < int > sliderVersion ;
2019-12-09 19:49:59 +08:00
private IBindable < Vector2 > sliderPosition ;
2020-12-01 13:17:36 +08:00
private IBindable < float > sliderScale ;
2019-12-10 10:20:08 +08:00
private IBindable < Vector2 > controlPointPosition ;
2019-12-09 19:49:59 +08:00
public PathControlPointPiece ( Slider slider , PathControlPoint controlPoint )
2018-10-29 13:07:06 +08:00
{
this . slider = slider ;
2019-12-09 19:49:59 +08:00
ControlPoint = controlPoint ;
2021-03-24 09:56:32 +08:00
slider . Path . ControlPoints . BindCollectionChanged ( ( _ , args ) = >
{
PointsInSegment = slider . Path . PointsInSegment ( controlPoint ) ;
} , runOnceImmediately : true ) ;
2018-10-29 13:07:06 +08:00
2020-04-13 20:59:23 +08:00
controlPoint . Type . BindValueChanged ( _ = > updateMarkerDisplay ( ) ) ;
2018-10-29 13:07:06 +08:00
Origin = Anchor . Centre ;
2018-10-29 15:15:04 +08:00
AutoSizeAxes = Axes . Both ;
2018-10-29 13:07:06 +08:00
InternalChildren = new Drawable [ ]
{
2019-10-31 15:23:54 +08:00
marker = new Container
2018-10-29 13:07:06 +08:00
{
2018-10-29 15:15:04 +08:00
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2019-10-31 15:23:54 +08:00
AutoSizeAxes = Axes . Both ,
Children = new [ ]
{
new Circle
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2020-12-01 13:17:36 +08:00
Size = new Vector2 ( 20 ) ,
2019-10-31 15:23:54 +08:00
} ,
markerRing = new CircularContainer
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2020-12-01 13:17:36 +08:00
Size = new Vector2 ( 28 ) ,
2019-10-31 15:23:54 +08:00
Masking = true ,
BorderThickness = 2 ,
BorderColour = Color4 . White ,
Alpha = 0 ,
Child = new Box
{
RelativeSizeAxes = Axes . Both ,
Alpha = 0 ,
AlwaysPresent = true
}
}
}
2018-10-29 13:07:06 +08:00
}
} ;
}
2019-12-09 19:49:59 +08:00
protected override void LoadComplete ( )
2018-10-29 13:07:06 +08:00
{
2019-12-09 19:49:59 +08:00
base . LoadComplete ( ) ;
2018-10-29 13:07:06 +08:00
2021-04-01 02:09:56 +08:00
sliderVersion = slider . Path . Version . GetBoundCopy ( ) ;
sliderVersion . BindValueChanged ( _ = > updatePathType ( ) ) ;
2019-12-09 19:49:59 +08:00
sliderPosition = slider . PositionBindable . GetBoundCopy ( ) ;
2019-12-10 10:20:08 +08:00
sliderPosition . BindValueChanged ( _ = > updateMarkerDisplay ( ) ) ;
2019-10-31 15:23:54 +08:00
2019-12-10 10:20:08 +08:00
controlPointPosition = ControlPoint . Position . GetBoundCopy ( ) ;
controlPointPosition . BindValueChanged ( _ = > updateMarkerDisplay ( ) ) ;
2019-10-31 15:23:54 +08:00
2020-12-01 13:17:36 +08:00
sliderScale = slider . ScaleBindable . GetBoundCopy ( ) ;
sliderScale . BindValueChanged ( _ = > updateMarkerDisplay ( ) ) ;
2019-12-09 19:49:59 +08:00
IsSelected . BindValueChanged ( _ = > updateMarkerDisplay ( ) ) ;
2018-10-29 13:07:06 +08:00
2019-12-09 19:49:59 +08:00
updateMarkerDisplay ( ) ;
2018-10-29 13:07:06 +08:00
}
2019-10-31 15:23:54 +08:00
// The connecting path is excluded from positional input
2018-10-29 15:15:04 +08:00
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos ) = > marker . ReceivePositionalInputAt ( screenSpacePos ) ;
2019-12-09 19:49:59 +08:00
protected override bool OnHover ( HoverEvent e )
{
updateMarkerDisplay ( ) ;
2019-12-09 23:08:38 +08:00
return false ;
2019-12-09 19:49:59 +08:00
}
protected override void OnHoverLost ( HoverLostEvent e )
{
updateMarkerDisplay ( ) ;
}
2019-10-31 16:13:10 +08:00
protected override bool OnMouseDown ( MouseDownEvent e )
{
2019-11-13 16:28:18 +08:00
if ( RequestSelection = = null )
2019-11-12 17:35:28 +08:00
return false ;
2019-11-13 16:28:18 +08:00
switch ( e . Button )
2019-11-03 18:59:37 +08:00
{
2019-11-13 16:28:18 +08:00
case MouseButton . Left :
2019-12-09 19:49:59 +08:00
RequestSelection . Invoke ( this , e ) ;
2019-11-13 16:28:18 +08:00
return true ;
case MouseButton . Right :
if ( ! IsSelected . Value )
2019-12-09 19:49:59 +08:00
RequestSelection . Invoke ( this , e ) ;
2019-11-13 16:28:18 +08:00
return false ; // Allow context menu to show
2019-11-03 18:59:37 +08:00
}
return false ;
2019-10-31 16:13:10 +08:00
}
2019-11-13 16:28:18 +08:00
protected override bool OnClick ( ClickEvent e ) = > RequestSelection ! = null ;
2019-11-05 01:21:50 +08:00
2020-12-01 12:46:30 +08:00
private Vector2 dragStartPosition ;
2021-03-22 22:59:06 +08:00
private PathType ? dragPathType ;
2020-12-01 12:46:30 +08:00
2020-04-09 21:00:56 +08:00
protected override bool OnDragStart ( DragStartEvent e )
{
2020-04-23 11:17:11 +08:00
if ( RequestSelection = = null )
return false ;
2020-04-09 21:00:56 +08:00
if ( e . Button = = MouseButton . Left )
{
2020-12-01 12:46:30 +08:00
dragStartPosition = ControlPoint . Position . Value ;
2021-03-22 22:59:06 +08:00
dragPathType = PointsInSegment [ 0 ] . Type . Value ;
2020-04-09 21:00:56 +08:00
changeHandler ? . BeginChange ( ) ;
return true ;
}
return false ;
}
2018-10-29 13:07:06 +08:00
2020-01-20 17:17:21 +08:00
protected override void OnDrag ( DragEvent e )
2018-10-29 13:07:06 +08:00
{
2019-12-09 19:49:59 +08:00
if ( ControlPoint = = slider . Path . ControlPoints [ 0 ] )
2018-10-29 14:36:43 +08:00
{
2019-11-07 13:00:12 +08:00
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
2020-05-22 17:57:28 +08:00
var result = snapProvider ? . SnapScreenSpacePositionToValidTime ( e . ScreenSpaceMousePosition ) ;
Vector2 movementDelta = Parent . ToLocalSpace ( result ? . ScreenSpacePosition ? ? e . ScreenSpaceMousePosition ) - slider . Position ;
2019-11-07 13:00:12 +08:00
slider . Position + = movementDelta ;
2020-05-20 17:19:21 +08:00
slider . StartTime = result ? . Time ? ? slider . StartTime ;
2018-10-29 14:36:43 +08:00
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
2019-12-05 18:53:31 +08:00
for ( int i = 1 ; i < slider . Path . ControlPoints . Count ; i + + )
slider . Path . ControlPoints [ i ] . Position . Value - = movementDelta ;
2018-10-29 14:36:43 +08:00
}
else
2020-12-01 12:46:30 +08:00
ControlPoint . Position . Value = dragStartPosition + ( e . MousePosition - e . MouseDownPosition ) ;
2021-03-22 22:59:06 +08:00
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
2021-03-24 09:57:47 +08:00
PointsInSegment [ 0 ] . Type . Value = dragPathType ;
2018-10-29 13:07:06 +08:00
}
2020-04-09 21:00:56 +08:00
protected override void OnDragEnd ( DragEndEvent e ) = > changeHandler ? . EndChange ( ) ;
2021-04-01 02:09:56 +08:00
/// <summary>
/// Handles correction of invalid path types.
/// </summary>
private void updatePathType ( )
{
if ( PointsInSegment [ 0 ] . Type . Value ! = PathType . PerfectCurve )
return ;
ReadOnlySpan < Vector2 > points = PointsInSegment . Select ( p = > p . Position . Value ) . ToArray ( ) ;
if ( points . Length ! = 3 )
return ;
RectangleF boundingBox = PathApproximator . CircularArcBoundingBox ( points ) ;
if ( boundingBox . Width > = 640 | | boundingBox . Height > = 480 )
PointsInSegment [ 0 ] . Type . Value = PathType . Bezier ;
}
2019-12-09 19:49:59 +08:00
/// <summary>
/// Updates the state of the circular control point marker.
/// </summary>
private void updateMarkerDisplay ( )
{
Position = slider . StackedPosition + ControlPoint . Position . Value ;
markerRing . Alpha = IsSelected . Value ? 1 : 0 ;
2021-03-19 13:00:26 +08:00
Color4 colour = getColourFromNodeType ( ) ;
2020-04-13 20:59:23 +08:00
2019-12-09 19:49:59 +08:00
if ( IsHovered | | IsSelected . Value )
2020-04-13 20:59:23 +08:00
colour = colour . Lighten ( 1 ) ;
2019-12-09 19:49:59 +08:00
marker . Colour = colour ;
2020-12-01 13:17:36 +08:00
marker . Scale = new Vector2 ( slider . Scale ) ;
2019-12-09 19:49:59 +08:00
}
2021-03-19 13:00:26 +08:00
private Color4 getColourFromNodeType ( )
{
if ( ! ( ControlPoint . Type . Value is PathType pathType ) )
return colours . Yellow ;
switch ( pathType )
{
case PathType . Catmull :
return colours . Seafoam ;
case PathType . Bezier :
return colours . Pink ;
case PathType . PerfectCurve :
return colours . PurpleDark ;
default :
return colours . Red ;
}
}
2021-03-19 15:58:08 +08:00
public string TooltipText = > ControlPoint . Type . Value . ToString ( ) ? ? string . Empty ;
2018-10-29 13:07:06 +08:00
}
}