// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit { public class OsuHitObjectComposer : HitObjectComposer { public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) { } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new HitCircleCompositionTool(), new SliderCompositionTool(), new SpinnerCompositionTool() }; public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) { case DrawableHitCircle circle: return new HitCircleSelectionBlueprint(circle); case DrawableSlider slider: return new SliderSelectionBlueprint(slider); case DrawableSpinner spinner: return new SpinnerSelectionBlueprint(spinner); } return base.CreateBlueprintFor(hitObject); } protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable selectedHitObjects) { var objects = selectedHitObjects.ToList(); if (objects.Count == 0) return createGrid(h => h.StartTime <= EditorClock.CurrentTime); double minTime = objects.Min(h => h.StartTime); return createGrid(h => h.StartTime < minTime, objects.Count + 1); } /// /// Creates a grid from the last matching a predicate to a target . /// /// A predicate that matches s where the grid can start from. /// Only the last matching the predicate is used. /// An offset from the selected via at which the grid should stop. /// The from a selected to a target . private OsuDistanceSnapGrid createGrid(Func sourceSelector, int targetOffset = 1) { if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset)); int sourceIndex = -1; for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { if (!sourceSelector(EditorBeatmap.HitObjects[i])) break; sourceIndex = i; } if (sourceIndex == -1) return null; OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; int targetIndex = sourceIndex + targetOffset; OsuHitObject targetObject = null; // Keep advancing the target object while its start time falls before the end time of the source object while (true) { if (targetIndex >= EditorBeatmap.HitObjects.Count) break; if (EditorBeatmap.HitObjects[targetIndex].StartTime >= sourceObject.GetEndTime()) { targetObject = EditorBeatmap.HitObjects[targetIndex]; break; } targetIndex++; } return new OsuDistanceSnapGrid(sourceObject, targetObject); } } }