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 ;
using System.Linq ;
2019-10-16 19:20:07 +08:00
using JetBrains.Annotations ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Allocation ;
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.Framework.Timing ;
using osu.Game.Beatmaps ;
2019-10-25 11:34:49 +08:00
using osu.Game.Beatmaps.ControlPoints ;
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 ;
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 ;
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-01-28 14:08:02 +08:00
using Key = osuTK . Input . Key ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.Edit
{
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
2019-10-16 19:20:07 +08:00
[Resolved]
protected IFrameBasedClock EditorClock { get ; private set ; }
2019-12-27 18:46:33 +08:00
[Resolved]
protected EditorBeatmap EditorBeatmap { get ; private set ; }
2019-11-07 13:08:02 +08:00
[Resolved]
private IAdjustableClock adjustableClock { get ; set ; }
2019-10-25 11:34:49 +08:00
[Resolved]
2020-01-23 13:39:56 +08:00
private IBeatSnapProvider beatSnapProvider { get ; set ; }
2019-10-25 11:34:49 +08:00
2019-08-29 17:20:43 +08:00
private DrawableEditRulesetWrapper < TObject > drawableRulesetWrapper ;
2020-01-15 18:09:49 +08:00
private ComposeBlueprintContainer blueprintContainer ;
2019-10-16 19:20:07 +08:00
private Container distanceSnapGridContainer ;
private DistanceSnapGrid distanceSnapGrid ;
2019-08-29 17:02:50 +08:00
private readonly List < Container > layerContainers = new List < Container > ( ) ;
2018-10-17 14:46:30 +08:00
2018-11-14 17:34:13 +08:00
private InputManager inputManager ;
2020-01-28 14:07:37 +08:00
private RadioButtonCollection toolboxCollection ;
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
RelativeSizeAxes = Axes . Both ;
}
[BackgroundDependencyLoader]
2019-08-29 17:02:50 +08:00
private void load ( IFrameBasedClock framedClock )
2018-04-13 17:19:50 +08:00
{
2019-12-27 18:46:33 +08:00
EditorBeatmap . HitObjectAdded + = addHitObject ;
EditorBeatmap . HitObjectRemoved + = removeHitObject ;
EditorBeatmap . StartTimeChanged + = UpdateHitObject ;
Config = Dependencies . Get < RulesetConfigCache > ( ) . GetConfigFor ( Ruleset ) ;
2018-04-13 17:19:50 +08:00
try
{
2019-12-27 18:46:33 +08:00
drawableRulesetWrapper = new DrawableEditRulesetWrapper < TObject > ( CreateDrawableRuleset ( Ruleset , EditorBeatmap . PlayableBeatmap ) )
2019-08-29 17:02:50 +08:00
{
2019-10-18 17:21:53 +08:00
Clock = framedClock ,
ProcessCustomClock = false
2019-08-29 17:02:50 +08:00
} ;
2018-04-13 17:19:50 +08:00
}
catch ( Exception e )
{
Logger . Error ( e , "Could not load beatmap sucessfully!" ) ;
return ;
}
2019-10-16 19:20:07 +08:00
var layerBelowRuleset = drawableRulesetWrapper . CreatePlayfieldAdjustmentContainer ( ) . WithChildren ( new Drawable [ ]
{
distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes . Both } ,
new EditorPlayfieldBorder { RelativeSizeAxes = Axes . Both }
} ) ;
2018-04-13 17:19:50 +08:00
2020-01-02 10:46:18 +08:00
var layerAboveRuleset = drawableRulesetWrapper . CreatePlayfieldAdjustmentContainer ( ) . WithChild ( blueprintContainer = CreateBlueprintContainer ( ) ) ;
2018-04-13 17:19:50 +08:00
layerContainers . Add ( layerBelowRuleset ) ;
layerContainers . Add ( layerAboveRuleset ) ;
InternalChild = new GridContainer
{
RelativeSizeAxes = Axes . Both ,
Content = new [ ]
{
new Drawable [ ]
{
new FillFlowContainer
{
Name = "Sidebar" ,
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding { Right = 10 } ,
Children = new Drawable [ ]
{
new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes . X } }
}
} ,
new Container
{
Name = "Content" ,
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
{
layerBelowRuleset ,
2019-08-29 17:20:43 +08:00
drawableRulesetWrapper ,
2018-04-13 17:19:50 +08:00
layerAboveRuleset
}
}
} ,
} ,
ColumnDimensions = new [ ]
{
new Dimension ( GridSizeMode . Absolute , 200 ) ,
}
} ;
2020-01-28 14:00:45 +08:00
toolboxCollection . Items = CompositionTools
. Prepend ( new SelectTool ( ) )
. Select ( t = > new RadioButton ( t . Name , ( ) = > toolSelected ( t ) ) )
. ToList ( ) ;
2018-04-13 17:19:50 +08:00
2020-01-28 14:07:37 +08:00
setSelectTool ( ) ;
2019-10-16 19:20:07 +08:00
blueprintContainer . SelectionChanged + = selectionChanged ;
2018-04-13 17:19:50 +08:00
}
2020-01-28 14:08:02 +08:00
protected override bool OnKeyDown ( KeyDownEvent e )
{
if ( e . Key > = Key . Number1 & & e . Key < = Key . Number9 )
{
2020-02-01 01:32:47 +08:00
var item = toolboxCollection . Items . ElementAtOrDefault ( e . Key - Key . Number1 ) ;
2020-01-28 14:08:02 +08:00
if ( item ! = null )
{
item . Select ( ) ;
return true ;
}
}
return base . OnKeyDown ( e ) ;
}
2019-08-29 17:02:50 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
inputManager = GetContainingInputManager ( ) ;
2018-07-17 14:35:32 +08:00
}
2019-10-23 15:39:14 +08:00
private double lastGridUpdateTime ;
2019-10-16 19:20:07 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2020-02-07 15:09:54 +08:00
if ( EditorClock . CurrentTime ! = lastGridUpdateTime & & ! ( blueprintContainer . CurrentTool is SelectTool ) )
2019-10-16 19:20:07 +08:00
showGridFor ( Enumerable . Empty < HitObject > ( ) ) ;
}
2018-04-13 17:19:50 +08:00
protected override void UpdateAfterChildren ( )
{
base . UpdateAfterChildren ( ) ;
layerContainers . ForEach ( l = >
{
2019-08-29 17:20:43 +08:00
l . Anchor = drawableRulesetWrapper . Playfield . Anchor ;
l . Origin = drawableRulesetWrapper . Playfield . Origin ;
l . Position = drawableRulesetWrapper . Playfield . Position ;
l . Size = drawableRulesetWrapper . Playfield . Size ;
2018-04-13 17:19:50 +08:00
} ) ;
}
2019-10-16 19:20:07 +08:00
private void selectionChanged ( IEnumerable < HitObject > selectedHitObjects )
{
var hitObjects = selectedHitObjects . ToArray ( ) ;
2020-01-28 14:07:37 +08:00
if ( hitObjects . Any ( ) )
{
// ensure in selection mode if a selection is made.
setSelectTool ( ) ;
2019-10-16 19:20:07 +08:00
showGridFor ( hitObjects ) ;
2020-01-28 14:07:37 +08:00
}
else
distanceSnapGridContainer . Hide ( ) ;
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
{
blueprintContainer . CurrentTool = tool ;
2020-01-28 14:00:45 +08:00
if ( tool is SelectTool )
2019-10-16 19:20:07 +08:00
distanceSnapGridContainer . Hide ( ) ;
else
2020-01-28 14:07:37 +08:00
{
EditorBeatmap . SelectedHitObjects . Clear ( ) ;
2019-10-16 19:20:07 +08:00
showGridFor ( Enumerable . Empty < HitObject > ( ) ) ;
2020-01-28 14:07:37 +08:00
}
2019-10-16 19:20:07 +08:00
}
private void showGridFor ( IEnumerable < HitObject > selectedHitObjects )
{
distanceSnapGridContainer . Clear ( ) ;
distanceSnapGrid = CreateDistanceSnapGrid ( selectedHitObjects ) ;
if ( distanceSnapGrid ! = null )
{
distanceSnapGridContainer . Child = distanceSnapGrid ;
distanceSnapGridContainer . Show ( ) ;
}
2019-10-23 16:49:21 +08:00
lastGridUpdateTime = EditorClock . CurrentTime ;
2019-10-16 19:20:07 +08:00
}
2019-10-31 17:15:19 +08:00
private void addHitObject ( HitObject hitObject ) = > UpdateHitObject ( hitObject ) ;
private void removeHitObject ( HitObject hitObject ) = > UpdateHitObject ( null ) ;
2019-08-29 17:20:43 +08:00
public override IEnumerable < DrawableHitObject > HitObjects = > drawableRulesetWrapper . Playfield . AllHitObjects ;
public override bool CursorInPlacementArea = > drawableRulesetWrapper . Playfield . ReceivePositionalInputAt ( inputManager . CurrentState . Mouse . Position ) ;
2019-08-29 17:02:50 +08:00
protected abstract IReadOnlyList < HitObjectCompositionTool > CompositionTools { get ; }
2018-10-17 17:01:38 +08:00
2020-01-16 10:54:03 +08:00
protected abstract ComposeBlueprintContainer CreateBlueprintContainer ( ) ;
2019-12-12 14:58:11 +08:00
protected abstract DrawableRuleset < TObject > CreateDrawableRuleset ( Ruleset ruleset , IBeatmap beatmap , IReadOnlyList < Mod > mods = null ) ;
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-10-18 16:56:31 +08:00
if ( distanceSnapGrid ! = null )
2019-10-25 11:34:49 +08:00
hitObject . StartTime = GetSnappedPosition ( distanceSnapGrid . ToLocalSpace ( inputManager . CurrentState . Mouse . Position ) , hitObject . StartTime ) . time ;
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-02-07 17:02:48 +08:00
adjustableClock . Seek ( hitObject . StartTime ) ;
}
2019-11-07 13:08:02 +08:00
2019-10-18 18:04:08 +08:00
showGridFor ( Enumerable . Empty < HitObject > ( ) ) ;
}
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
2019-10-25 11:34:49 +08:00
public override ( Vector2 position , double time ) GetSnappedPosition ( Vector2 position , double time ) = > distanceSnapGrid ? . GetSnappedPosition ( position ) ? ? ( position , time ) ;
public override float GetBeatSnapDistanceAt ( double referenceTime )
{
DifficultyControlPoint difficultyPoint = EditorBeatmap . ControlPointInfo . DifficultyPointAt ( referenceTime ) ;
2020-01-23 13:39:56 +08:00
return ( float ) ( 100 * EditorBeatmap . BeatmapInfo . BaseDifficulty . SliderMultiplier * difficultyPoint . SpeedMultiplier / beatSnapProvider . BeatDivisor ) ;
2019-10-25 11:34:49 +08:00
}
public override float DurationToDistance ( double referenceTime , double duration )
{
2020-01-23 14:31:56 +08:00
double beatLength = beatSnapProvider . GetBeatLengthAtTime ( referenceTime ) ;
2019-10-25 11:34:49 +08:00
return ( float ) ( duration / beatLength * GetBeatSnapDistanceAt ( referenceTime ) ) ;
}
2019-10-16 19:34:02 +08:00
2019-10-25 11:34:49 +08:00
public override double DistanceToDuration ( double referenceTime , float distance )
{
2020-01-23 14:31:56 +08:00
double beatLength = beatSnapProvider . GetBeatLengthAtTime ( referenceTime ) ;
2019-10-25 11:34:49 +08:00
return distance / GetBeatSnapDistanceAt ( referenceTime ) * beatLength ;
}
public override double GetSnappedDurationFromDistance ( double referenceTime , float distance )
2020-01-28 11:48:24 +08:00
= > beatSnapProvider . SnapTime ( referenceTime + DistanceToDuration ( referenceTime , distance ) , referenceTime ) - referenceTime ;
2019-10-25 11:34:49 +08:00
public override float GetSnappedDistanceFromDistance ( double referenceTime , float distance )
2020-02-07 15:43:50 +08:00
{
var snappedEndTime = beatSnapProvider . SnapTime ( referenceTime + DistanceToDuration ( referenceTime , distance ) , referenceTime ) ;
return DurationToDistance ( referenceTime , snappedEndTime - referenceTime ) ;
}
2019-10-24 17:09:20 +08:00
2020-02-05 16:16:15 +08:00
public override void UpdateHitObject ( HitObject hitObject ) = > EditorBeatmap . UpdateHitObject ( hitObject ) ;
2019-08-29 15:06:40 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2019-10-16 19:10:03 +08:00
if ( EditorBeatmap ! = null )
2019-08-29 15:06:40 +08:00
{
2019-10-16 19:10:03 +08:00
EditorBeatmap . HitObjectAdded - = addHitObject ;
EditorBeatmap . HitObjectRemoved - = removeHitObject ;
2019-08-29 15:06:40 +08:00
}
}
2018-10-17 17:01:38 +08:00
}
2019-08-29 17:02:50 +08:00
[Cached(typeof(HitObjectComposer))]
2019-10-25 15:50:21 +08:00
[Cached(typeof(IDistanceSnapProvider))]
public abstract class HitObjectComposer : CompositeDrawable , IDistanceSnapProvider
2019-08-29 17:02:50 +08:00
{
internal HitObjectComposer ( )
{
RelativeSizeAxes = Axes . Both ;
}
/// <summary>
/// All the <see cref="DrawableHitObject"/>s.
/// </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 ; }
2019-10-16 19:20:07 +08:00
/// <summary>
/// Creates the <see cref="DistanceSnapGrid"/> applicable for a <see cref="HitObject"/> selection.
/// </summary>
/// <param name="selectedHitObjects">The <see cref="HitObject"/> selection.</param>
2020-02-07 15:22:59 +08:00
/// <returns>The <see cref="DistanceSnapGrid"/> for <paramref name="selectedHitObjects"/>. If empty, a grid is returned for the current point in time.</returns>
2019-10-16 19:20:07 +08:00
[CanBeNull]
protected virtual DistanceSnapGrid CreateDistanceSnapGrid ( [ NotNull ] IEnumerable < HitObject > selectedHitObjects ) = > null ;
2019-10-16 19:34:02 +08:00
2019-10-31 17:15:19 +08:00
/// <summary>
/// Updates a <see cref="HitObject"/>, invoking <see cref="HitObject.ApplyDefaults"/> and re-processing the beatmap.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to update.</param>
public abstract void UpdateHitObject ( [ CanBeNull ] HitObject hitObject ) ;
2019-10-25 11:34:49 +08:00
public abstract ( Vector2 position , double time ) GetSnappedPosition ( Vector2 position , double time ) ;
2019-10-31 17:15:19 +08:00
2019-10-25 11:34:49 +08:00
public abstract float GetBeatSnapDistanceAt ( double referenceTime ) ;
2019-10-31 17:15:19 +08:00
2019-10-25 11:34:49 +08:00
public abstract float DurationToDistance ( double referenceTime , double duration ) ;
2019-10-31 17:15:19 +08:00
2019-10-25 11:34:49 +08:00
public abstract double DistanceToDuration ( double referenceTime , float distance ) ;
2019-10-31 17:15:19 +08:00
2019-10-25 11:34:49 +08:00
public abstract double GetSnappedDurationFromDistance ( double referenceTime , float distance ) ;
2019-10-31 17:15:19 +08:00
2019-10-25 11:34:49 +08:00
public abstract float GetSnappedDistanceFromDistance ( double referenceTime , float distance ) ;
2019-08-29 17:02:50 +08:00
}
2018-04-13 17:19:50 +08:00
}