2019-10-10 20:55:48 +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.
2022-06-17 15:37:17 +08:00
#nullable disable
2020-01-28 10:59:21 +08:00
using System ;
2019-10-10 20:55:48 +08:00
using osu.Framework.Allocation ;
2022-01-18 20:25:32 +08:00
using osu.Framework.Bindables ;
2022-05-06 17:25:02 +08:00
using osu.Framework.Extensions.Color4Extensions ;
2019-10-10 20:55:48 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2020-02-24 19:52:15 +08:00
using osu.Framework.Layout ;
2023-06-09 14:54:22 +08:00
using osu.Game.Configuration ;
2019-10-10 20:55:48 +08:00
using osu.Game.Graphics ;
2019-10-25 11:34:49 +08:00
using osu.Game.Rulesets.Edit ;
2021-09-01 17:05:10 +08:00
using osu.Game.Rulesets.Objects ;
2019-10-10 20:55:48 +08:00
using osuTK ;
2022-05-06 17:25:02 +08:00
using osuTK.Graphics ;
2019-10-10 20:55:48 +08:00
namespace osu.Game.Screens.Edit.Compose.Components
{
2019-10-17 14:32:02 +08:00
/// <summary>
/// A grid which takes user input and returns a quantized ("snapped") position and time.
/// </summary>
public abstract partial class DistanceSnapGrid : CompositeDrawable
2019-10-10 20:55:48 +08:00
{
2019-10-11 14:41:01 +08:00
/// <summary>
2019-10-25 11:34:49 +08:00
/// The spacing between each tick of the beat snapping grid.
2019-10-11 14:41:01 +08:00
/// </summary>
2022-05-06 14:45:36 +08:00
protected float DistanceBetweenTicks { get ; private set ; }
2019-10-10 20:55:48 +08:00
2022-05-05 16:08:53 +08:00
protected IBindable < double > DistanceSpacingMultiplier { get ; private set ; }
2019-10-11 14:41:01 +08:00
/// <summary>
2019-12-10 15:00:09 +08:00
/// The maximum number of distance snapping intervals allowed.
2019-10-11 14:41:01 +08:00
/// </summary>
2019-12-10 15:00:09 +08:00
protected int MaxIntervals { get ; private set ; }
2019-10-10 20:55:48 +08:00
2019-11-06 13:55:05 +08:00
/// <summary>
2019-12-10 15:00:09 +08:00
/// The position which the grid should start.
2022-05-06 14:45:36 +08:00
/// The first beat snapping tick is located at <see cref="StartPosition"/> + <see cref="DistanceBetweenTicks"/> away from this point.
2019-11-06 13:55:05 +08:00
/// </summary>
2019-12-10 15:00:09 +08:00
protected readonly Vector2 StartPosition ;
2019-11-06 13:55:05 +08:00
2019-10-11 14:41:21 +08:00
/// <summary>
2019-12-10 15:00:09 +08:00
/// The snapping time at <see cref="StartPosition"/>.
2019-10-11 14:41:21 +08:00
/// </summary>
2019-12-10 15:00:09 +08:00
protected readonly double StartTime ;
2019-10-10 20:55:48 +08:00
2022-05-05 17:25:46 +08:00
protected readonly double? LatestEndTime ;
2019-10-10 20:55:48 +08:00
[Resolved]
2019-10-23 15:58:56 +08:00
protected OsuColour Colours { get ; private set ; }
2019-10-10 20:55:48 +08:00
[Resolved]
2022-04-24 12:08:53 +08:00
protected IDistanceSnapProvider SnapProvider { get ; private set ; }
2019-10-10 20:55:48 +08:00
[Resolved]
2022-05-06 16:09:02 +08:00
protected EditorBeatmap Beatmap { get ; private set ; }
2019-10-10 20:55:48 +08:00
2019-10-25 11:34:49 +08:00
[Resolved]
2019-10-25 16:25:46 +08:00
private BindableBeatDivisor beatDivisor { get ; set ; }
2019-10-25 11:34:49 +08:00
2023-06-09 14:54:22 +08:00
/// <summary>
/// When enabled, distance snap should only snap to the current time (as per the editor clock).
/// This is to emulate stable behaviour.
/// </summary>
2023-06-16 22:24:07 +08:00
protected Bindable < bool > LimitedDistanceSnap { get ; private set ; }
2023-06-09 14:54:22 +08:00
[BackgroundDependencyLoader]
private void load ( OsuConfigManager config )
{
LimitedDistanceSnap = config . GetBindable < bool > ( OsuSetting . EditorLimitedDistanceSnap ) ;
}
2020-02-24 19:52:15 +08:00
private readonly LayoutValue gridCache = new LayoutValue ( Invalidation . RequiredParentSizeToFit ) ;
2019-10-10 20:55:48 +08:00
2021-09-01 17:05:10 +08:00
protected readonly HitObject ReferenceObject ;
2019-12-10 15:00:09 +08:00
/// <summary>
/// Creates a new <see cref="DistanceSnapGrid"/>.
/// </summary>
2021-09-01 17:05:10 +08:00
/// <param name="referenceObject">A reference object to gather relevant difficulty values from.</param>
2019-12-10 15:00:09 +08:00
/// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param>
/// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param>
/// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param>
2021-09-01 17:05:10 +08:00
protected DistanceSnapGrid ( HitObject referenceObject , Vector2 startPosition , double startTime , double? endTime = null )
2019-10-10 20:55:48 +08:00
{
2021-09-01 17:05:10 +08:00
ReferenceObject = referenceObject ;
2022-05-05 17:25:46 +08:00
LatestEndTime = endTime ;
2021-09-01 17:05:10 +08:00
2019-12-10 15:00:09 +08:00
StartPosition = startPosition ;
StartTime = startTime ;
2019-11-06 13:55:05 +08:00
2019-10-10 20:55:48 +08:00
RelativeSizeAxes = Axes . Both ;
2020-02-24 19:52:15 +08:00
AddLayout ( gridCache ) ;
2019-10-10 20:55:48 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2022-04-28 11:52:42 +08:00
beatDivisor . BindValueChanged ( _ = > updateSpacing ( ) ) ;
2022-01-18 20:25:32 +08:00
2022-05-05 16:08:53 +08:00
DistanceSpacingMultiplier = SnapProvider . DistanceSpacingMultiplier . GetBoundCopy ( ) ;
DistanceSpacingMultiplier . BindValueChanged ( _ = > updateSpacing ( ) , true ) ;
2019-10-10 20:55:48 +08:00
}
private void updateSpacing ( )
{
2022-05-06 16:09:02 +08:00
float distanceSpacingMultiplier = ( float ) DistanceSpacingMultiplier . Value ;
2022-11-01 14:00:23 +08:00
float beatSnapDistance = SnapProvider . GetBeatSnapDistanceAt ( ReferenceObject , false ) ;
2022-05-06 16:09:02 +08:00
DistanceBetweenTicks = beatSnapDistance * distanceSpacingMultiplier ;
2019-11-06 13:55:05 +08:00
2022-05-05 17:25:46 +08:00
if ( LatestEndTime = = null )
2019-11-06 13:55:05 +08:00
MaxIntervals = int . MaxValue ;
else
2022-05-06 16:09:02 +08:00
MaxIntervals = ( int ) ( ( LatestEndTime . Value - StartTime ) / SnapProvider . DistanceToDuration ( ReferenceObject , beatSnapDistance ) ) ;
2019-11-06 13:55:05 +08:00
2019-10-10 20:55:48 +08:00
gridCache . Invalidate ( ) ;
}
protected override void Update ( )
{
base . Update ( ) ;
if ( ! gridCache . IsValid )
{
ClearInternal ( ) ;
2020-01-28 10:59:21 +08:00
CreateContent ( ) ;
2019-10-10 20:55:48 +08:00
gridCache . Validate ( ) ;
}
}
/// <summary>
2019-10-11 14:41:01 +08:00
/// Creates the content which visualises the grid ticks.
2019-10-10 20:55:48 +08:00
/// </summary>
2020-01-28 10:59:21 +08:00
protected abstract void CreateContent ( ) ;
2019-10-10 20:55:48 +08:00
/// <summary>
2019-10-11 14:46:11 +08:00
/// Snaps a position to this grid.
2019-10-10 20:55:48 +08:00
/// </summary>
2019-10-17 14:32:02 +08:00
/// <param name="position">The original position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param>
2019-10-25 11:34:49 +08:00
/// <returns>A tuple containing the snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/> and the respective time value.</returns>
public abstract ( Vector2 position , double time ) GetSnappedPosition ( Vector2 position ) ;
2019-10-24 17:09:20 +08:00
2019-10-10 20:55:48 +08:00
/// <summary>
/// Retrieves the applicable colour for a beat index.
/// </summary>
2020-01-28 10:59:21 +08:00
/// <param name="placementIndex">The 0-based beat index from the point of placement.</param>
2019-10-10 20:55:48 +08:00
/// <returns>The applicable colour.</returns>
2022-05-06 17:25:02 +08:00
protected Color4 GetColourForIndexFromPlacement ( int placementIndex )
2019-10-10 20:55:48 +08:00
{
2022-05-06 16:09:02 +08:00
var timingPoint = Beatmap . ControlPointInfo . TimingPointAt ( StartTime ) ;
2021-10-27 12:04:41 +08:00
double beatLength = timingPoint . BeatLength / beatDivisor . Value ;
int beatIndex = ( int ) Math . Round ( ( StartTime - timingPoint . Time ) / beatLength ) ;
2020-01-29 14:16:48 +08:00
2020-01-28 10:59:21 +08:00
var colour = BindableBeatDivisor . GetColourFor ( BindableBeatDivisor . GetDivisorForBeatIndex ( beatIndex + placementIndex + 1 , beatDivisor . Value ) , Colours ) ;
2019-10-10 20:55:48 +08:00
2020-01-28 10:59:21 +08:00
int repeatIndex = placementIndex / beatDivisor . Value ;
2022-05-06 17:25:02 +08:00
return colour . Opacity ( 0.5f / ( repeatIndex + 1 ) ) ;
2019-10-10 20:55:48 +08:00
}
}
}