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-04-13 17:19:50 +08:00
2019-12-11 17:52:38 +08:00
using System.Collections.Generic ;
2019-11-12 14:00:57 +08:00
using System.Diagnostics ;
2020-11-03 19:45:48 +08:00
using System.Linq ;
2019-10-24 18:02:59 +08:00
using osu.Framework.Allocation ;
2019-12-06 15:36:08 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
2019-11-12 13:44:11 +08:00
using osu.Framework.Graphics.Primitives ;
2019-11-12 12:32:31 +08:00
using osu.Framework.Graphics.UserInterface ;
using osu.Framework.Input.Events ;
using osu.Game.Graphics.UserInterface ;
2019-10-24 18:02:59 +08:00
using osu.Game.Rulesets.Edit ;
using osu.Game.Rulesets.Objects ;
2018-11-07 15:08:56 +08:00
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Osu.Objects ;
using osu.Game.Rulesets.Osu.Objects.Drawables ;
2020-04-09 18:54:58 +08:00
using osu.Game.Screens.Edit ;
2019-12-11 17:52:38 +08:00
using osu.Game.Screens.Edit.Compose ;
2018-11-20 15:51:59 +08:00
using osuTK ;
2019-11-12 12:32:31 +08:00
using osuTK.Input ;
2018-04-13 17:19:50 +08:00
2018-11-07 15:08:56 +08:00
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
2018-04-13 17:19:50 +08:00
{
2019-11-12 12:38:42 +08:00
public class SliderSelectionBlueprint : OsuSelectionBlueprint < Slider >
2018-04-13 17:19:50 +08:00
{
2020-10-09 15:54:43 +08:00
protected SliderBodyPiece BodyPiece { get ; private set ; }
protected SliderCircleSelectionBlueprint HeadBlueprint { get ; private set ; }
protected SliderCircleSelectionBlueprint TailBlueprint { get ; private set ; }
protected PathControlPointVisualiser ControlPointVisualiser { get ; private set ; }
2020-10-09 13:05:00 +08:00
private readonly DrawableSlider slider ;
2018-04-13 17:19:50 +08:00
2019-10-24 18:04:00 +08:00
[Resolved(CanBeNull = true)]
2019-10-24 18:02:59 +08:00
private HitObjectComposer composer { get ; set ; }
2019-12-11 17:52:38 +08:00
[Resolved(CanBeNull = true)]
private IPlacementHandler placementHandler { get ; set ; }
2020-04-09 18:54:58 +08:00
[Resolved(CanBeNull = true)]
private EditorBeatmap editorBeatmap { get ; set ; }
2020-04-09 21:00:56 +08:00
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get ; set ; }
2020-11-20 21:43:10 +08:00
private readonly BindableList < PathControlPoint > controlPoints = new BindableList < PathControlPoint > ( ) ;
private readonly IBindable < int > pathVersion = new Bindable < int > ( ) ;
2018-11-06 16:56:04 +08:00
public SliderSelectionBlueprint ( DrawableSlider slider )
2018-04-13 17:19:50 +08:00
: base ( slider )
{
2020-10-09 13:05:00 +08:00
this . slider = slider ;
}
2018-04-13 17:19:50 +08:00
2020-10-09 13:05:00 +08:00
[BackgroundDependencyLoader]
private void load ( )
{
2018-04-13 17:19:50 +08:00
InternalChildren = new Drawable [ ]
{
2019-10-01 18:33:24 +08:00
BodyPiece = new SliderBodyPiece ( ) ,
HeadBlueprint = CreateCircleSelectionBlueprint ( slider , SliderPosition . Start ) ,
TailBlueprint = CreateCircleSelectionBlueprint ( slider , SliderPosition . End ) ,
2018-04-13 17:19:50 +08:00
} ;
}
2019-12-06 15:36:08 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2020-11-20 21:43:10 +08:00
controlPoints . BindTo ( HitObject . Path . ControlPoints ) ;
pathVersion . BindTo ( HitObject . Path . Version ) ;
2019-12-06 15:36:08 +08:00
pathVersion . BindValueChanged ( _ = > updatePath ( ) ) ;
2020-10-09 13:04:26 +08:00
BodyPiece . UpdateFrom ( HitObject ) ;
2019-12-06 15:36:08 +08:00
}
2020-11-03 19:45:48 +08:00
public override bool HandleQuickDeletion ( )
{
2020-11-07 08:56:41 +08:00
var hoveredControlPoint = ControlPointVisualiser ? . Pieces . FirstOrDefault ( p = > p . IsHovered ) ;
2020-11-03 19:45:48 +08:00
if ( hoveredControlPoint = = null )
return false ;
hoveredControlPoint . IsSelected . Value = true ;
ControlPointVisualiser . DeleteSelected ( ) ;
return true ;
}
2019-09-27 17:45:22 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2020-10-09 13:04:26 +08:00
if ( IsSelected )
BodyPiece . UpdateFrom ( HitObject ) ;
2019-09-27 17:45:22 +08:00
}
2020-10-09 13:05:00 +08:00
protected override void OnSelected ( )
{
2020-11-05 12:51:46 +08:00
AddInternal ( ControlPointVisualiser = new PathControlPointVisualiser ( slider . HitObject , true )
2020-10-09 13:05:00 +08:00
{
RemoveControlPointsRequested = removeControlPoints
} ) ;
base . OnSelected ( ) ;
}
protected override void OnDeselected ( )
{
base . OnDeselected ( ) ;
// throw away frame buffers on deselection.
ControlPointVisualiser ? . Expire ( ) ;
BodyPiece . RecyclePath ( ) ;
2019-09-27 17:45:22 +08:00
}
2019-11-12 12:32:31 +08:00
private Vector2 rightClickPosition ;
protected override bool OnMouseDown ( MouseDownEvent e )
{
2019-11-12 14:00:57 +08:00
switch ( e . Button )
{
case MouseButton . Right :
rightClickPosition = e . MouseDownPosition ;
return false ; // Allow right click to be handled by context menu
2020-11-05 12:58:41 +08:00
case MouseButton . Left :
if ( e . ControlPressed & & IsSelected )
{
placementControlPointIndex = addControlPoint ( e . MousePosition ) ;
return true ; // Stop input from being handled and modifying the selection
}
break ;
2019-11-12 14:00:57 +08:00
}
2019-11-12 12:32:31 +08:00
return false ;
}
2019-11-12 14:00:57 +08:00
private int? placementControlPointIndex ;
2020-04-09 21:00:56 +08:00
protected override bool OnDragStart ( DragStartEvent e )
{
if ( placementControlPointIndex ! = null )
{
changeHandler ? . BeginChange ( ) ;
return true ;
}
return false ;
}
2019-11-12 14:00:57 +08:00
2020-01-20 17:17:21 +08:00
protected override void OnDrag ( DragEvent e )
2019-11-12 12:32:31 +08:00
{
2019-11-12 14:00:57 +08:00
Debug . Assert ( placementControlPointIndex ! = null ) ;
2019-12-05 18:53:31 +08:00
HitObject . Path . ControlPoints [ placementControlPointIndex . Value ] . Position . Value = e . MousePosition - HitObject . Position ;
2019-11-12 14:00:57 +08:00
}
2020-01-20 17:17:21 +08:00
protected override void OnDragEnd ( DragEndEvent e )
2019-11-12 14:00:57 +08:00
{
2020-04-09 21:00:56 +08:00
if ( placementControlPointIndex ! = null )
{
placementControlPointIndex = null ;
changeHandler ? . EndChange ( ) ;
}
2019-11-12 14:00:57 +08:00
}
private int addControlPoint ( Vector2 position )
{
position - = HitObject . Position ;
2019-11-12 12:32:31 +08:00
2019-11-12 13:37:07 +08:00
int insertionIndex = 0 ;
float minDistance = float . MaxValue ;
2019-11-12 12:32:31 +08:00
2019-12-11 17:52:38 +08:00
for ( int i = 0 ; i < controlPoints . Count - 1 ; i + + )
2019-11-12 13:37:07 +08:00
{
2019-12-11 17:52:38 +08:00
float dist = new Line ( controlPoints [ i ] . Position . Value , controlPoints [ i + 1 ] . Position . Value ) . DistanceToPoint ( position ) ;
2019-11-12 13:37:07 +08:00
if ( dist < minDistance )
{
insertionIndex = i + 1 ;
minDistance = dist ;
}
2019-11-12 12:32:31 +08:00
}
2019-11-12 13:37:07 +08:00
// Move the control points from the insertion index onwards to make room for the insertion
2019-12-11 17:52:38 +08:00
controlPoints . Insert ( insertionIndex , new PathControlPoint { Position = { Value = position } } ) ;
2019-11-12 14:00:57 +08:00
return insertionIndex ;
2019-11-12 12:32:31 +08:00
}
2019-12-11 17:52:38 +08:00
private void removeControlPoints ( List < PathControlPoint > toRemove )
{
// Ensure that there are any points to be deleted
if ( toRemove . Count = = 0 )
return ;
foreach ( var c in toRemove )
{
// The first control point in the slider must have a type, so take it from the previous "first" one
// Todo: Should be handled within SliderPath itself
if ( c = = controlPoints [ 0 ] & & controlPoints . Count > 1 & & controlPoints [ 1 ] . Type . Value = = null )
controlPoints [ 1 ] . Type . Value = controlPoints [ 0 ] . Type . Value ;
controlPoints . Remove ( c ) ;
}
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
if ( controlPoints . Count < = 1 )
{
placementHandler ? . Delete ( HitObject ) ;
return ;
}
// The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
Vector2 first = controlPoints [ 0 ] . Position . Value ;
foreach ( var c in controlPoints )
c . Position . Value - = first ;
HitObject . Position + = first ;
}
2019-12-06 15:36:08 +08:00
private void updatePath ( )
2019-10-24 18:02:59 +08:00
{
2019-12-06 14:53:19 +08:00
HitObject . Path . ExpectedDistance . Value = composer ? . GetSnappedDistanceFromDistance ( HitObject . StartTime , ( float ) HitObject . Path . CalculatedDistance ) ? ? ( float ) HitObject . Path . CalculatedDistance ;
2020-10-08 17:06:46 +08:00
editorBeatmap ? . Update ( HitObject ) ;
2019-10-24 18:02:59 +08:00
}
2019-11-12 12:38:42 +08:00
public override MenuItem [ ] ContextMenuItems = > new MenuItem [ ]
2019-11-12 12:32:31 +08:00
{
2019-11-12 14:00:57 +08:00
new OsuMenuItem ( "Add control point" , MenuItemType . Standard , ( ) = > addControlPoint ( rightClickPosition ) ) ,
2019-11-12 12:32:31 +08:00
} ;
2020-09-28 16:32:57 +08:00
public override Vector2 ScreenSpaceSelectionPoint = > BodyPiece . ToScreenSpace ( BodyPiece . PathStartLocation ) ;
2019-10-01 18:33:24 +08:00
2020-11-03 19:45:48 +08:00
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos ) = >
2020-11-05 12:58:52 +08:00
BodyPiece . ReceivePositionalInputAt ( screenSpacePos ) | | ControlPointVisualiser ? . Pieces . Any ( p = > p . ReceivePositionalInputAt ( screenSpacePos ) ) = = true ;
2019-10-25 17:37:44 +08:00
2019-10-01 18:33:24 +08:00
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint ( DrawableSlider slider , SliderPosition position ) = > new SliderCircleSelectionBlueprint ( slider , position ) ;
2018-04-13 17:19:50 +08:00
}
}