diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs index fdc05e9456..b00582d6f3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSelectionBlueprintDeselection.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests.Editor { @@ -16,13 +18,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestSingleDeleteAtSameTime() { HitCircle? circle1 = null; - HitCircle? circle2 = null; AddStep("add two circles at the same time", () => { - circle1 = new HitCircle(); - circle2 = new HitCircle(); EditorClock.Seek(0); + circle1 = new HitCircle(); + var circle2 = new HitCircle(); EditorBeatmap.Add(circle1); EditorBeatmap.Add(circle2); EditorBeatmap.SelectedHitObjects.Add(circle1); @@ -31,5 +32,33 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("delete the first circle", () => EditorBeatmap.Remove(circle1)); } + + [Test] + public void TestBigStackDeleteAtSameTime() + { + AddStep("add 20 circles at the same time", () => + { + EditorClock.Seek(0); + + for (int i = 0; i < 20; i++) + { + EditorBeatmap.Add(new HitCircle()); + } + }); + + AddStep("select half of the circles", () => + { + foreach (var hitObject in EditorBeatmap.HitObjects.SkipLast(10).Reverse()) + { + EditorBeatmap.SelectedHitObjects.Add(hitObject); + } + }); + + AddStep("delete all selected circles", () => + { + InputManager.PressKey(Key.Delete); + InputManager.ReleaseKey(Key.Delete); + }); + } } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 8aa754b305..95b848fec0 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; namespace osu.Game.Screens.Edit @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Edit private readonly IBeatmapProcessor beatmapProcessor; - private readonly Dictionary> startTimeBindables = new Dictionary>(); + private readonly Dictionary> hitObjectBindables = new Dictionary>(); public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null, BeatmapInfo beatmapInfo = null) { @@ -97,7 +98,7 @@ namespace osu.Game.Screens.Edit beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); foreach (var obj in HitObjects) - trackStartTime(obj); + trackBindables(obj); } /// @@ -222,7 +223,7 @@ namespace osu.Game.Screens.Edit /// The to insert. public void Insert(int index, HitObject hitObject) { - trackStartTime(hitObject); + trackBindables(hitObject); mutableHitObjects.Insert(index, hitObject); @@ -299,9 +300,9 @@ namespace osu.Game.Screens.Edit mutableHitObjects.RemoveAt(index); - var bindable = startTimeBindables[hitObject]; - bindable.UnbindAll(); - startTimeBindables.Remove(hitObject); + var bindables = hitObjectBindables[hitObject]; + bindables.ForEach(b => b.UnbindAll()); + hitObjectBindables.Remove(hitObject); BeginChange(); batchPendingDeletes.Add(hitObject); @@ -325,25 +326,25 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PreProcess(); + foreach (var h in batchPendingUpdates) processHitObject(h); foreach (var h in batchPendingDeletes) processHitObject(h); foreach (var h in batchPendingInserts) processHitObject(h); - foreach (var h in batchPendingUpdates) processHitObject(h); beatmapProcessor?.PostProcess(); // callbacks may modify the lists so let's be safe about it + var updates = batchPendingUpdates.ToArray(); + batchPendingUpdates.Clear(); + var deletes = batchPendingDeletes.ToArray(); batchPendingDeletes.Clear(); var inserts = batchPendingInserts.ToArray(); batchPendingInserts.Clear(); - var updates = batchPendingUpdates.ToArray(); - batchPendingUpdates.Clear(); - + foreach (var h in updates) HitObjectUpdated?.Invoke(h); foreach (var h in deletes) HitObjectRemoved?.Invoke(h); foreach (var h in inserts) HitObjectAdded?.Invoke(h); - foreach (var h in updates) HitObjectUpdated?.Invoke(h); updateInProgress.Value = false; } @@ -355,10 +356,12 @@ namespace osu.Game.Screens.Edit private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, PlayableBeatmap.Difficulty); - private void trackStartTime(HitObject hitObject) + private void trackBindables(HitObject hitObject) { - startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy(); - startTimeBindables[hitObject].ValueChanged += _ => + var bindables = new List(3); + + var startTimeBindable = hitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.ValueChanged += _ => { // For now we'll remove and re-add the hitobject. This is not optimal and can be improved if required. mutableHitObjects.Remove(hitObject); @@ -368,6 +371,20 @@ namespace osu.Game.Screens.Edit Update(hitObject); }; + bindables.Add(startTimeBindable); + + if (hitObject is IHasComboInformation hasCombo) + { + var comboIndexBindable = hasCombo.ComboIndexBindable.GetBoundCopy(); + comboIndexBindable.ValueChanged += _ => Update(hitObject); + bindables.Add(comboIndexBindable); + + var indexInCurrentComboBindable = hasCombo.IndexInCurrentComboBindable.GetBoundCopy(); + indexInCurrentComboBindable.ValueChanged += _ => Update(hitObject); + bindables.Add(indexInCurrentComboBindable); + } + + hitObjectBindables[hitObject] = bindables; } private int findInsertionIndex(IReadOnlyList list, double startTime)