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-10-16 19:07:11 +08:00
using System ;
2020-11-12 18:52:02 +08:00
using System.Collections.Generic ;
2020-02-17 14:06:14 +08:00
using System.Collections.Specialized ;
2019-10-23 17:58:15 +08:00
using System.Diagnostics ;
2018-10-18 15:36:06 +08:00
using System.Linq ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Allocation ;
2020-01-21 19:46:39 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2018-11-06 16:51:26 +08:00
using osu.Framework.Graphics.Primitives ;
2019-10-03 15:14:42 +08:00
using osu.Framework.Input ;
2019-11-11 12:41:10 +08:00
using osu.Framework.Input.Bindings ;
2018-10-02 11:02:47 +08:00
using osu.Framework.Input.Events ;
2020-06-23 17:42:56 +08:00
using osu.Game.Graphics.UserInterface ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Edit ;
2019-08-29 15:06:40 +08:00
using osu.Game.Rulesets.Objects ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Objects.Drawables ;
2019-10-16 19:34:16 +08:00
using osuTK ;
2019-11-07 21:51:49 +08:00
using osuTK.Input ;
2018-04-13 17:19:50 +08:00
2018-11-06 17:28:22 +08:00
namespace osu.Game.Screens.Edit.Compose.Components
2018-04-13 17:19:50 +08:00
{
2020-01-02 10:46:18 +08:00
/// <summary>
/// A container which provides a "blueprint" display of hitobjects.
2020-09-25 13:19:35 +08:00
/// Includes selection and manipulation support via a <see cref="Components.SelectionHandler"/>.
2020-01-02 10:46:18 +08:00
/// </summary>
public abstract class BlueprintContainer : CompositeDrawable , IKeyBindingHandler < PlatformAction >
2018-04-13 17:19:50 +08:00
{
2020-01-16 10:54:03 +08:00
protected DragBox DragBox { get ; private set ; }
2020-01-15 18:09:49 +08:00
2020-09-24 13:22:18 +08:00
public Container < SelectionBlueprint > SelectionBlueprints { get ; private set ; }
2020-01-15 18:09:49 +08:00
2020-09-25 16:42:49 +08:00
protected SelectionHandler SelectionHandler { get ; private set ; }
2018-11-06 16:51:26 +08:00
2020-11-13 16:10:29 +08:00
protected readonly HitObjectComposer Composer ;
2020-04-09 21:00:56 +08:00
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get ; set ; }
2019-11-06 17:15:57 +08:00
[Resolved]
2020-11-26 18:20:39 +08:00
protected EditorClock EditorClock { get ; private set ; }
2019-11-06 17:15:57 +08:00
2019-08-29 15:06:40 +08:00
[Resolved]
2020-09-25 13:10:30 +08:00
protected EditorBeatmap Beatmap { get ; private set ; }
2019-08-29 15:06:40 +08:00
2020-01-21 19:46:39 +08:00
private readonly BindableList < HitObject > selectedHitObjects = new BindableList < HitObject > ( ) ;
2020-11-12 18:52:02 +08:00
private readonly Dictionary < HitObject , SelectionBlueprint > blueprintMap = new Dictionary < HitObject , SelectionBlueprint > ( ) ;
2020-01-21 19:46:39 +08:00
2020-01-15 18:09:49 +08:00
[Resolved(canBeNull: true)]
2020-05-20 16:48:43 +08:00
private IPositionSnapProvider snapProvider { get ; set ; }
2020-01-15 18:09:49 +08:00
2020-11-12 18:52:02 +08:00
protected BlueprintContainer ( HitObjectComposer composer )
2018-04-13 17:19:50 +08:00
{
2020-11-13 16:10:29 +08:00
Composer = composer ;
2020-11-12 18:52:02 +08:00
2018-04-13 17:19:50 +08:00
RelativeSizeAxes = Axes . Both ;
}
[BackgroundDependencyLoader]
2018-10-17 16:41:17 +08:00
private void load ( )
2018-04-13 17:19:50 +08:00
{
2020-09-25 13:19:35 +08:00
SelectionHandler = CreateSelectionHandler ( ) ;
SelectionHandler . DeselectAll = deselectAll ;
2018-04-13 17:19:50 +08:00
2020-01-15 18:09:49 +08:00
AddRangeInternal ( new [ ]
2018-04-13 17:19:50 +08:00
{
2020-09-08 16:22:59 +08:00
DragBox = CreateDragBox ( selectBlueprintsFromDragRectangle ) ,
2020-09-25 13:19:35 +08:00
SelectionHandler ,
2020-02-07 17:04:10 +08:00
SelectionBlueprints = CreateSelectionBlueprintContainer ( ) ,
2020-09-25 13:19:35 +08:00
SelectionHandler . CreateProxy ( ) ,
2020-01-16 10:54:03 +08:00
DragBox . CreateProxy ( ) . With ( p = > p . Depth = float . MinValue )
2020-01-15 18:09:49 +08:00
} ) ;
2018-06-07 15:27:49 +08:00
2020-11-12 18:52:02 +08:00
// For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context.
2020-11-13 16:10:29 +08:00
if ( Composer ! = null )
2020-11-12 18:52:02 +08:00
{
2020-11-13 16:10:29 +08:00
foreach ( var obj in Composer . HitObjects )
2020-11-12 18:52:02 +08:00
addBlueprintFor ( obj . HitObject ) ;
}
2020-01-21 19:46:39 +08:00
2020-09-25 13:10:30 +08:00
selectedHitObjects . BindTo ( Beatmap . SelectedHitObjects ) ;
2020-02-17 14:06:14 +08:00
selectedHitObjects . CollectionChanged + = ( selectedObjects , args ) = >
2020-01-21 19:46:39 +08:00
{
2020-02-17 14:06:14 +08:00
switch ( args . Action )
{
case NotifyCollectionChangedAction . Add :
foreach ( var o in args . NewItems )
SelectionBlueprints . FirstOrDefault ( b = > b . HitObject = = o ) ? . Select ( ) ;
break ;
case NotifyCollectionChangedAction . Remove :
foreach ( var o in args . OldItems )
SelectionBlueprints . FirstOrDefault ( b = > b . HitObject = = o ) ? . Deselect ( ) ;
2020-06-23 19:36:09 +08:00
2020-02-17 14:06:14 +08:00
break ;
}
2020-01-21 19:46:39 +08:00
} ;
2019-08-29 15:06:40 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2020-11-12 18:52:02 +08:00
Beatmap . HitObjectAdded + = addBlueprintFor ;
2020-09-25 13:10:30 +08:00
Beatmap . HitObjectRemoved + = removeBlueprintFor ;
2020-11-13 16:08:20 +08:00
2020-11-13 16:10:29 +08:00
if ( Composer ! = null )
2020-11-13 16:08:20 +08:00
{
// For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above.
2020-11-13 16:10:29 +08:00
foreach ( var obj in Composer . HitObjects )
2020-11-13 16:08:20 +08:00
addBlueprintFor ( obj . HitObject ) ;
2020-11-13 16:10:29 +08:00
Composer . Playfield . HitObjectUsageBegan + = addBlueprintFor ;
Composer . Playfield . HitObjectUsageFinished + = removeBlueprintFor ;
2020-11-13 16:08:20 +08:00
}
2018-04-13 17:19:50 +08:00
}
2020-11-18 12:33:22 +08:00
protected virtual Container < SelectionBlueprint > CreateSelectionBlueprintContainer ( ) = > new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes . Both } ;
2020-01-24 16:50:36 +08:00
2020-01-16 10:54:03 +08:00
/// <summary>
2020-09-25 13:19:35 +08:00
/// Creates a <see cref="Components.SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
2020-01-16 10:54:03 +08:00
/// </summary>
protected virtual SelectionHandler CreateSelectionHandler ( ) = > new SelectionHandler ( ) ;
/// <summary>
/// Creates a <see cref="SelectionBlueprint"/> for a specific <see cref="DrawableHitObject"/>.
/// </summary>
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to create the overlay for.</param>
protected virtual SelectionBlueprint CreateBlueprintFor ( HitObject hitObject ) = > null ;
protected virtual DragBox CreateDragBox ( Action < RectangleF > performSelect ) = > new DragBox ( performSelect ) ;
2019-10-24 13:58:02 +08:00
protected override bool OnMouseDown ( MouseDownEvent e )
{
2020-11-04 16:53:03 +08:00
if ( ! beginClickSelection ( e ) ) return true ;
2020-11-03 19:45:48 +08:00
2020-01-22 20:43:02 +08:00
prepareSelectionMovement ( ) ;
2019-11-08 12:40:47 +08:00
return e . Button = = MouseButton . Left ;
2019-10-24 13:58:02 +08:00
}
2020-07-17 16:03:57 +08:00
private SelectionBlueprint clickedBlueprint ;
2018-11-06 17:02:55 +08:00
protected override bool OnClick ( ClickEvent e )
{
2019-11-07 21:51:49 +08:00
if ( e . Button = = MouseButton . Right )
return false ;
2020-07-17 16:03:57 +08:00
// store for double-click handling
2020-09-25 13:19:35 +08:00
clickedBlueprint = SelectionHandler . SelectedBlueprints . FirstOrDefault ( b = > b . IsHovered ) ;
2020-07-17 16:03:57 +08:00
2019-10-24 15:14:29 +08:00
// Deselection should only occur if no selected blueprints are hovered
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
2020-07-17 16:03:57 +08:00
if ( endClickSelection ( ) | | clickedBlueprint ! = null )
2019-10-24 13:58:02 +08:00
return true ;
2018-11-06 17:02:55 +08:00
deselectAll ( ) ;
return true ;
}
2019-11-06 17:15:57 +08:00
protected override bool OnDoubleClick ( DoubleClickEvent e )
{
2019-11-07 21:51:49 +08:00
if ( e . Button = = MouseButton . Right )
return false ;
2020-07-17 16:03:57 +08:00
// ensure the blueprint which was hovered for the first click is still the hovered blueprint.
2020-09-25 13:19:35 +08:00
if ( clickedBlueprint = = null | | SelectionHandler . SelectedBlueprints . FirstOrDefault ( b = > b . IsHovered ) ! = clickedBlueprint )
2019-11-06 17:15:57 +08:00
return false ;
2021-01-15 15:14:21 +08:00
EditorClock ? . SeekSmoothlyTo ( clickedBlueprint . HitObject . StartTime ) ;
2019-11-06 17:15:57 +08:00
return true ;
}
2020-01-20 17:17:21 +08:00
protected override void OnMouseUp ( MouseUpEvent e )
2019-10-24 14:58:22 +08:00
{
2019-10-24 15:14:29 +08:00
// Special case for when a drag happened instead of a click
Schedule ( ( ) = > endClickSelection ( ) ) ;
2020-01-22 20:43:02 +08:00
finishSelectionMovement ( ) ;
2019-10-24 14:58:22 +08:00
}
2019-10-24 14:11:54 +08:00
protected override bool OnDragStart ( DragStartEvent e )
{
2019-11-07 21:51:49 +08:00
if ( e . Button = = MouseButton . Right )
return false ;
2020-11-25 16:25:54 +08:00
if ( movementBlueprints ! = null )
2020-04-09 21:00:56 +08:00
{
isDraggingBlueprint = true ;
changeHandler ? . BeginChange ( ) ;
2020-01-22 16:54:11 +08:00
return true ;
2020-04-09 21:00:56 +08:00
}
2020-01-15 18:09:49 +08:00
2020-01-22 16:54:11 +08:00
if ( DragBox . HandleDrag ( e ) )
{
DragBox . Show ( ) ;
return true ;
2019-10-24 16:22:14 +08:00
}
2019-10-24 14:11:54 +08:00
2020-01-22 16:54:11 +08:00
return false ;
2019-10-24 14:11:54 +08:00
}
2020-01-20 17:17:21 +08:00
protected override void OnDrag ( DragEvent e )
2019-10-24 14:11:54 +08:00
{
2019-11-07 21:51:49 +08:00
if ( e . Button = = MouseButton . Right )
2020-01-20 17:17:21 +08:00
return ;
2019-11-07 21:51:49 +08:00
2020-01-22 16:54:11 +08:00
if ( DragBox . State = = Visibility . Visible )
2020-01-23 14:37:54 +08:00
DragBox . HandleDrag ( e ) ;
2019-10-24 14:11:54 +08:00
2020-01-23 14:37:54 +08:00
moveCurrentSelection ( e ) ;
2019-10-24 14:11:54 +08:00
}
2020-01-20 17:17:21 +08:00
protected override void OnDragEnd ( DragEndEvent e )
2019-10-24 14:11:54 +08:00
{
2019-11-07 21:51:49 +08:00
if ( e . Button = = MouseButton . Right )
2020-01-20 17:17:21 +08:00
return ;
2019-11-07 21:51:49 +08:00
2020-04-09 21:00:56 +08:00
if ( isDraggingBlueprint )
{
2020-09-28 13:37:55 +08:00
// handle positional change etc.
foreach ( var obj in selectedHitObjects )
2020-10-08 17:06:46 +08:00
Beatmap . Update ( obj ) ;
2020-09-28 13:37:55 +08:00
2020-04-09 21:00:56 +08:00
changeHandler ? . EndChange ( ) ;
isDraggingBlueprint = false ;
}
2020-01-22 16:54:11 +08:00
if ( DragBox . State = = Visibility . Visible )
DragBox . Hide ( ) ;
2019-10-24 14:11:54 +08:00
}
2019-11-11 12:41:10 +08:00
protected override bool OnKeyDown ( KeyDownEvent e )
{
switch ( e . Key )
{
case Key . Escape :
2020-09-25 13:19:35 +08:00
if ( ! SelectionHandler . SelectedBlueprints . Any ( ) )
2019-11-11 12:41:10 +08:00
return false ;
deselectAll ( ) ;
return true ;
}
return false ;
}
public bool OnPressed ( PlatformAction action )
{
switch ( action . ActionType )
{
case PlatformActionType . SelectAll :
selectAll ( ) ;
return true ;
}
return false ;
}
2020-01-22 12:22:34 +08:00
public void OnReleased ( PlatformAction action )
{
}
2019-11-11 12:41:10 +08:00
2019-10-24 15:17:48 +08:00
#region Blueprint Addition / Removal
2020-11-12 18:52:02 +08:00
private void addBlueprintFor ( HitObject hitObject )
2018-10-18 15:36:06 +08:00
{
2020-11-12 18:52:02 +08:00
if ( blueprintMap . ContainsKey ( hitObject ) )
return ;
var blueprint = CreateBlueprintFor ( hitObject ) ;
2018-11-06 17:02:55 +08:00
if ( blueprint = = null )
2018-10-18 15:36:06 +08:00
return ;
2020-11-12 18:52:02 +08:00
blueprintMap [ hitObject ] = blueprint ;
blueprint . Selected + = onBlueprintSelected ;
blueprint . Deselected + = onBlueprintDeselected ;
if ( Beatmap . SelectedHitObjects . Contains ( hitObject ) )
blueprint . Select ( ) ;
SelectionBlueprints . Add ( blueprint ) ;
OnBlueprintAdded ( hitObject ) ;
}
private void removeBlueprintFor ( HitObject hitObject )
{
if ( ! blueprintMap . Remove ( hitObject , out var blueprint ) )
return ;
2018-11-06 16:51:26 +08:00
2020-11-12 18:52:02 +08:00
blueprint . Deselect ( ) ;
2018-11-06 17:02:55 +08:00
blueprint . Selected - = onBlueprintSelected ;
blueprint . Deselected - = onBlueprintDeselected ;
2018-11-06 16:51:26 +08:00
2020-02-07 17:04:10 +08:00
SelectionBlueprints . Remove ( blueprint ) ;
2020-06-23 19:36:09 +08:00
2020-11-25 16:25:54 +08:00
if ( movementBlueprints ? . Contains ( blueprint ) = = true )
2020-06-23 19:36:09 +08:00
finishSelectionMovement ( ) ;
2020-11-12 18:52:02 +08:00
OnBlueprintRemoved ( hitObject ) ;
2018-11-06 16:51:26 +08:00
}
2020-11-12 18:52:02 +08:00
/// <summary>
/// Called after a <see cref="HitObject"/> blueprint has been added.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> for which the blueprint has been added.</param>
protected virtual void OnBlueprintAdded ( HitObject hitObject )
2019-08-29 15:06:40 +08:00
{
2020-11-12 18:52:02 +08:00
}
2020-09-12 19:31:50 +08:00
2020-11-12 18:52:02 +08:00
/// <summary>
/// Called after a <see cref="HitObject"/> blueprint has been removed.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> for which the blueprint has been removed.</param>
protected virtual void OnBlueprintRemoved ( HitObject hitObject )
{
2019-08-29 15:06:40 +08:00
}
2019-10-24 15:17:48 +08:00
#endregion
2019-08-29 15:06:40 +08:00
2019-10-24 14:58:22 +08:00
#region Selection
/// <summary>
/// Whether a blueprint was selected by a previous click event.
/// </summary>
private bool clickSelectionBegan ;
/// <summary>
/// Attempts to select any hovered blueprints.
/// </summary>
/// <param name="e">The input event that triggered this selection.</param>
2020-11-04 16:53:03 +08:00
/// <returns>Whether a selection was performed.</returns>
2020-11-03 19:45:48 +08:00
private bool beginClickSelection ( MouseButtonEvent e )
2019-10-24 14:58:22 +08:00
{
2020-11-18 12:37:15 +08:00
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
2020-11-17 21:56:21 +08:00
foreach ( SelectionBlueprint blueprint in SelectionBlueprints . AliveChildren . Reverse ( ) )
2019-10-24 14:58:22 +08:00
{
2020-11-10 16:16:28 +08:00
if ( ! blueprint . IsHovered ) continue ;
2020-11-16 04:06:47 +08:00
return clickSelectionBegan = SelectionHandler . HandleSelectionRequested ( blueprint , e ) ;
2019-10-24 14:58:22 +08:00
}
2020-11-03 19:45:48 +08:00
2020-11-16 04:06:47 +08:00
return false ;
2019-10-24 14:58:22 +08:00
}
/// <summary>
/// Finishes the current blueprint selection.
/// </summary>
2019-10-24 15:14:29 +08:00
/// <returns>Whether a click selection was active.</returns>
private bool endClickSelection ( )
2019-10-24 14:58:22 +08:00
{
2019-10-24 15:14:29 +08:00
if ( ! clickSelectionBegan )
return false ;
2019-10-24 14:58:22 +08:00
clickSelectionBegan = false ;
2019-10-24 15:14:29 +08:00
return true ;
2019-10-24 14:58:22 +08:00
}
2018-11-06 16:51:26 +08:00
/// <summary>
/// Select all masks in a given rectangle selection area.
/// </summary>
/// <param name="rect">The rectangle to perform a selection on in screen-space coordinates.</param>
2020-09-08 16:22:59 +08:00
private void selectBlueprintsFromDragRectangle ( RectangleF rect )
2018-11-06 16:51:26 +08:00
{
2020-02-07 17:04:10 +08:00
foreach ( var blueprint in SelectionBlueprints )
2018-11-06 16:51:26 +08:00
{
2020-06-23 17:42:56 +08:00
// only run when utmost necessary to avoid unnecessary rect computations.
bool isValidForSelection ( ) = > blueprint . IsAlive & & blueprint . IsPresent & & rect . Contains ( blueprint . ScreenSpaceSelectionPoint ) ;
switch ( blueprint . State )
{
case SelectionState . NotSelected :
if ( isValidForSelection ( ) )
blueprint . Select ( ) ;
break ;
case SelectionState . Selected :
// if the editor is playing, we generally don't want to deselect objects even if outside the selection area.
2020-11-26 18:20:39 +08:00
if ( ! EditorClock . IsRunning & & ! isValidForSelection ( ) )
2020-06-23 17:42:56 +08:00
blueprint . Deselect ( ) ;
break ;
}
2018-11-06 16:51:26 +08:00
}
}
2019-11-11 12:41:10 +08:00
/// <summary>
/// Selects all <see cref="SelectionBlueprint"/>s.
/// </summary>
2020-11-13 16:08:20 +08:00
private void selectAll ( )
{
2020-11-13 16:10:29 +08:00
Composer . Playfield . KeepAllAlive ( ) ;
2020-11-13 16:08:20 +08:00
// Scheduled to allow the change in lifetime to take place.
Schedule ( ( ) = > SelectionBlueprints . ToList ( ) . ForEach ( m = > m . Select ( ) ) ) ;
}
2019-11-11 12:41:10 +08:00
2018-11-06 16:51:26 +08:00
/// <summary>
2018-11-06 16:56:04 +08:00
/// Deselects all selected <see cref="SelectionBlueprint"/>s.
2018-11-06 16:51:26 +08:00
/// </summary>
2020-09-25 13:19:35 +08:00
private void deselectAll ( ) = > SelectionHandler . SelectedBlueprints . ToList ( ) . ForEach ( m = > m . Deselect ( ) ) ;
2018-11-06 16:51:26 +08:00
2018-11-06 16:56:04 +08:00
private void onBlueprintSelected ( SelectionBlueprint blueprint )
2018-11-06 16:51:26 +08:00
{
2020-09-25 13:19:35 +08:00
SelectionHandler . HandleSelected ( blueprint ) ;
2020-02-07 17:04:10 +08:00
SelectionBlueprints . ChangeChildDepth ( blueprint , 1 ) ;
2020-11-13 16:08:20 +08:00
2020-11-13 16:10:29 +08:00
Composer . Playfield . SetKeepAlive ( blueprint . HitObject , true ) ;
2018-11-06 16:51:26 +08:00
}
2018-11-06 16:56:04 +08:00
private void onBlueprintDeselected ( SelectionBlueprint blueprint )
2018-11-06 16:51:26 +08:00
{
2020-09-25 13:19:35 +08:00
SelectionHandler . HandleDeselected ( blueprint ) ;
2020-02-07 17:04:10 +08:00
SelectionBlueprints . ChangeChildDepth ( blueprint , 0 ) ;
2020-11-13 16:08:20 +08:00
2020-11-13 16:10:29 +08:00
Composer . Playfield . SetKeepAlive ( blueprint . HitObject , false ) ;
2018-11-06 16:51:26 +08:00
}
2019-10-24 14:58:22 +08:00
#endregion
#region Selection Movement
2020-11-24 16:14:39 +08:00
private Vector2 [ ] movementBlueprintOriginalPositions ;
2020-11-25 16:25:54 +08:00
private SelectionBlueprint [ ] movementBlueprints ;
2020-04-09 21:00:56 +08:00
private bool isDraggingBlueprint ;
2019-10-23 17:58:15 +08:00
2019-10-24 14:11:54 +08:00
/// <summary>
/// Attempts to begin the movement of any selected blueprints.
/// </summary>
2020-01-24 16:30:37 +08:00
private void prepareSelectionMovement ( )
2019-10-23 17:37:57 +08:00
{
2020-09-25 13:19:35 +08:00
if ( ! SelectionHandler . SelectedBlueprints . Any ( ) )
2020-01-24 16:30:37 +08:00
return ;
2019-10-24 14:11:54 +08:00
2019-10-24 15:14:29 +08:00
// Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement
// A special case is added for when a click selection occurred before the drag
2020-09-25 13:19:35 +08:00
if ( ! clickSelectionBegan & & ! SelectionHandler . SelectedBlueprints . Any ( b = > b . IsHovered ) )
2020-01-24 16:30:37 +08:00
return ;
2019-10-24 14:11:54 +08:00
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
2020-11-25 16:25:54 +08:00
movementBlueprints = SelectionHandler . SelectedBlueprints . OrderBy ( b = > b . HitObject . StartTime ) . ToArray ( ) ;
movementBlueprintOriginalPositions = movementBlueprints . Select ( m = > m . ScreenSpaceSelectionPoint ) . ToArray ( ) ;
2019-10-23 17:37:57 +08:00
}
2018-11-06 16:51:26 +08:00
2019-10-24 14:11:54 +08:00
/// <summary>
/// Moves the current selected blueprints.
/// </summary>
/// <param name="e">The <see cref="DragEvent"/> defining the movement event.</param>
/// <returns>Whether a movement was active.</returns>
private bool moveCurrentSelection ( DragEvent e )
2019-10-08 17:57:03 +08:00
{
2020-11-25 16:25:54 +08:00
if ( movementBlueprints = = null )
2019-10-24 14:11:54 +08:00
return false ;
2019-10-23 17:37:57 +08:00
2020-11-14 21:48:48 +08:00
if ( snapProvider = = null )
return true ;
2020-11-24 16:14:39 +08:00
Debug . Assert ( movementBlueprintOriginalPositions ! = null ) ;
2019-10-23 17:58:15 +08:00
2020-11-24 16:14:39 +08:00
Vector2 distanceTravelled = e . ScreenSpaceMousePosition - e . ScreenSpaceMouseDownPosition ;
// check for positional snap for every object in selection (for things like object-object snapping)
for ( var i = 0 ; i < movementBlueprintOriginalPositions . Length ; i + + )
{
var testPosition = movementBlueprintOriginalPositions [ i ] + distanceTravelled ;
var positionalResult = snapProvider . SnapScreenSpacePositionToValidPosition ( testPosition ) ;
if ( positionalResult . ScreenSpacePosition = = testPosition ) continue ;
// attempt to move the objects, and abort any time based snapping if we can.
2020-11-25 16:25:54 +08:00
if ( SelectionHandler . HandleMovement ( new MoveSelectionEvent ( movementBlueprints [ i ] , positionalResult . ScreenSpacePosition ) ) )
2020-11-24 16:14:39 +08:00
return true ;
}
// if no positional snapping could be performed, try unrestricted snapping from the earliest
// hitobject in the selection.
2019-10-25 11:34:49 +08:00
2020-04-27 19:35:24 +08:00
// The final movement position, relative to movementBlueprintOriginalPosition.
2020-11-24 16:14:39 +08:00
Vector2 movePosition = movementBlueprintOriginalPositions . First ( ) + distanceTravelled ;
2020-01-22 20:43:02 +08:00
2020-04-27 19:35:24 +08:00
// Retrieve a snapped position.
2020-05-20 17:19:21 +08:00
var result = snapProvider . SnapScreenSpacePositionToValidTime ( movePosition ) ;
2019-10-16 19:34:02 +08:00
2020-04-27 19:35:24 +08:00
// Move the hitobjects.
2020-11-25 16:25:54 +08:00
if ( ! SelectionHandler . HandleMovement ( new MoveSelectionEvent ( movementBlueprints . First ( ) , result . ScreenSpacePosition ) ) )
2019-11-06 16:27:41 +08:00
return true ;
2019-10-16 19:34:16 +08:00
2020-05-20 17:19:21 +08:00
if ( result . Time . HasValue )
{
// Apply the start time at the newly snapped-to position
2020-11-25 16:25:54 +08:00
double offset = result . Time . Value - movementBlueprints . First ( ) . HitObject . StartTime ;
2020-10-08 15:56:55 +08:00
2020-10-09 17:50:05 +08:00
foreach ( HitObject obj in Beatmap . SelectedHitObjects )
2020-05-20 17:19:21 +08:00
obj . StartTime + = offset ;
}
2019-10-23 17:37:57 +08:00
return true ;
2019-10-08 17:57:03 +08:00
}
2018-11-06 16:51:26 +08:00
2019-10-24 14:11:54 +08:00
/// <summary>
/// Finishes the current movement of selected blueprints.
/// </summary>
/// <returns>Whether a movement was active.</returns>
private bool finishSelectionMovement ( )
2019-10-08 17:57:03 +08:00
{
2020-11-25 16:25:54 +08:00
if ( movementBlueprints = = null )
2019-10-24 14:11:54 +08:00
return false ;
2020-11-24 16:14:39 +08:00
movementBlueprintOriginalPositions = null ;
2020-11-25 16:25:54 +08:00
movementBlueprints = null ;
2019-10-16 19:34:16 +08:00
2019-10-23 17:58:15 +08:00
return true ;
2019-10-08 17:57:03 +08:00
}
2018-11-06 16:51:26 +08:00
2019-10-24 14:58:22 +08:00
#endregion
2019-08-29 15:06:40 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2020-09-25 13:10:30 +08:00
if ( Beatmap ! = null )
2019-08-29 15:06:40 +08:00
{
2020-11-12 18:52:02 +08:00
Beatmap . HitObjectAdded - = addBlueprintFor ;
2020-09-25 13:10:30 +08:00
Beatmap . HitObjectRemoved - = removeBlueprintFor ;
2019-08-29 15:06:40 +08:00
}
2020-11-13 16:08:20 +08:00
2020-11-13 16:10:29 +08:00
if ( Composer ! = null )
2020-11-13 16:08:20 +08:00
{
2020-11-13 16:10:29 +08:00
Composer . Playfield . HitObjectUsageBegan - = addBlueprintFor ;
Composer . Playfield . HitObjectUsageFinished - = removeBlueprintFor ;
2020-11-13 16:08:20 +08:00
}
2019-08-29 15:06:40 +08:00
}
2018-04-13 17:19:50 +08:00
}
}