2021-06-22 10:39:04 +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.
2021-07-19 21:43:28 +08:00
using System.Collections.Generic ;
2021-06-22 10:39:04 +08:00
using System.Linq ;
2021-07-19 21:15:35 +08:00
using JetBrains.Annotations ;
2021-06-22 10:39:04 +08:00
using osu.Framework.Allocation ;
2021-07-06 15:46:12 +08:00
using osu.Framework.Caching ;
2021-07-06 16:15:51 +08:00
using osu.Framework.Graphics ;
2021-06-22 10:39:04 +08:00
using osu.Framework.Graphics.Primitives ;
2021-07-19 21:43:28 +08:00
using osu.Framework.Graphics.UserInterface ;
using osu.Framework.Input.Events ;
using osu.Game.Graphics.UserInterface ;
2021-07-06 15:46:12 +08:00
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components ;
2021-06-22 10:39:04 +08:00
using osu.Game.Rulesets.Catch.Objects ;
using osu.Game.Rulesets.Objects ;
2021-07-19 21:15:35 +08:00
using osu.Game.Screens.Edit ;
2021-06-22 10:39:04 +08:00
using osuTK ;
2021-07-19 21:43:28 +08:00
using osuTK.Input ;
2021-06-22 10:39:04 +08:00
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
{
public class JuiceStreamSelectionBlueprint : CatchSelectionBlueprint < JuiceStream >
{
2021-06-23 09:19:09 +08:00
public override Quad SelectionQuad = > HitObjectContainer . ToScreenSpace ( getBoundingBox ( ) . Offset ( new Vector2 ( 0 , HitObjectContainer . DrawHeight ) ) ) ;
2021-06-22 10:39:04 +08:00
2021-07-19 21:43:28 +08:00
public override MenuItem [ ] ContextMenuItems = > getContextMenuItems ( ) . ToArray ( ) ;
2021-06-22 10:39:04 +08:00
private float minNestedX ;
private float maxNestedX ;
2021-07-06 15:46:12 +08:00
private readonly ScrollingPath scrollingPath ;
2021-07-06 16:15:51 +08:00
private readonly NestedOutlineContainer nestedOutlineContainer ;
2021-07-06 15:46:12 +08:00
private readonly Cached pathCache = new Cached ( ) ;
2021-07-19 21:15:35 +08:00
private readonly SelectionEditablePath editablePath ;
2021-07-21 12:27:07 +08:00
/// <summary>
/// The <see cref="JuiceStreamPath.InvalidationID"/> of the <see cref="JuiceStreamPath"/> corresponding the current <see cref="SliderPath"/> of the hit object.
/// When the path is edited, the change is detected and the <see cref="SliderPath"/> of the hit object is updated.
/// </summary>
2021-07-19 21:15:35 +08:00
private int lastEditablePathId = - 1 ;
2021-07-21 12:27:07 +08:00
/// <summary>
/// The <see cref="SliderPath.Version"/> of the current <see cref="SliderPath"/> of the hit object.
/// When the <see cref="SliderPath"/> of the hit object is changed by external means, the change is detected and the <see cref="JuiceStreamPath"/> is re-initialized.
/// </summary>
2021-07-19 21:15:35 +08:00
private int lastSliderPathVersion = - 1 ;
2021-07-19 21:43:28 +08:00
private Vector2 rightMouseDownPosition ;
2021-07-19 21:15:35 +08:00
[Resolved(CanBeNull = true)]
[CanBeNull]
private EditorBeatmap editorBeatmap { get ; set ; }
2021-06-22 10:39:04 +08:00
public JuiceStreamSelectionBlueprint ( JuiceStream hitObject )
: base ( hitObject )
{
2021-07-06 16:15:51 +08:00
InternalChildren = new Drawable [ ]
{
scrollingPath = new ScrollingPath ( ) ,
2021-07-19 21:15:35 +08:00
nestedOutlineContainer = new NestedOutlineContainer ( ) ,
editablePath = new SelectionEditablePath ( positionToDistance )
2021-07-06 16:15:51 +08:00
} ;
2021-06-22 10:39:04 +08:00
}
[BackgroundDependencyLoader]
private void load ( )
{
HitObject . DefaultsApplied + = onDefaultsApplied ;
2021-06-23 09:19:09 +08:00
computeObjectBounds ( ) ;
2021-06-22 10:39:04 +08:00
}
2021-07-06 15:46:12 +08:00
protected override void Update ( )
{
base . Update ( ) ;
if ( ! IsSelected ) return ;
2021-07-19 21:15:35 +08:00
if ( editablePath . PathId ! = lastEditablePathId )
updateHitObjectFromPath ( ) ;
Vector2 startPosition = CatchHitObjectUtils . GetStartPosition ( HitObjectContainer , HitObject ) ;
editablePath . Position = nestedOutlineContainer . Position = scrollingPath . Position = startPosition ;
editablePath . UpdateFrom ( HitObjectContainer , HitObject ) ;
2021-07-06 15:46:12 +08:00
if ( pathCache . IsValid ) return ;
scrollingPath . UpdatePathFrom ( HitObjectContainer , HitObject ) ;
2021-07-06 16:15:51 +08:00
nestedOutlineContainer . UpdateNestedObjectsFrom ( HitObjectContainer , HitObject ) ;
2021-07-06 15:46:12 +08:00
pathCache . Validate ( ) ;
}
2021-07-19 21:15:35 +08:00
protected override void OnSelected ( )
{
initializeJuiceStreamPath ( ) ;
base . OnSelected ( ) ;
}
2021-07-19 21:43:28 +08:00
protected override bool OnMouseDown ( MouseDownEvent e )
{
if ( ! IsSelected ) return base . OnMouseDown ( e ) ;
switch ( e . Button )
{
case MouseButton . Left when e . ControlPressed :
editablePath . AddVertex ( editablePath . ToRelativePosition ( e . ScreenSpaceMouseDownPosition ) ) ;
return true ;
case MouseButton . Right :
// Record the mouse position to be used in the "add vertex" action.
rightMouseDownPosition = editablePath . ToRelativePosition ( e . ScreenSpaceMouseDownPosition ) ;
break ;
}
return base . OnMouseDown ( e ) ;
}
2021-07-06 15:46:12 +08:00
private void onDefaultsApplied ( HitObject _ )
{
computeObjectBounds ( ) ;
pathCache . Invalidate ( ) ;
2021-07-19 21:15:35 +08:00
if ( lastSliderPathVersion ! = HitObject . Path . Version . Value )
initializeJuiceStreamPath ( ) ;
2021-07-06 15:46:12 +08:00
}
2021-06-22 10:39:04 +08:00
2021-06-23 09:19:09 +08:00
private void computeObjectBounds ( )
2021-06-22 10:39:04 +08:00
{
minNestedX = HitObject . NestedHitObjects . OfType < CatchHitObject > ( ) . Min ( nested = > nested . OriginalX ) - HitObject . OriginalX ;
maxNestedX = HitObject . NestedHitObjects . OfType < CatchHitObject > ( ) . Max ( nested = > nested . OriginalX ) - HitObject . OriginalX ;
}
2021-06-23 09:19:09 +08:00
private RectangleF getBoundingBox ( )
2021-06-22 10:39:04 +08:00
{
float left = HitObject . OriginalX + minNestedX ;
float right = HitObject . OriginalX + maxNestedX ;
float top = HitObjectContainer . PositionAtTime ( HitObject . EndTime ) ;
float bottom = HitObjectContainer . PositionAtTime ( HitObject . StartTime ) ;
float objectRadius = CatchHitObject . OBJECT_RADIUS * HitObject . Scale ;
return new RectangleF ( left , top , right - left , bottom - top ) . Inflate ( objectRadius ) ;
}
2021-07-19 21:15:35 +08:00
private double positionToDistance ( float relativeYPosition )
{
double time = HitObjectContainer . TimeAtPosition ( relativeYPosition , HitObject . StartTime ) ;
return ( time - HitObject . StartTime ) * HitObject . Velocity ;
}
private void initializeJuiceStreamPath ( )
{
editablePath . InitializeFromHitObject ( HitObject ) ;
// Record the current ID to update the hit object only when a change is made to the path.
lastEditablePathId = editablePath . PathId ;
lastSliderPathVersion = HitObject . Path . Version . Value ;
}
private void updateHitObjectFromPath ( )
{
editablePath . UpdateHitObjectFromPath ( HitObject ) ;
editorBeatmap ? . Update ( HitObject ) ;
lastEditablePathId = editablePath . PathId ;
lastSliderPathVersion = HitObject . Path . Version . Value ;
}
2021-07-19 21:43:28 +08:00
private IEnumerable < MenuItem > getContextMenuItems ( )
{
yield return new OsuMenuItem ( "Add vertex" , MenuItemType . Standard , ( ) = >
{
editablePath . AddVertex ( rightMouseDownPosition ) ;
} ) ;
}
2021-06-22 10:39:04 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
HitObject . DefaultsApplied - = onDefaultsApplied ;
}
}
}