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 ;
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 ;
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
{
2019-10-01 18:33:24 +08:00
protected readonly SliderBodyPiece BodyPiece ;
protected readonly SliderCircleSelectionBlueprint HeadBlueprint ;
protected readonly SliderCircleSelectionBlueprint TailBlueprint ;
2019-10-31 15:51:58 +08:00
protected readonly PathControlPointVisualiser ControlPointVisualiser ;
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 ; }
2018-11-06 16:56:04 +08:00
public SliderSelectionBlueprint ( DrawableSlider slider )
2018-04-13 17:19:50 +08:00
: base ( slider )
{
var sliderObject = ( Slider ) slider . HitObject ;
InternalChildren = new Drawable [ ]
{
2019-10-01 18:33:24 +08:00
BodyPiece = new SliderBodyPiece ( ) ,
HeadBlueprint = CreateCircleSelectionBlueprint ( slider , SliderPosition . Start ) ,
TailBlueprint = CreateCircleSelectionBlueprint ( slider , SliderPosition . End ) ,
2019-12-06 11:31:22 +08:00
ControlPointVisualiser = new PathControlPointVisualiser ( sliderObject , true )
2019-12-11 17:52:38 +08:00
{
RemoveControlPointsRequested = removeControlPoints
}
2018-04-13 17:19:50 +08:00
} ;
}
2019-12-06 15:36:08 +08:00
private IBindable < int > pathVersion ;
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
pathVersion = HitObject . Path . Version . GetBoundCopy ( ) ;
pathVersion . BindValueChanged ( _ = > updatePath ( ) ) ;
}
2019-09-27 17:45:22 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2019-10-01 18:33:24 +08:00
BodyPiece . UpdateFrom ( HitObject ) ;
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
2019-11-12 14:07:54 +08:00
case MouseButton . Left when e . ControlPressed & & IsSelected :
2019-11-12 14:00:57 +08:00
placementControlPointIndex = addControlPoint ( e . MousePosition ) ;
return true ; // Stop input from being handled and modifying the selection
}
2019-11-12 12:32:31 +08:00
return false ;
}
2019-11-12 14:00:57 +08:00
private int? placementControlPointIndex ;
protected override bool OnDragStart ( DragStartEvent e ) = > placementControlPointIndex ! = null ;
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
{
placementControlPointIndex = null ;
}
2019-12-11 17:52:38 +08:00
private BindableList < PathControlPoint > controlPoints = > HitObject . Path . ControlPoints ;
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 ;
2019-10-31 17:24:38 +08:00
UpdateHitObject ( ) ;
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-01-24 16:50:09 +08:00
public override Vector2 SelectionPoint = > ( ( DrawableSlider ) DrawableObject ) . HeadCircle . ScreenSpaceDrawQuad . Centre ;
2019-10-01 18:33:24 +08:00
2019-10-25 17:37:44 +08:00
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos ) = > BodyPiece . ReceivePositionalInputAt ( screenSpacePos ) ;
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
}
}