2019-01-24 17:43:03 +09: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 18:19:50 +09:00
2022-06-17 16:37:17 +09:00
#nullable disable
2020-11-12 19:52:02 +09:00
using System.Collections.Generic ;
2021-05-11 17:49:00 +09:00
using System.Collections.Specialized ;
2019-10-23 18:58:15 +09:00
using System.Diagnostics ;
2018-10-18 16:36:06 +09:00
using System.Linq ;
2021-06-23 09:40:07 +09:00
using JetBrains.Annotations ;
2018-03-29 17:13:45 +09:00
using osu.Framework.Allocation ;
2021-05-11 17:49:00 +09:00
using osu.Framework.Bindables ;
2018-02-20 18:01:45 +09:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2019-10-03 16:14:42 +09:00
using osu.Framework.Input ;
2019-11-11 13:41:10 +09:00
using osu.Framework.Input.Bindings ;
2018-10-02 12:02:47 +09:00
using osu.Framework.Input.Events ;
2022-10-11 14:11:45 +09:00
using osu.Game.Graphics.UserInterface ;
2023-05-24 19:24:14 +09:00
using osu.Game.Input.Bindings ;
2018-03-14 15:18:21 +09:00
using osu.Game.Rulesets.Edit ;
2019-10-16 20:34:16 +09:00
using osuTK ;
2019-11-07 22:51:49 +09:00
using osuTK.Input ;
2018-04-13 18:19:50 +09:00
2018-11-06 18:28:22 +09:00
namespace osu.Game.Screens.Edit.Compose.Components
2018-02-20 18:01:45 +09:00
{
2020-01-02 11:46:18 +09:00
/// <summary>
2021-04-27 18:33:47 +09:00
/// A container which provides a "blueprint" display of items.
2021-04-27 15:40:35 +09:00
/// Includes selection and manipulation support via a <see cref="Components.SelectionHandler{T}"/>.
2020-01-02 11:46:18 +09:00
/// </summary>
2023-05-24 19:24:14 +09:00
public abstract partial class BlueprintContainer < T > : CompositeDrawable , IKeyBindingHandler < PlatformAction > , IKeyBindingHandler < GlobalAction >
2021-05-11 17:49:00 +09:00
where T : class
2018-02-20 18:01:45 +09:00
{
2020-01-16 11:54:03 +09:00
protected DragBox DragBox { get ; private set ; }
2020-01-15 19:09:49 +09:00
2021-04-27 15:40:35 +09:00
public Container < SelectionBlueprint < T > > SelectionBlueprints { get ; private set ; }
2020-01-15 19:09:49 +09:00
2023-07-09 21:00:35 +02:00
public SelectionHandler < T > SelectionHandler { get ; private set ; }
2018-11-06 17:51:26 +09:00
2021-04-27 15:40:35 +09:00
private readonly Dictionary < T , SelectionBlueprint < T > > blueprintMap = new Dictionary < T , SelectionBlueprint < T > > ( ) ;
2020-01-21 20:46:39 +09:00
2020-01-15 19:09:49 +09:00
[Resolved(canBeNull: true)]
2020-05-20 17:48:43 +09:00
private IPositionSnapProvider snapProvider { get ; set ; }
2020-01-15 19:09:49 +09:00
2021-04-27 15:40:35 +09:00
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get ; set ; }
2020-11-12 19:52:02 +09:00
2021-05-11 17:49:00 +09:00
protected readonly BindableList < T > SelectedItems = new BindableList < T > ( ) ;
2021-04-27 15:40:35 +09:00
protected BlueprintContainer ( )
{
2018-02-20 18:01:45 +09:00
RelativeSizeAxes = Axes . Both ;
2018-03-29 22:27:14 +09:00
}
2018-04-13 18:19:50 +09:00
2018-03-29 22:27:14 +09:00
[BackgroundDependencyLoader]
2018-10-17 17:41:17 +09:00
private void load ( )
2018-03-29 22:27:14 +09:00
{
2022-06-24 21:25:23 +09:00
SelectedItems . CollectionChanged + = ( _ , args ) = >
2021-05-11 17:49:00 +09:00
{
switch ( args . Action )
{
case NotifyCollectionChangedAction . Add :
2022-12-16 18:16:26 +09:00
Debug . Assert ( args . NewItems ! = null ) ;
2021-10-27 13:04:41 +09:00
foreach ( object o in args . NewItems )
2022-10-05 21:26:00 +09:00
{
if ( blueprintMap . TryGetValue ( ( T ) o , out var blueprint ) )
blueprint . Select ( ) ;
}
2021-05-11 17:49:00 +09:00
break ;
case NotifyCollectionChangedAction . Remove :
2022-12-16 18:16:26 +09:00
Debug . Assert ( args . OldItems ! = null ) ;
2021-10-27 13:04:41 +09:00
foreach ( object o in args . OldItems )
2022-10-05 21:26:00 +09:00
{
if ( blueprintMap . TryGetValue ( ( T ) o , out var blueprint ) )
blueprint . Deselect ( ) ;
}
2021-05-11 17:49:00 +09:00
break ;
}
} ;
2020-09-25 14:19:35 +09:00
SelectionHandler = CreateSelectionHandler ( ) ;
2022-03-10 20:22:36 +09:00
SelectionHandler . SelectedItems . BindTo ( SelectedItems ) ;
2018-04-13 18:19:50 +09:00
2020-01-15 19:09:49 +09:00
AddRangeInternal ( new [ ]
2018-03-29 22:06:45 +09:00
{
2022-10-05 20:23:59 +09:00
DragBox = CreateDragBox ( ) ,
2020-09-25 14:19:35 +09:00
SelectionHandler ,
2020-02-07 18:04:10 +09:00
SelectionBlueprints = CreateSelectionBlueprintContainer ( ) ,
2020-09-25 14:19:35 +09:00
SelectionHandler . CreateProxy ( ) ,
2020-01-16 11:54:03 +09:00
DragBox . CreateProxy ( ) . With ( p = > p . Depth = float . MinValue )
2020-01-15 19:09:49 +09:00
} ) ;
2019-08-29 16:06:40 +09:00
}
2021-04-27 15:40:35 +09:00
protected virtual Container < SelectionBlueprint < T > > CreateSelectionBlueprintContainer ( ) = > new Container < SelectionBlueprint < T > > { RelativeSizeAxes = Axes . Both } ;
2020-01-24 17:50:36 +09:00
2020-01-16 11:54:03 +09:00
/// <summary>
2021-04-27 18:33:47 +09:00
/// Creates a <see cref="Components.SelectionHandler{T}"/> which outlines items and handles movement of selections.
2020-01-16 11:54:03 +09:00
/// </summary>
2021-04-27 18:06:27 +09:00
protected abstract SelectionHandler < T > CreateSelectionHandler ( ) ;
2020-01-16 11:54:03 +09:00
/// <summary>
2021-04-27 18:33:47 +09:00
/// Creates a <see cref="SelectionBlueprint{T}"/> for a specific item.
2020-01-16 11:54:03 +09:00
/// </summary>
2021-04-27 18:33:47 +09:00
/// <param name="item">The item to create the overlay for.</param>
2021-06-23 09:40:07 +09:00
[CanBeNull]
2021-04-27 18:33:47 +09:00
protected virtual SelectionBlueprint < T > CreateBlueprintFor ( T item ) = > null ;
2020-01-16 11:54:03 +09:00
2022-10-05 20:23:59 +09:00
protected virtual DragBox CreateDragBox ( ) = > new DragBox ( ) ;
2020-01-16 11:54:03 +09:00
2019-10-24 14:58:02 +09:00
protected override bool OnMouseDown ( MouseDownEvent e )
{
2021-04-13 13:03:14 +09:00
bool selectionPerformed = performMouseDownActions ( e ) ;
2021-07-18 16:04:23 +02:00
bool movementPossible = prepareSelectionMovement ( ) ;
2020-01-22 21:43:02 +09:00
2021-10-26 14:52:15 -04:00
// check if selection has occurred
if ( selectionPerformed )
{
2021-10-26 16:24:53 -04:00
// only unmodified right click should show context menu
2021-11-04 16:02:37 +09:00
bool shouldShowContextMenu = e . Button = = MouseButton . Right & & ! e . ShiftPressed & & ! e . AltPressed & & ! e . SuperPressed ;
2021-10-26 16:24:53 -04:00
// stop propagation if not showing context menu
return ! shouldShowContextMenu ;
2021-10-26 14:52:15 -04:00
}
// even if a selection didn't occur, a drag event may still move the selection.
return e . Button = = MouseButton . Left & & movementPossible ;
2019-10-24 14:58:02 +09:00
}
2021-04-27 15:40:35 +09:00
protected SelectionBlueprint < T > ClickedBlueprint { get ; private set ; }
2020-07-17 17:03:57 +09:00
2018-11-06 18:02:55 +09:00
protected override bool OnClick ( ClickEvent e )
{
2019-11-07 22:51:49 +09:00
if ( e . Button = = MouseButton . Right )
return false ;
2020-07-17 17:03:57 +09:00
// store for double-click handling
2021-04-27 15:40:35 +09:00
ClickedBlueprint = SelectionHandler . SelectedBlueprints . FirstOrDefault ( b = > b . IsHovered ) ;
2020-07-17 17:03:57 +09:00
2019-10-24 16:14:29 +09:00
// Deselection should only occur if no selected blueprints are hovered
2021-04-27 18:33:47 +09:00
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the item and should not trigger deselection
2021-04-27 15:40:35 +09:00
if ( endClickSelection ( e ) | | ClickedBlueprint ! = null )
2019-10-24 14:58:02 +09:00
return true ;
2022-10-06 13:50:56 +09:00
DeselectAll ( ) ;
2018-11-06 18:02:55 +09:00
return true ;
}
2019-11-06 18:15:57 +09:00
protected override bool OnDoubleClick ( DoubleClickEvent e )
{
2019-11-07 22:51:49 +09:00
if ( e . Button = = MouseButton . Right )
return false ;
2020-07-17 17:03:57 +09:00
// ensure the blueprint which was hovered for the first click is still the hovered blueprint.
2021-04-27 15:40:35 +09:00
if ( ClickedBlueprint = = null | | SelectionHandler . SelectedBlueprints . FirstOrDefault ( b = > b . IsHovered ) ! = ClickedBlueprint )
2019-11-06 18:15:57 +09:00
return false ;
2023-07-19 15:35:40 +09:00
doubleClickHandled = true ;
2019-11-06 18:15:57 +09:00
return true ;
}
2020-01-20 18:17:21 +09:00
protected override void OnMouseUp ( MouseUpEvent e )
2019-10-24 15:58:22 +09:00
{
2019-10-24 16:14:29 +09:00
// Special case for when a drag happened instead of a click
2021-04-12 19:05:23 +09:00
Schedule ( ( ) = >
{
endClickSelection ( e ) ;
2023-03-07 14:22:12 +09:00
clickSelectionHandled = false ;
2023-07-19 15:35:40 +09:00
doubleClickHandled = false ;
2021-04-12 19:05:23 +09:00
isDraggingBlueprint = false ;
2023-03-15 15:16:48 +09:00
wasDragStarted = false ;
2021-04-12 19:05:23 +09:00
} ) ;
2020-01-22 21:43:02 +09:00
finishSelectionMovement ( ) ;
2019-10-24 15:58:22 +09:00
}
2022-10-11 22:39:53 +09:00
private MouseButtonEvent lastDragEvent ;
2019-10-24 15:11:54 +09:00
protected override bool OnDragStart ( DragStartEvent e )
{
2019-11-07 22:51:49 +09:00
if ( e . Button = = MouseButton . Right )
return false ;
2022-10-11 22:39:53 +09:00
lastDragEvent = e ;
2023-03-15 15:16:48 +09:00
wasDragStarted = true ;
2022-10-11 22:39:53 +09:00
2020-11-25 17:25:54 +09:00
if ( movementBlueprints ! = null )
2020-04-09 22:00:56 +09:00
{
isDraggingBlueprint = true ;
changeHandler ? . BeginChange ( ) ;
2020-01-22 17:54:11 +09:00
return true ;
2020-04-09 22:00:56 +09:00
}
2020-01-15 19:09:49 +09:00
2022-10-05 20:23:59 +09:00
DragBox . HandleDrag ( e ) ;
DragBox . Show ( ) ;
return true ;
2019-10-24 15:11:54 +09:00
}
2020-01-20 18:17:21 +09:00
protected override void OnDrag ( DragEvent e )
2019-10-24 15:11:54 +09:00
{
2022-10-11 22:39:53 +09:00
lastDragEvent = e ;
2019-10-24 15:11:54 +09:00
2020-01-23 15:37:54 +09:00
moveCurrentSelection ( e ) ;
2019-10-24 15:11:54 +09:00
}
2020-01-20 18:17:21 +09:00
protected override void OnDragEnd ( DragEndEvent e )
2019-10-24 15:11:54 +09:00
{
2022-10-11 22:39:53 +09:00
lastDragEvent = null ;
2019-11-07 22:51:49 +09:00
2020-04-09 22:00:56 +09:00
if ( isDraggingBlueprint )
{
2021-04-27 18:57:51 +09:00
DragOperationCompleted ( ) ;
2020-04-09 22:00:56 +09:00
changeHandler ? . EndChange ( ) ;
}
2022-10-05 20:23:59 +09:00
DragBox . Hide ( ) ;
2019-10-24 15:11:54 +09:00
}
2022-10-11 22:39:53 +09:00
protected override void Update ( )
{
base . Update ( ) ;
if ( lastDragEvent ! = null & & DragBox . State = = Visibility . Visible )
{
lastDragEvent . Target = this ;
DragBox . HandleDrag ( lastDragEvent ) ;
UpdateSelectionFromDragBox ( ) ;
}
}
2021-04-27 18:57:51 +09:00
/// <summary>
/// Called whenever a drag operation completes, before any change transaction is committed.
/// </summary>
protected virtual void DragOperationCompleted ( )
2021-04-27 15:40:35 +09:00
{
}
2019-11-11 13:41:10 +09:00
protected override bool OnKeyDown ( KeyDownEvent e )
{
switch ( e . Key )
{
case Key . Escape :
2020-09-25 14:19:35 +09:00
if ( ! SelectionHandler . SelectedBlueprints . Any ( ) )
2019-11-11 13:41:10 +09:00
return false ;
2022-10-06 13:50:56 +09:00
DeselectAll ( ) ;
2019-11-11 13:41:10 +09:00
return true ;
}
return false ;
}
2021-09-16 18:26:12 +09:00
public bool OnPressed ( KeyBindingPressEvent < PlatformAction > e )
2019-11-11 13:41:10 +09:00
{
2021-11-18 12:36:15 +09:00
if ( e . Repeat )
return false ;
2021-09-16 18:26:12 +09:00
switch ( e . Action )
2019-11-11 13:41:10 +09:00
{
2021-07-20 14:23:34 +09:00
case PlatformAction . SelectAll :
2021-04-27 15:40:35 +09:00
SelectAll ( ) ;
2019-11-11 13:41:10 +09:00
return true ;
}
return false ;
}
2021-09-16 18:26:12 +09:00
public void OnReleased ( KeyBindingReleaseEvent < PlatformAction > e )
2020-01-22 13:22:34 +09:00
{
}
2019-11-11 13:41:10 +09:00
2023-05-24 19:24:14 +09:00
public bool OnPressed ( KeyBindingPressEvent < GlobalAction > e )
{
if ( e . Repeat )
return false ;
switch ( e . Action )
{
case GlobalAction . Back :
if ( SelectedItems . Count > 0 )
{
DeselectAll ( ) ;
return true ;
}
break ;
}
return false ;
}
public void OnReleased ( KeyBindingReleaseEvent < GlobalAction > e )
{
}
2019-10-24 16:17:48 +09:00
#region Blueprint Addition / Removal
2021-04-27 15:40:35 +09:00
protected virtual void AddBlueprintFor ( T item )
2018-10-18 16:36:06 +09:00
{
2021-04-27 15:40:35 +09:00
if ( blueprintMap . ContainsKey ( item ) )
2020-11-12 19:52:02 +09:00
return ;
2021-04-27 15:40:35 +09:00
var blueprint = CreateBlueprintFor ( item ) ;
2018-11-06 18:02:55 +09:00
if ( blueprint = = null )
2018-10-18 16:36:06 +09:00
return ;
2021-04-27 15:40:35 +09:00
blueprintMap [ item ] = blueprint ;
2020-11-12 19:52:02 +09:00
2021-04-27 15:40:35 +09:00
blueprint . Selected + = OnBlueprintSelected ;
blueprint . Deselected + = OnBlueprintDeselected ;
2020-11-12 19:52:02 +09:00
SelectionBlueprints . Add ( blueprint ) ;
2021-04-27 18:33:47 +09:00
if ( SelectionHandler . SelectedItems . Contains ( item ) )
blueprint . Select ( ) ;
OnBlueprintAdded ( blueprint . Item ) ;
2020-11-12 19:52:02 +09:00
}
2021-04-27 15:40:35 +09:00
protected void RemoveBlueprintFor ( T item )
2020-11-12 19:52:02 +09:00
{
2021-04-27 15:40:35 +09:00
if ( ! blueprintMap . Remove ( item , out var blueprint ) )
2020-11-12 19:52:02 +09:00
return ;
2018-11-06 17:51:26 +09:00
2020-11-12 19:52:02 +09:00
blueprint . Deselect ( ) ;
2021-04-27 15:40:35 +09:00
blueprint . Selected - = OnBlueprintSelected ;
blueprint . Deselected - = OnBlueprintDeselected ;
2018-11-06 17:51:26 +09:00
2022-08-26 15:19:05 +09:00
SelectionBlueprints . Remove ( blueprint , true ) ;
2020-06-23 20:36:09 +09:00
2020-11-25 17:25:54 +09:00
if ( movementBlueprints ? . Contains ( blueprint ) = = true )
2020-06-23 20:36:09 +09:00
finishSelectionMovement ( ) ;
2020-11-12 19:52:02 +09:00
2021-04-27 18:33:47 +09:00
OnBlueprintRemoved ( blueprint . Item ) ;
2018-11-06 17:51:26 +09:00
}
2020-11-12 19:52:02 +09:00
/// <summary>
2021-04-27 18:33:47 +09:00
/// Called after an item's blueprint has been added.
2020-11-12 19:52:02 +09:00
/// </summary>
2021-04-27 18:33:47 +09:00
/// <param name="item">The item for which the blueprint has been added.</param>
protected virtual void OnBlueprintAdded ( T item )
2019-08-29 16:06:40 +09:00
{
2020-11-12 19:52:02 +09:00
}
2020-09-12 20:31:50 +09:00
2020-11-12 19:52:02 +09:00
/// <summary>
2021-04-27 18:33:47 +09:00
/// Called after an item's blueprint has been removed.
2020-11-12 19:52:02 +09:00
/// </summary>
2021-04-27 18:33:47 +09:00
/// <param name="item">The item for which the blueprint has been removed.</param>
protected virtual void OnBlueprintRemoved ( T item )
2020-11-12 19:52:02 +09:00
{
2019-08-29 16:06:40 +09:00
}
2021-05-13 21:16:19 +09:00
/// <summary>
/// Retrieves an item's blueprint.
/// </summary>
/// <param name="item">The item to retrieve the blueprint of.</param>
/// <returns>The blueprint.</returns>
protected SelectionBlueprint < T > GetBlueprintFor ( T item ) = > blueprintMap [ item ] ;
2019-10-24 16:17:48 +09:00
#endregion
2019-08-29 16:06:40 +09:00
2019-10-24 15:58:22 +09:00
#region Selection
/// <summary>
/// Whether a blueprint was selected by a previous click event.
/// </summary>
2023-03-07 14:22:12 +09:00
private bool clickSelectionHandled ;
2023-07-19 15:35:40 +09:00
/// <summary>
/// Whether a blueprint was double-clicked since last mouse down.
/// </summary>
private bool doubleClickHandled ;
2023-03-07 14:22:12 +09:00
/// <summary>
/// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case.
/// </summary>
private bool selectedBlueprintAlreadySelectedOnMouseDown ;
2019-10-24 15:58:22 +09:00
2023-07-20 19:13:37 +02:00
/// <summary>
/// Sorts the supplied <paramref name="blueprints"/> by the order of preference when making a selection.
/// Blueprints at the start of the list will be prioritised over later items if the selection requested is ambiguous due to spatial overlap.
/// </summary>
2023-07-19 15:55:38 +09:00
protected virtual IEnumerable < SelectionBlueprint < T > > ApplySelectionOrder ( IEnumerable < SelectionBlueprint < T > > blueprints ) = > blueprints . Reverse ( ) ;
2019-10-24 15:58:22 +09:00
/// <summary>
/// Attempts to select any hovered blueprints.
/// </summary>
/// <param name="e">The input event that triggered this selection.</param>
2020-11-04 17:53:03 +09:00
/// <returns>Whether a selection was performed.</returns>
2021-04-13 13:03:14 +09:00
private bool performMouseDownActions ( MouseButtonEvent e )
2019-10-24 15:58:22 +09:00
{
2020-11-18 13:37:15 +09:00
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
2021-03-19 22:20:40 +01:00
// Priority is given to already-selected blueprints.
2023-07-19 15:55:38 +09:00
foreach ( SelectionBlueprint < T > blueprint in SelectionBlueprints . AliveChildren . Where ( b = > b . IsSelected ) )
2019-10-24 15:58:22 +09:00
{
2023-07-19 15:55:38 +09:00
if ( runForBlueprint ( blueprint ) )
return true ;
}
2020-11-10 17:16:28 +09:00
2023-07-19 15:55:38 +09:00
foreach ( SelectionBlueprint < T > blueprint in ApplySelectionOrder ( SelectionBlueprints . AliveChildren ) )
{
if ( runForBlueprint ( blueprint ) )
return true ;
2019-10-24 15:58:22 +09:00
}
2020-11-03 20:45:48 +09:00
2020-11-15 21:06:47 +01:00
return false ;
2023-07-19 15:55:38 +09:00
bool runForBlueprint ( SelectionBlueprint < T > blueprint )
{
if ( ! blueprint . IsHovered ) return false ;
selectedBlueprintAlreadySelectedOnMouseDown = blueprint . State = = SelectionState . Selected ;
clickSelectionHandled = SelectionHandler . MouseDownSelectionRequested ( blueprint , e ) ;
return true ;
}
2019-10-24 15:58:22 +09:00
}
/// <summary>
/// Finishes the current blueprint selection.
/// </summary>
2021-04-12 19:05:23 +09:00
/// <param name="e">The mouse event which triggered end of selection.</param>
2019-10-24 16:14:29 +09:00
/// <returns>Whether a click selection was active.</returns>
2021-04-12 19:05:23 +09:00
private bool endClickSelection ( MouseButtonEvent e )
2019-10-24 15:58:22 +09:00
{
2023-07-20 20:51:39 +02:00
// If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action.
2023-07-19 15:35:40 +09:00
if ( clickSelectionHandled | | doubleClickHandled | | isDraggingBlueprint ) return true ;
2023-03-08 19:23:32 +09:00
if ( e . Button ! = MouseButton . Left ) return false ;
if ( e . ControlPressed )
2021-04-12 19:05:23 +09:00
{
2023-03-08 19:23:32 +09:00
// if a selection didn't occur, we may want to trigger a deselection.
2021-04-12 19:05:23 +09:00
2023-03-08 19:23:32 +09:00
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
// Priority is given to already-selected blueprints.
foreach ( SelectionBlueprint < T > blueprint in SelectionBlueprints . AliveChildren . Where ( b = > b . IsHovered ) . OrderByDescending ( b = > b . IsSelected ) )
return clickSelectionHandled = SelectionHandler . MouseUpSelectionRequested ( blueprint , e ) ;
2023-03-07 14:22:12 +09:00
2023-03-08 19:23:32 +09:00
return false ;
}
2023-07-19 15:35:40 +09:00
if ( ! wasDragStarted & & selectedBlueprintAlreadySelectedOnMouseDown & & SelectedItems . Count = = 1 )
2023-03-08 19:23:32 +09:00
{
// If a click occurred and was handled by the currently selected blueprint but didn't result in a drag,
// cycle between other blueprints which are also under the cursor.
2023-03-07 14:22:12 +09:00
2023-03-08 19:23:32 +09:00
// The depth of blueprints is constantly changing (see above where selected blueprints are brought to the front).
// For this logic, we want a stable sort order so we can correctly cycle, thus using the blueprintMap instead.
2023-07-19 15:55:38 +09:00
IEnumerable < SelectionBlueprint < T > > cyclingSelectionBlueprints = ApplySelectionOrder ( blueprintMap . Values ) ;
2023-03-07 14:22:12 +09:00
2023-03-08 19:23:32 +09:00
// If there's already a selection, let's start from the blueprint after the selection.
cyclingSelectionBlueprints = cyclingSelectionBlueprints . SkipWhile ( b = > ! b . IsSelected ) . Skip ( 1 ) ;
2023-03-07 14:22:12 +09:00
2023-03-08 19:23:32 +09:00
// Add the blueprints from before the selection to the end of the enumerable to allow for cyclic selection.
2023-07-19 15:55:38 +09:00
cyclingSelectionBlueprints = cyclingSelectionBlueprints . Concat ( ApplySelectionOrder ( blueprintMap . Values ) . TakeWhile ( b = > ! b . IsSelected ) ) ;
2023-03-07 14:22:12 +09:00
2023-03-08 19:23:32 +09:00
foreach ( SelectionBlueprint < T > blueprint in cyclingSelectionBlueprints )
{
if ( ! blueprint . IsHovered ) continue ;
2023-03-07 14:22:12 +09:00
2023-03-08 19:23:32 +09:00
// We are performing a mouse up, but selection handlers perform selection on mouse down, so we need to call that instead.
return clickSelectionHandled = SelectionHandler . MouseDownSelectionRequested ( blueprint , e ) ;
2021-04-12 19:05:23 +09:00
}
}
2019-10-24 16:14:29 +09:00
2023-03-08 19:23:32 +09:00
return false ;
2019-10-24 15:58:22 +09:00
}
2018-11-06 17:51:26 +09:00
/// <summary>
2022-10-05 20:23:59 +09:00
/// Select all blueprints in a selection area specified by <see cref="DragBox"/>.
2018-11-06 17:51:26 +09:00
/// </summary>
2022-10-05 20:23:59 +09:00
protected virtual void UpdateSelectionFromDragBox ( )
2018-11-06 17:51:26 +09:00
{
2022-10-05 20:23:59 +09:00
var quad = DragBox . Box . ScreenSpaceDrawQuad ;
2020-02-07 18:04:10 +09:00
foreach ( var blueprint in SelectionBlueprints )
2018-11-06 17:51:26 +09:00
{
2022-10-11 14:11:45 +09:00
switch ( blueprint . State )
{
case SelectionState . Selected :
// Selection is preserved even after blueprint becomes dead.
if ( ! quad . Contains ( blueprint . ScreenSpaceSelectionPoint ) )
blueprint . Deselect ( ) ;
break ;
2020-06-23 18:42:56 +09:00
2022-10-11 14:11:45 +09:00
case SelectionState . NotSelected :
2023-07-25 18:13:35 +09:00
if ( blueprint . IsSelectable & & quad . Contains ( blueprint . ScreenSpaceSelectionPoint ) )
2022-10-11 14:11:45 +09:00
blueprint . Select ( ) ;
break ;
}
2018-11-06 17:51:26 +09:00
}
}
2019-11-11 13:41:10 +09:00
/// <summary>
2022-10-06 13:50:56 +09:00
/// Select all currently-present items.
2019-11-11 13:41:10 +09:00
/// </summary>
2022-10-06 13:50:56 +09:00
protected abstract void SelectAll ( ) ;
2019-11-11 13:41:10 +09:00
2018-11-06 17:51:26 +09:00
/// <summary>
2022-10-06 13:50:56 +09:00
/// Deselect all selected items.
2018-11-06 17:51:26 +09:00
/// </summary>
2022-10-06 13:50:56 +09:00
protected void DeselectAll ( ) = > SelectedItems . Clear ( ) ;
2018-11-06 17:51:26 +09:00
2021-04-27 15:40:35 +09:00
protected virtual void OnBlueprintSelected ( SelectionBlueprint < T > blueprint )
2018-11-06 17:51:26 +09:00
{
2020-09-25 14:19:35 +09:00
SelectionHandler . HandleSelected ( blueprint ) ;
2020-02-07 18:04:10 +09:00
SelectionBlueprints . ChangeChildDepth ( blueprint , 1 ) ;
2018-11-06 17:51:26 +09:00
}
2021-04-27 15:40:35 +09:00
protected virtual void OnBlueprintDeselected ( SelectionBlueprint < T > blueprint )
2018-11-06 17:51:26 +09:00
{
2020-02-07 18:04:10 +09:00
SelectionBlueprints . ChangeChildDepth ( blueprint , 0 ) ;
2021-04-16 15:55:33 +09:00
SelectionHandler . HandleDeselected ( blueprint ) ;
2018-11-06 17:51:26 +09:00
}
2019-10-24 15:58:22 +09:00
#endregion
#region Selection Movement
2023-01-18 21:34:23 +01:00
private Vector2 [ ] [ ] movementBlueprintsOriginalPositions ;
2021-04-27 15:40:35 +09:00
private SelectionBlueprint < T > [ ] movementBlueprints ;
2023-03-07 14:22:12 +09:00
/// <summary>
/// Whether a blueprint is currently being dragged.
/// </summary>
2023-03-08 19:24:03 +09:00
private bool isDraggingBlueprint ;
2019-10-23 18:58:15 +09:00
2023-03-15 15:16:48 +09:00
/// <summary>
/// Whether a drag operation was started at all.
/// </summary>
private bool wasDragStarted ;
2019-10-24 15:11:54 +09:00
/// <summary>
/// Attempts to begin the movement of any selected blueprints.
/// </summary>
2021-07-18 16:04:23 +02:00
/// <returns>Whether a movement is possible.</returns>
private bool prepareSelectionMovement ( )
2019-10-23 18:37:57 +09:00
{
2020-09-25 14:19:35 +09:00
if ( ! SelectionHandler . SelectedBlueprints . Any ( ) )
2021-07-18 16:04:23 +02:00
return false ;
2019-10-24 15:11:54 +09:00
2021-04-27 18:33:47 +09:00
// Any selected blueprint that is hovered can begin the movement of the group, however only the first item (according to SortForMovement) is used for movement.
2019-10-24 16:14:29 +09:00
// A special case is added for when a click selection occurred before the drag
2023-03-07 14:22:12 +09:00
if ( ! clickSelectionHandled & & ! SelectionHandler . SelectedBlueprints . Any ( b = > b . IsHovered ) )
2021-07-18 16:04:23 +02:00
return false ;
2019-10-24 15:11:54 +09:00
2021-04-27 18:33:47 +09:00
// Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item
2021-04-27 15:40:35 +09:00
movementBlueprints = SortForMovement ( SelectionHandler . SelectedBlueprints ) . ToArray ( ) ;
2023-01-18 22:00:39 +01:00
movementBlueprintsOriginalPositions = movementBlueprints . Select ( m = > m . ScreenSpaceSnapPoints ) . ToArray ( ) ;
2021-07-18 16:04:23 +02:00
return true ;
2019-10-23 18:37:57 +09:00
}
2018-11-06 17:51:26 +09:00
2021-04-27 18:33:47 +09:00
/// <summary>
/// Apply sorting of selected blueprints before performing movement. Generally used to surface the "main" item to the beginning of the collection.
/// </summary>
/// <param name="blueprints">The blueprints to be moved.</param>
/// <returns>Sorted blueprints.</returns>
2021-04-27 15:40:35 +09:00
protected virtual IEnumerable < SelectionBlueprint < T > > SortForMovement ( IReadOnlyList < SelectionBlueprint < T > > blueprints ) = > blueprints ;
2019-10-24 15:11:54 +09: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 18:57:03 +09:00
{
2020-11-25 17:25:54 +09:00
if ( movementBlueprints = = null )
2019-10-24 15:11:54 +09:00
return false ;
2019-10-23 18:37:57 +09:00
2023-01-18 21:34:23 +01:00
Debug . Assert ( movementBlueprintsOriginalPositions ! = null ) ;
2019-10-23 18:58:15 +09:00
2023-01-18 15:54:24 +01:00
Vector2 distanceTravelled = e . ScreenSpaceMousePosition - e . ScreenSpaceMouseDownPosition ;
2020-11-24 17:14:39 +09:00
2021-04-27 17:41:46 +09:00
if ( snapProvider ! = null )
2020-11-24 17:14:39 +09:00
{
2023-01-18 21:34:23 +01:00
for ( int i = 0 ; i < movementBlueprints . Length ; i + + )
{
if ( checkSnappingBlueprintToNearbyObjects ( movementBlueprints [ i ] , distanceTravelled , movementBlueprintsOriginalPositions [ i ] ) )
return true ;
}
2020-11-24 17:14:39 +09:00
}
// if no positional snapping could be performed, try unrestricted snapping from the earliest
2021-04-27 18:33:47 +09:00
// item in the selection.
2019-10-25 12:34:49 +09:00
2020-04-27 20:35:24 +09:00
// The final movement position, relative to movementBlueprintOriginalPosition.
2023-01-18 21:34:23 +01:00
Vector2 movePosition = movementBlueprintsOriginalPositions . First ( ) . First ( ) + distanceTravelled ;
2020-01-22 21:43:02 +09:00
2020-04-27 20:35:24 +09:00
// Retrieve a snapped position.
2022-05-12 15:23:41 +09:00
var result = snapProvider ? . FindSnappedPositionAndTime ( movePosition , ~ SnapType . NearbyObjects ) ;
2021-04-27 17:41:46 +09:00
if ( result = = null )
{
2021-04-29 15:29:25 +09:00
return SelectionHandler . HandleMovement ( new MoveSelectionEvent < T > ( movementBlueprints . First ( ) , movePosition - movementBlueprints . First ( ) . ScreenSpaceSelectionPoint ) ) ;
2021-04-27 17:41:46 +09:00
}
2019-10-16 20:34:02 +09:00
2021-04-27 15:40:35 +09:00
return ApplySnapResult ( movementBlueprints , result ) ;
}
2019-10-23 18:37:57 +09:00
2023-01-17 20:41:49 +01:00
/// <summary>
2023-01-18 21:34:23 +01:00
/// Check for positional snap for given blueprint.
2023-01-17 20:41:49 +01:00
/// </summary>
2023-01-23 21:22:18 +01:00
/// <param name="blueprint">The blueprint to check for snapping.</param>
/// <param name="distanceTravelled">Distance travelled since start of dragging action.</param>
/// <param name="originalPositions">The snap positions of blueprint before start of dragging action.</param>
/// <returns>Whether an object to snap to was found.</returns>
2023-01-18 21:34:23 +01:00
private bool checkSnappingBlueprintToNearbyObjects ( SelectionBlueprint < T > blueprint , Vector2 distanceTravelled , Vector2 [ ] originalPositions )
2023-01-17 20:41:49 +01:00
{
2023-01-18 22:00:39 +01:00
var currentPositions = blueprint . ScreenSpaceSnapPoints ;
2023-01-18 21:34:23 +01:00
2023-01-17 20:41:49 +01:00
for ( int i = 0 ; i < originalPositions . Length ; i + + )
{
Vector2 originalPosition = originalPositions [ i ] ;
2023-01-18 15:54:24 +01:00
var testPosition = originalPosition + distanceTravelled ;
2023-01-17 20:41:49 +01:00
var positionalResult = snapProvider . FindSnappedPositionAndTime ( testPosition , SnapType . NearbyObjects ) ;
if ( positionalResult . ScreenSpacePosition = = testPosition ) continue ;
var delta = positionalResult . ScreenSpacePosition - currentPositions [ i ] ;
// attempt to move the objects, and abort any time based snapping if we can.
2023-01-18 21:34:23 +01:00
if ( SelectionHandler . HandleMovement ( new MoveSelectionEvent < T > ( blueprint , delta ) ) )
2023-01-17 20:41:49 +01:00
return true ;
}
2023-01-18 22:00:39 +01:00
2023-01-17 20:41:49 +01:00
return false ;
}
2021-04-27 17:41:46 +09:00
protected virtual bool ApplySnapResult ( SelectionBlueprint < T > [ ] blueprints , SnapResult result ) = >
2021-04-29 15:29:25 +09:00
SelectionHandler . HandleMovement ( new MoveSelectionEvent < T > ( blueprints . First ( ) , result . ScreenSpacePosition - blueprints . First ( ) . ScreenSpaceSelectionPoint ) ) ;
2018-11-06 17:51:26 +09:00
2019-10-24 15:11:54 +09:00
/// <summary>
/// Finishes the current movement of selected blueprints.
/// </summary>
/// <returns>Whether a movement was active.</returns>
private bool finishSelectionMovement ( )
2019-10-08 18:57:03 +09:00
{
2020-11-25 17:25:54 +09:00
if ( movementBlueprints = = null )
2019-10-24 15:11:54 +09:00
return false ;
2023-01-18 21:34:23 +01:00
movementBlueprintsOriginalPositions = null ;
2020-11-25 17:25:54 +09:00
movementBlueprints = null ;
2019-10-16 20:34:16 +09:00
2019-10-23 18:58:15 +09:00
return true ;
2019-10-08 18:57:03 +09:00
}
2018-11-06 17:51:26 +09:00
2019-10-24 15:58:22 +09:00
#endregion
2018-02-20 18:01:45 +09:00
}
}