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
using System ;
using System.Collections.Generic ;
2020-05-21 16:13:22 +08:00
using System.Collections.Specialized ;
2018-04-13 17:19:50 +08:00
using System.Linq ;
using osu.Framework.Allocation ;
2021-07-19 16:08:40 +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-14 17:34:13 +08:00
using osu.Framework.Input ;
2020-01-28 14:08:02 +08:00
using osu.Framework.Input.Events ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Logging ;
using osu.Game.Beatmaps ;
2022-02-14 16:51:39 +08:00
using osu.Game.Graphics.Containers ;
2022-01-18 01:09:02 +08:00
using osu.Game.Graphics.UserInterface ;
using osu.Game.Overlays.Settings.Sections ;
2018-07-17 15:53:32 +08:00
using osu.Game.Rulesets.Configuration ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Edit.Tools ;
2019-04-08 17:32:05 +08:00
using osu.Game.Rulesets.Mods ;
2018-10-17 16:41:17 +08:00
using osu.Game.Rulesets.Objects ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Objects.Drawables ;
using osu.Game.Rulesets.UI ;
2020-05-25 18:21:53 +08:00
using osu.Game.Rulesets.UI.Scrolling ;
2019-08-29 11:43:43 +08:00
using osu.Game.Screens.Edit ;
2018-11-06 17:14:46 +08:00
using osu.Game.Screens.Edit.Components.RadioButtons ;
2020-09-25 15:33:22 +08:00
using osu.Game.Screens.Edit.Components.TernaryButtons ;
2019-08-29 15:06:40 +08:00
using osu.Game.Screens.Edit.Compose ;
2018-11-06 17:28:22 +08:00
using osu.Game.Screens.Edit.Compose.Components ;
2019-10-18 12:18:57 +08:00
using osuTK ;
2020-08-25 19:57:31 +08:00
using osuTK.Input ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.Edit
{
2020-09-25 13:08:47 +08:00
/// <summary>
/// Top level container for editor compose mode.
/// Responsible for providing snapping and generally gluing components together.
/// </summary>
/// <typeparam name="TObject">The base type of supported objects.</typeparam>
2019-08-29 17:02:50 +08:00
[Cached(Type = typeof(IPlacementHandler))]
public abstract class HitObjectComposer < TObject > : HitObjectComposer , IPlacementHandler
where TObject : HitObject
2018-04-13 17:19:50 +08:00
{
2018-07-17 15:53:32 +08:00
protected IRulesetConfigManager Config { get ; private set ; }
2019-12-05 19:12:25 +08:00
2019-08-29 17:02:50 +08:00
protected readonly Ruleset Ruleset ;
2018-04-13 17:19:50 +08:00
2021-06-17 09:07:52 +08:00
// Provides `Playfield`
private DependencyContainer dependencies ;
2019-10-16 19:20:07 +08:00
[Resolved]
2020-05-22 15:37:28 +08:00
protected EditorClock EditorClock { get ; private set ; }
2019-10-16 19:20:07 +08:00
2019-12-27 18:46:33 +08:00
[Resolved]
protected EditorBeatmap EditorBeatmap { get ; private set ; }
2019-10-25 11:34:49 +08:00
[Resolved]
2020-05-20 17:40:55 +08:00
protected IBeatSnapProvider BeatSnapProvider { get ; private set ; }
2019-10-25 11:34:49 +08:00
2022-01-18 01:09:02 +08:00
/// <summary>
/// Whether this composer supports a "distance spacing" multiplier for distance snap grids.
/// </summary>
/// <remarks>
/// Setting this to <see langword="true"/> displays a "distance spacing" slider and allows this composer to configure the value of <see cref="BeatmapInfo.DistanceSpacing"/>.
/// </remarks>
protected abstract bool SupportsDistanceSpacing { get ; }
2022-02-16 08:28:05 +08:00
private readonly BindableDouble distanceSpacing = new BindableDouble
2022-01-18 01:09:02 +08:00
{
2022-02-16 08:28:05 +08:00
Default = 1.0 ,
MinValue = 0.1 ,
MaxValue = 6.0 ,
Precision = 0.01 ,
2022-01-18 01:09:02 +08:00
} ;
2022-02-16 08:28:05 +08:00
public override IBindable < double > DistanceSpacingMultiplier = > distanceSpacing ;
2022-01-18 01:09:02 +08:00
private SnappingToolboxContainer snappingToolboxContainer ;
2020-02-07 23:12:23 +08:00
protected ComposeBlueprintContainer BlueprintContainer { get ; private set ; }
2020-02-07 18:08:49 +08:00
2021-04-26 14:37:42 +08:00
private DrawableEditorRulesetWrapper < TObject > drawableRulesetWrapper ;
2020-05-21 16:13:22 +08:00
protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes . Both } ;
2018-11-14 17:34:13 +08:00
private InputManager inputManager ;
2021-07-19 15:57:12 +08:00
private EditorRadioButtonCollection toolboxCollection ;
2020-01-28 14:07:37 +08:00
2020-09-25 15:44:37 +08:00
private FillFlowContainer togglesCollection ;
2020-09-24 15:09:06 +08:00
2021-07-19 16:08:40 +08:00
private IBindable < bool > hasTiming ;
2019-08-29 17:02:50 +08:00
protected HitObjectComposer ( Ruleset ruleset )
2018-04-13 17:19:50 +08:00
{
2018-10-17 17:01:38 +08:00
Ruleset = ruleset ;
2018-04-13 17:19:50 +08:00
}
2021-06-17 09:07:52 +08:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent ) = >
dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
2018-04-13 17:19:50 +08:00
[BackgroundDependencyLoader]
2020-05-22 15:37:28 +08:00
private void load ( )
2018-04-13 17:19:50 +08:00
{
2021-12-24 02:02:10 +08:00
Config = Dependencies . Get < IRulesetConfigCache > ( ) . GetConfigFor ( Ruleset ) ;
2019-12-27 18:46:33 +08:00
2018-04-13 17:19:50 +08:00
try
{
2021-04-26 14:37:42 +08:00
drawableRulesetWrapper = new DrawableEditorRulesetWrapper < TObject > ( CreateDrawableRuleset ( Ruleset , EditorBeatmap . PlayableBeatmap , new [ ] { Ruleset . GetAutoplayMod ( ) } ) )
2019-08-29 17:02:50 +08:00
{
2020-05-22 15:37:28 +08:00
Clock = EditorClock ,
2019-10-18 17:21:53 +08:00
ProcessCustomClock = false
2019-08-29 17:02:50 +08:00
} ;
2018-04-13 17:19:50 +08:00
}
catch ( Exception e )
{
2020-09-23 15:30:20 +08:00
Logger . Error ( e , "Could not load beatmap successfully!" ) ;
2018-04-13 17:19:50 +08:00
return ;
}
2021-06-17 09:07:52 +08:00
dependencies . CacheAs ( Playfield ) ;
2020-09-11 19:23:34 +08:00
InternalChildren = new Drawable [ ]
2018-04-13 17:19:50 +08:00
{
2020-09-11 19:23:34 +08:00
new Container
2018-04-13 17:19:50 +08:00
{
2020-09-11 19:23:34 +08:00
Name = "Content" ,
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
2018-04-13 17:19:50 +08:00
{
2020-09-11 19:23:34 +08:00
// layers below playfield
2020-10-20 03:20:13 +08:00
drawableRulesetWrapper . CreatePlayfieldAdjustmentContainer ( ) . WithChild ( LayerBelowRuleset ) ,
2020-09-11 19:23:34 +08:00
drawableRulesetWrapper ,
// layers above playfield
drawableRulesetWrapper . CreatePlayfieldAdjustmentContainer ( )
2020-11-12 18:52:02 +08:00
. WithChild ( BlueprintContainer = CreateBlueprintContainer ( ) )
2020-09-11 19:23:34 +08:00
}
} ,
2022-01-06 20:10:45 +08:00
new LeftToolboxFlow
2020-09-11 19:23:34 +08:00
{
2022-01-18 01:09:02 +08:00
Anchor = Anchor . TopLeft ,
Origin = Anchor . TopLeft ,
2020-09-11 19:23:34 +08:00
Children = new Drawable [ ]
{
2022-01-06 20:10:45 +08:00
new EditorToolboxGroup ( "toolbox (1-9)" )
2018-04-13 17:19:50 +08:00
{
2021-07-19 15:57:12 +08:00
Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes . X }
2018-04-13 17:19:50 +08:00
} ,
2022-01-06 20:10:45 +08:00
new EditorToolboxGroup ( "toggles (Q~P)" )
2018-04-13 17:19:50 +08:00
{
2020-09-25 15:44:37 +08:00
Child = togglesCollection = new FillFlowContainer
2018-04-13 17:19:50 +08:00
{
2020-09-25 15:44:37 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Direction = FillDirection . Vertical ,
Spacing = new Vector2 ( 0 , 5 ) ,
} ,
2022-01-18 01:09:02 +08:00
} ,
2020-09-11 19:23:34 +08:00
}
2018-04-13 17:19:50 +08:00
} ,
} ;
2022-02-16 08:28:05 +08:00
distanceSpacing . Value = EditorBeatmap . BeatmapInfo . DistanceSpacing ;
2022-01-18 01:09:02 +08:00
if ( SupportsDistanceSpacing )
{
2022-02-16 08:28:05 +08:00
ExpandableSlider < double , SizeSlider < double > > distanceSpacingSlider ;
2022-01-18 01:09:02 +08:00
AddInternal ( snappingToolboxContainer = new SnappingToolboxContainer
{
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
Child = new EditorToolboxGroup ( "snapping" )
{
2022-02-16 08:28:05 +08:00
Child = distanceSpacingSlider = new ExpandableSlider < double , SizeSlider < double > >
2022-01-18 01:09:02 +08:00
{
Current = { BindTarget = distanceSpacing } ,
KeyboardStep = 0.01f ,
}
}
} ) ;
distanceSpacing . BindValueChanged ( v = >
{
distanceSpacingSlider . ContractedLabelText = $"D. S. ({v.NewValue:0.##x})" ;
distanceSpacingSlider . ExpandedLabelText = $"Distance Spacing ({v.NewValue:0.##x})" ;
EditorBeatmap . BeatmapInfo . DistanceSpacing = v . NewValue ;
} , true ) ;
}
else
distanceSpacing . Disabled = true ;
2020-01-28 14:00:45 +08:00
toolboxCollection . Items = CompositionTools
. Prepend ( new SelectTool ( ) )
2020-09-09 17:35:25 +08:00
. Select ( t = > new RadioButton ( t . Name , ( ) = > toolSelected ( t ) , t . CreateIcon ) )
2020-01-28 14:00:45 +08:00
. ToList ( ) ;
2018-04-13 17:19:50 +08:00
2020-09-25 16:45:19 +08:00
TernaryStates = CreateTernaryButtons ( ) . ToArray ( ) ;
togglesCollection . AddRange ( TernaryStates . Select ( b = > new DrawableTernaryButton ( b ) ) ) ;
2020-09-25 16:40:43 +08:00
2020-01-28 14:07:37 +08:00
setSelectTool ( ) ;
2019-10-16 19:20:07 +08:00
2020-05-21 16:13:22 +08:00
EditorBeatmap . SelectedHitObjects . CollectionChanged + = selectionChanged ;
2018-04-13 17:19:50 +08:00
}
2020-05-29 10:46:08 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
inputManager = GetContainingInputManager ( ) ;
2021-07-19 16:08:40 +08:00
hasTiming = EditorBeatmap . HasTiming . GetBoundCopy ( ) ;
hasTiming . BindValueChanged ( timing = >
{
// it's important this is performed before the similar code in EditorRadioButton disables the button.
if ( ! timing . NewValue )
setSelectTool ( ) ;
} ) ;
2020-05-29 10:46:08 +08:00
}
public override Playfield Playfield = > drawableRulesetWrapper . Playfield ;
public override IEnumerable < DrawableHitObject > HitObjects = > drawableRulesetWrapper . Playfield . AllHitObjects ;
public override bool CursorInPlacementArea = > drawableRulesetWrapper . Playfield . ReceivePositionalInputAt ( inputManager . CurrentState . Mouse . Position ) ;
/// <summary>
/// Defines all available composition tools, listed on the left side of the editor screen as button controls.
/// This should usually define one tool for each <see cref="HitObject"/> type used in the target ruleset.
/// </summary>
/// <remarks>
/// A "select" tool is automatically added as the first tool.
/// </remarks>
protected abstract IReadOnlyList < HitObjectCompositionTool > CompositionTools { get ; }
2020-09-09 18:14:28 +08:00
/// <summary>
2020-09-25 16:45:19 +08:00
/// A collection of states which will be displayed to the user in the toolbox.
2020-09-09 18:14:28 +08:00
/// </summary>
2020-09-25 16:45:19 +08:00
public TernaryButton [ ] TernaryStates { get ; private set ; }
2020-09-25 16:40:43 +08:00
2020-09-25 16:45:19 +08:00
/// <summary>
/// Create all ternary states required to be displayed to the user.
/// </summary>
protected virtual IEnumerable < TernaryButton > CreateTernaryButtons ( ) = > BlueprintContainer . TernaryStates ;
2020-09-09 18:14:28 +08:00
2020-05-29 10:46:08 +08:00
/// <summary>
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
/// </summary>
2021-04-27 17:06:27 +08:00
protected virtual ComposeBlueprintContainer CreateBlueprintContainer ( ) = > new ComposeBlueprintContainer ( this ) ;
2020-05-29 10:46:08 +08:00
/// <summary>
/// Construct a drawable ruleset for the provided ruleset.
/// </summary>
/// <remarks>
/// Can be overridden to add editor-specific logical changes to a <see cref="Ruleset"/>'s standard <see cref="DrawableRuleset{TObject}"/>.
/// For example, hit animations or judgement logic may be changed to give a better editor user experience.
/// </remarks>
/// <param name="ruleset">The ruleset used to construct its drawable counterpart.</param>
/// <param name="beatmap">The loaded beatmap.</param>
/// <param name="mods">The mods to be applied.</param>
2020-05-31 21:33:10 +08:00
/// <returns>An editor-relevant <see cref="DrawableRuleset{TObject}"/>.</returns>
2020-05-29 10:46:08 +08:00
protected virtual DrawableRuleset < TObject > CreateDrawableRuleset ( Ruleset ruleset , IBeatmap beatmap , IReadOnlyList < Mod > mods = null )
= > ( DrawableRuleset < TObject > ) ruleset . CreateDrawableRulesetWith ( beatmap , mods ) ;
#region Tool selection logic
2022-01-18 01:09:02 +08:00
private bool distanceSpacingScrollActive ;
2020-01-28 14:08:02 +08:00
protected override bool OnKeyDown ( KeyDownEvent e )
{
2022-01-18 01:09:02 +08:00
if ( SupportsDistanceSpacing & & e . AltPressed & & e . Key = = Key . D & & ! e . Repeat )
{
snappingToolboxContainer . Expanded . Value = true ;
distanceSpacingScrollActive = true ;
return true ;
}
2020-09-25 15:56:39 +08:00
if ( e . ControlPressed | | e . AltPressed | | e . SuperPressed )
return false ;
2021-10-27 12:04:41 +08:00
if ( checkLeftToggleFromKey ( e . Key , out int leftIndex ) )
2020-01-28 14:08:02 +08:00
{
2020-09-24 15:09:06 +08:00
var item = toolboxCollection . Items . ElementAtOrDefault ( leftIndex ) ;
2020-01-28 14:08:02 +08:00
if ( item ! = null )
{
2021-07-17 01:30:13 +08:00
if ( ! item . Selected . Disabled )
item . Select ( ) ;
2020-01-28 14:08:02 +08:00
return true ;
}
}
2021-10-27 12:04:41 +08:00
if ( checkRightToggleFromKey ( e . Key , out int rightIndex ) )
2020-09-24 15:09:06 +08:00
{
2020-09-25 12:25:42 +08:00
var item = togglesCollection . ElementAtOrDefault ( rightIndex ) ;
2020-09-24 15:09:06 +08:00
2020-09-25 15:33:22 +08:00
if ( item is DrawableTernaryButton button )
2020-09-24 15:09:06 +08:00
{
2020-09-25 15:33:22 +08:00
button . Button . Toggle ( ) ;
2020-09-24 15:09:06 +08:00
return true ;
}
}
2020-01-28 14:08:02 +08:00
return base . OnKeyDown ( e ) ;
}
2022-01-18 01:09:02 +08:00
protected override void OnKeyUp ( KeyUpEvent e )
{
if ( distanceSpacingScrollActive & & ( e . Key = = Key . AltLeft | | e . Key = = Key . AltRight | | e . Key = = Key . D ) )
{
snappingToolboxContainer . Expanded . Value = false ;
distanceSpacingScrollActive = false ;
}
base . OnKeyUp ( e ) ;
}
protected override bool OnScroll ( ScrollEvent e )
{
if ( distanceSpacingScrollActive )
{
2022-01-26 16:39:31 +08:00
distanceSpacing . Value + = e . ScrollDelta . Y * ( e . IsPrecise ? 0.01f : 0.1f ) ;
2022-01-18 01:09:02 +08:00
return true ;
}
return base . OnScroll ( e ) ;
}
2020-09-24 15:09:06 +08:00
private bool checkLeftToggleFromKey ( Key key , out int index )
{
if ( key < Key . Number1 | | key > Key . Number9 )
{
index = - 1 ;
return false ;
}
index = key - Key . Number1 ;
return true ;
}
private bool checkRightToggleFromKey ( Key key , out int index )
{
switch ( key )
{
case Key . Q :
index = 0 ;
break ;
case Key . W :
index = 1 ;
break ;
case Key . E :
index = 2 ;
break ;
case Key . R :
index = 3 ;
break ;
case Key . T :
index = 4 ;
break ;
case Key . Y :
index = 5 ;
break ;
case Key . U :
index = 6 ;
break ;
case Key . I :
index = 7 ;
break ;
case Key . O :
index = 8 ;
break ;
case Key . P :
index = 9 ;
break ;
default :
index = - 1 ;
break ;
}
return index > = 0 ;
}
2020-05-21 16:13:22 +08:00
private void selectionChanged ( object sender , NotifyCollectionChangedEventArgs changedArgs )
2019-10-16 19:20:07 +08:00
{
2020-05-21 16:13:22 +08:00
if ( EditorBeatmap . SelectedHitObjects . Any ( ) )
2020-01-28 14:07:37 +08:00
{
// ensure in selection mode if a selection is made.
setSelectTool ( ) ;
}
2019-10-16 19:20:07 +08:00
}
2020-01-28 14:07:37 +08:00
private void setSelectTool ( ) = > toolboxCollection . Items . First ( ) . Select ( ) ;
2020-01-28 14:00:45 +08:00
private void toolSelected ( HitObjectCompositionTool tool )
2019-10-16 19:20:07 +08:00
{
2020-02-07 18:08:49 +08:00
BlueprintContainer . CurrentTool = tool ;
2019-10-16 19:20:07 +08:00
2020-05-21 16:13:22 +08:00
if ( ! ( tool is SelectTool ) )
2020-01-28 14:07:37 +08:00
EditorBeatmap . SelectedHitObjects . Clear ( ) ;
2019-10-16 19:20:07 +08:00
}
2020-05-29 10:46:08 +08:00
#endregion
2019-08-29 17:02:50 +08:00
2020-05-29 10:46:08 +08:00
#region IPlacementHandler
2019-08-29 15:06:40 +08:00
public void BeginPlacement ( HitObject hitObject )
{
2020-02-07 17:03:14 +08:00
EditorBeatmap . PlacementObject . Value = hitObject ;
2019-08-29 15:06:40 +08:00
}
2020-02-07 17:02:48 +08:00
public void EndPlacement ( HitObject hitObject , bool commit )
2019-10-18 18:04:08 +08:00
{
2020-02-07 17:03:14 +08:00
EditorBeatmap . PlacementObject . Value = null ;
2020-02-07 17:02:48 +08:00
if ( commit )
{
EditorBeatmap . Add ( hitObject ) ;
2019-11-07 13:08:02 +08:00
2020-05-22 15:37:28 +08:00
if ( EditorClock . CurrentTime < hitObject . StartTime )
2021-01-15 15:14:21 +08:00
EditorClock . SeekSmoothlyTo ( hitObject . StartTime ) ;
2020-02-07 17:02:48 +08:00
}
2019-10-18 18:04:08 +08:00
}
2019-08-29 15:06:40 +08:00
2019-10-16 19:10:03 +08:00
public void Delete ( HitObject hitObject ) = > EditorBeatmap . Remove ( hitObject ) ;
2019-08-29 15:06:40 +08:00
2020-05-29 10:46:08 +08:00
#endregion
#region IPositionSnapProvider
/// <summary>
/// Retrieve the relevant <see cref="Playfield"/> at a specified screen-space position.
/// In cases where a ruleset doesn't require custom logic (due to nested playfields, for example)
/// this will return the ruleset's main playfield.
/// </summary>
/// <param name="screenSpacePosition">The screen-space position to query.</param>
/// <returns>The most relevant <see cref="Playfield"/>.</returns>
2020-05-25 18:21:53 +08:00
protected virtual Playfield PlayfieldAtScreenSpacePosition ( Vector2 screenSpacePosition ) = > drawableRulesetWrapper . Playfield ;
public override SnapResult SnapScreenSpacePositionToValidTime ( Vector2 screenSpacePosition )
{
var playfield = PlayfieldAtScreenSpacePosition ( screenSpacePosition ) ;
double? targetTime = null ;
if ( playfield is ScrollingPlayfield scrollingPlayfield )
{
targetTime = scrollingPlayfield . TimeAtScreenSpacePosition ( screenSpacePosition ) ;
// apply beat snapping
targetTime = BeatSnapProvider . SnapTime ( targetTime . Value ) ;
// convert back to screen space
screenSpacePosition = scrollingPlayfield . ScreenSpacePositionAtTime ( targetTime . Value ) ;
}
return new SnapResult ( screenSpacePosition , targetTime , playfield ) ;
}
2019-10-25 11:34:49 +08:00
2021-09-01 17:05:10 +08:00
public override float GetBeatSnapDistanceAt ( HitObject referenceObject )
2019-10-25 11:34:49 +08:00
{
2022-01-18 01:09:02 +08:00
return ( float ) ( 100 * referenceObject . DifficultyControlPoint . SliderVelocity * EditorBeatmap . Difficulty . SliderMultiplier * distanceSpacing . Value / BeatSnapProvider . BeatDivisor ) ;
2019-10-25 11:34:49 +08:00
}
2021-09-01 17:05:10 +08:00
public override float DurationToDistance ( HitObject referenceObject , double duration )
2019-10-25 11:34:49 +08:00
{
2021-09-01 17:05:10 +08:00
double beatLength = BeatSnapProvider . GetBeatLengthAtTime ( referenceObject . StartTime ) ;
return ( float ) ( duration / beatLength * GetBeatSnapDistanceAt ( referenceObject ) ) ;
2019-10-25 11:34:49 +08:00
}
2019-10-16 19:34:02 +08:00
2021-09-01 17:05:10 +08:00
public override double DistanceToDuration ( HitObject referenceObject , float distance )
2019-10-25 11:34:49 +08:00
{
2021-09-01 17:05:10 +08:00
double beatLength = BeatSnapProvider . GetBeatLengthAtTime ( referenceObject . StartTime ) ;
return distance / GetBeatSnapDistanceAt ( referenceObject ) * beatLength ;
2019-10-25 11:34:49 +08:00
}
2021-09-01 17:05:10 +08:00
public override double GetSnappedDurationFromDistance ( HitObject referenceObject , float distance )
= > BeatSnapProvider . SnapTime ( referenceObject . StartTime + DistanceToDuration ( referenceObject , distance ) , referenceObject . StartTime ) - referenceObject . StartTime ;
2019-10-25 11:34:49 +08:00
2021-09-01 17:05:10 +08:00
public override float GetSnappedDistanceFromDistance ( HitObject referenceObject , float distance )
2020-02-07 15:43:50 +08:00
{
2021-09-01 17:05:10 +08:00
double startTime = referenceObject . StartTime ;
2020-08-25 17:56:15 +08:00
2021-09-01 17:05:10 +08:00
double actualDuration = startTime + DistanceToDuration ( referenceObject , distance ) ;
2020-08-25 17:56:15 +08:00
2021-09-01 17:05:10 +08:00
double snappedEndTime = BeatSnapProvider . SnapTime ( actualDuration , startTime ) ;
double beatLength = BeatSnapProvider . GetBeatLengthAtTime ( startTime ) ;
2020-08-25 19:57:31 +08:00
2020-08-25 17:56:15 +08:00
// we don't want to exceed the actual duration and snap to a point in the future.
2020-08-25 19:57:31 +08:00
// as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it.
if ( snappedEndTime > actualDuration + 1 )
snappedEndTime - = beatLength ;
2020-02-07 15:43:50 +08:00
2021-09-01 17:05:10 +08:00
return DurationToDistance ( referenceObject , snappedEndTime - startTime ) ;
2020-02-07 15:43:50 +08:00
}
2020-05-29 10:46:08 +08:00
#endregion
2022-01-06 20:10:45 +08:00
private class LeftToolboxFlow : ExpandingButtonContainer
{
public LeftToolboxFlow ( )
: base ( 80 , 200 )
{
RelativeSizeAxes = Axes . Y ;
Padding = new MarginPadding { Right = 10 } ;
2022-01-18 01:09:02 +08:00
FillFlow . Spacing = new Vector2 ( 10 ) ;
}
}
private class SnappingToolboxContainer : ExpandingContainer
{
public SnappingToolboxContainer ( )
: base ( 130 , 250 )
{
RelativeSizeAxes = Axes . Y ;
Padding = new MarginPadding { Left = 10 } ;
2022-01-06 20:10:45 +08:00
FillFlow . Spacing = new Vector2 ( 10 ) ;
}
}
2018-10-17 17:01:38 +08:00
}
2019-08-29 17:02:50 +08:00
2020-05-29 10:46:08 +08:00
/// <summary>
/// A non-generic definition of a HitObject composer class.
/// Generally used to access certain methods without requiring a generic type for <see cref="HitObjectComposer{T}" />.
/// </summary>
2019-08-29 17:02:50 +08:00
[Cached(typeof(HitObjectComposer))]
2020-05-20 16:48:43 +08:00
[Cached(typeof(IPositionSnapProvider))]
2021-06-17 09:07:52 +08:00
public abstract class HitObjectComposer : CompositeDrawable , IPositionSnapProvider
2019-08-29 17:02:50 +08:00
{
2020-05-27 22:15:16 +08:00
protected HitObjectComposer ( )
2019-08-29 17:02:50 +08:00
{
RelativeSizeAxes = Axes . Both ;
}
2020-05-29 10:46:08 +08:00
/// <summary>
/// The target ruleset's playfield.
/// </summary>
2020-05-27 22:15:16 +08:00
public abstract Playfield Playfield { get ; }
2019-08-29 17:02:50 +08:00
/// <summary>
2020-05-29 10:46:08 +08:00
/// All <see cref="DrawableHitObject"/>s in currently loaded beatmap.
2019-08-29 17:02:50 +08:00
/// </summary>
public abstract IEnumerable < DrawableHitObject > HitObjects { get ; }
/// <summary>
/// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement.
/// </summary>
public abstract bool CursorInPlacementArea { get ; }
2021-03-29 17:29:05 +08:00
public virtual string ConvertSelectionToString ( ) = > string . Empty ;
2021-03-26 15:25:20 +08:00
2020-05-29 10:46:08 +08:00
#region IPositionSnapProvider
2022-02-16 08:28:05 +08:00
public abstract IBindable < double > DistanceSpacingMultiplier { get ; }
2022-01-18 20:25:32 +08:00
2020-05-20 17:19:21 +08:00
public abstract SnapResult SnapScreenSpacePositionToValidTime ( Vector2 screenSpacePosition ) ;
2019-10-31 17:15:19 +08:00
2020-11-24 16:14:39 +08:00
public virtual SnapResult SnapScreenSpacePositionToValidPosition ( Vector2 screenSpacePosition ) = >
new SnapResult ( screenSpacePosition , null ) ;
2021-09-01 17:05:10 +08:00
public abstract float GetBeatSnapDistanceAt ( HitObject referenceObject ) ;
2019-10-31 17:15:19 +08:00
2021-09-01 17:05:10 +08:00
public abstract float DurationToDistance ( HitObject referenceObject , double duration ) ;
2019-10-31 17:15:19 +08:00
2021-09-01 17:05:10 +08:00
public abstract double DistanceToDuration ( HitObject referenceObject , float distance ) ;
2019-10-31 17:15:19 +08:00
2021-09-01 17:05:10 +08:00
public abstract double GetSnappedDurationFromDistance ( HitObject referenceObject , float distance ) ;
2019-10-31 17:15:19 +08:00
2021-09-01 17:05:10 +08:00
public abstract float GetSnappedDistanceFromDistance ( HitObject referenceObject , float distance ) ;
2020-05-29 10:46:08 +08:00
#endregion
2019-08-29 17:02:50 +08:00
}
2018-04-13 17:19:50 +08:00
}