From 8bae00454edbeffdfef9e47a3f5c7ccf87a5f508 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:53:55 +0900 Subject: [PATCH 01/21] Fix slider serialization --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 705e88040f..5aeb23a425 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -112,8 +112,9 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double TickDistanceMultiplier = 1; - public HitCircle HeadCircle; - public SliderTailCircle TailCircle; + public HitCircle HeadCircle { get; protected set; } + + public SliderTailCircle TailCircle { get; protected set; } public Slider() { From 8e028dd88fa5c8ac1feda1128fb403fb22519c88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:54:11 +0900 Subject: [PATCH 02/21] Fix incorrect ordering of ApplyDefaults for newly added objects --- osu.Game/Screens/Edit/EditorBeatmap.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 061009e519..fd5270653d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -79,11 +79,11 @@ namespace osu.Game.Screens.Edit private void updateHitObject([CanBeNull] HitObject hitObject, bool silent) { - scheduledUpdate?.Cancel(); - if (hitObject != null) pendingUpdates.Add(hitObject); + if (scheduledUpdate?.Completed == false) return; + scheduledUpdate = Schedule(() => { beatmapProcessor?.PreProcess(); @@ -158,10 +158,14 @@ namespace osu.Game.Screens.Edit { trackStartTime(hitObject); - mutableHitObjects.Insert(index, hitObject); - - HitObjectAdded?.Invoke(hitObject); updateHitObject(hitObject, true); + + // must occur after the batch-scheduled ApplyDefaults. + Schedule(() => + { + mutableHitObjects.Insert(index, hitObject); + HitObjectAdded?.Invoke(hitObject); + }); } /// From 7d7401123c05a2179bab664c027c5bdba252fd90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:54:20 +0900 Subject: [PATCH 03/21] Add initial implementation of editor clipboard --- osu.Game/Screens/Edit/ClipboardContent.cs | 27 ++++++++++++ osu.Game/Screens/Edit/Editor.cs | 52 ++++++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/ClipboardContent.cs diff --git a/osu.Game/Screens/Edit/ClipboardContent.cs b/osu.Game/Screens/Edit/ClipboardContent.cs new file mode 100644 index 0000000000..b2edbedccc --- /dev/null +++ b/osu.Game/Screens/Edit/ClipboardContent.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.IO.Serialization; +using osu.Game.IO.Serialization.Converters; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public class ClipboardContent : IJsonSerializable + { + [JsonConverter(typeof(TypedListConverter))] + public IList HitObjects; + + public ClipboardContent() + { + } + + public ClipboardContent(EditorBeatmap editorBeatmap) + { + HitObjects = editorBeatmap.SelectedHitObjects.ToList(); + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 23eb704920..a063b0a303 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,6 +23,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.IO.Serialization; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; @@ -131,9 +133,14 @@ namespace osu.Game.Screens.Edit updateLastSavedHash(); EditorMenuBar menuBar; + OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; + EditorMenuItem cutMenuItem; + EditorMenuItem copyMenuItem; + EditorMenuItem pasteMenuItem; + var fileMenuItems = new List { new EditorMenuItem("Save", MenuItemType.Standard, Save) @@ -183,7 +190,11 @@ namespace osu.Game.Screens.Edit Items = new[] { undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo) + redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo), + new EditorMenuItemSpacer(), + cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste), } } } @@ -244,6 +255,17 @@ namespace osu.Game.Screens.Edit changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); + // todo: BindCollectionChanged + editorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => + { + var hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; + + cutMenuItem.Action.Disabled = !hasObjects; + copyMenuItem.Action.Disabled = !hasObjects; + }; + + clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); + menuBar.Mode.ValueChanged += onModeChanged; bottomBackground.Colour = colours.Gray2; @@ -394,6 +416,34 @@ namespace osu.Game.Screens.Edit this.Exit(); } + private readonly Bindable clipboard = new Bindable(); + + protected void Cut() + { + Copy(); + foreach (var h in editorBeatmap.SelectedHitObjects.ToArray()) + editorBeatmap.Remove(h); + } + + protected void Copy() + { + clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); + } + + protected void Paste() + { + if (string.IsNullOrEmpty(clipboard.Value)) + return; + + var objects = clipboard.Value.Deserialize().HitObjects; + double timeOffset = clock.CurrentTime - objects.First().StartTime; + + foreach (var h in objects) + h.StartTime += timeOffset; + + editorBeatmap.AddRange(objects); + } + protected void Undo() => changeHandler.RestoreState(-1); protected void Redo() => changeHandler.RestoreState(1); From de3d8e83e178986573108086bc59ecf7601aa74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 19:55:41 +0900 Subject: [PATCH 04/21] Add keyboard shortcuts --- osu.Game/Screens/Edit/Editor.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a063b0a303..2af319870d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -292,6 +292,18 @@ namespace osu.Game.Screens.Edit { switch (action.ActionType) { + case PlatformActionType.Cut: + Cut(); + return true; + + case PlatformActionType.Copy: + Copy(); + return true; + + case PlatformActionType.Paste: + Paste(); + return true; + case PlatformActionType.Undo: Undo(); return true; From cb14d847deec463a2d236e33f516d74c61f80340 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 22:40:12 +0900 Subject: [PATCH 05/21] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index a2686c380e..6cbb4b2e68 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 48582ae29d..8d23a32c3c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0eed2fa911..d00b174195 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 001cd1194c934d02c326ce55a8da991396e568a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 22:53:03 +0900 Subject: [PATCH 06/21] Consume BindCollectionChanged --- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 896c4f9f61..d6b2b4ba3a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -256,13 +256,13 @@ namespace osu.Game.Screens.Edit changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); // todo: BindCollectionChanged - editorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => + editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => { var hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; cutMenuItem.Action.Disabled = !hasObjects; copyMenuItem.Action.Disabled = !hasObjects; - }; + }, true); clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); From 2d9b0acabe7fd4826e7d791d88c89a348afc0601 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Sep 2020 15:33:13 +0900 Subject: [PATCH 07/21] Fix empty selection via keyboard shortcuts crashing --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d6b2b4ba3a..19bac2a778 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework; using osu.Framework.Allocation; @@ -439,6 +440,9 @@ namespace osu.Game.Screens.Edit protected void Copy() { + if (editorBeatmap.SelectedHitObjects.Count == 0) + return; + clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); } @@ -448,6 +452,9 @@ namespace osu.Game.Screens.Edit return; var objects = clipboard.Value.Deserialize().HitObjects; + + Debug.Assert(objects.Any()); + double timeOffset = clock.CurrentTime - objects.First().StartTime; foreach (var h in objects) From 81f30cd2649a6edd2abc28868e13f492fc95bf1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Sep 2020 20:31:50 +0900 Subject: [PATCH 08/21] Select blueprint if object is already selected at the point of adding --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index b7b222d87b..bf1e18771f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -271,6 +271,9 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected += onBlueprintSelected; blueprint.Deselected += onBlueprintDeselected; + if (beatmap.SelectedHitObjects.Contains(hitObject)) + blueprint.Select(); + SelectionBlueprints.Add(blueprint); } From 3854caae9b0a6df9ba6599960a0a02fade064ae5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Sep 2020 21:20:37 +0900 Subject: [PATCH 09/21] Remove secondary schedule logic --- osu.Game/Screens/Edit/EditorBeatmap.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index fd5270653d..5272530228 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit /// The to update. public void UpdateHitObject([NotNull] HitObject hitObject) => updateHitObject(hitObject, false); - private void updateHitObject([CanBeNull] HitObject hitObject, bool silent) + private void updateHitObject([CanBeNull] HitObject hitObject, bool silent, bool performAdd = false) { if (hitObject != null) pendingUpdates.Add(hitObject); @@ -93,6 +93,12 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PostProcess(); + if (performAdd) + { + foreach (var obj in pendingUpdates) + HitObjectAdded?.Invoke(obj); + } + if (!silent) { foreach (var obj in pendingUpdates) @@ -158,14 +164,8 @@ namespace osu.Game.Screens.Edit { trackStartTime(hitObject); - updateHitObject(hitObject, true); - - // must occur after the batch-scheduled ApplyDefaults. - Schedule(() => - { - mutableHitObjects.Insert(index, hitObject); - HitObjectAdded?.Invoke(hitObject); - }); + mutableHitObjects.Insert(index, hitObject); + updateHitObject(hitObject, true, true); } /// From 1a9f0ac16afbc396728521cc2664509b07695808 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 23:02:23 +0900 Subject: [PATCH 10/21] Select new objects --- osu.Game/Screens/Edit/Editor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 19bac2a778..ee3befe8bd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -460,7 +460,10 @@ namespace osu.Game.Screens.Edit foreach (var h in objects) h.StartTime += timeOffset; + editorBeatmap.SelectedHitObjects.Clear(); + editorBeatmap.AddRange(objects); + editorBeatmap.SelectedHitObjects.AddRange(objects); } protected void Undo() => changeHandler.RestoreState(-1); From c573392bb2db182233f2b45c17aa1c0c565c9c54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Sep 2020 22:31:59 +0900 Subject: [PATCH 11/21] Remove completed todo --- osu.Game/Screens/Edit/Editor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ee3befe8bd..d80f899f90 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -256,7 +256,6 @@ namespace osu.Game.Screens.Edit changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); - // todo: BindCollectionChanged editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => { var hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; From 320e3143565023804e2e5279a6fc8267c49ec87e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Sep 2020 22:53:30 +0900 Subject: [PATCH 12/21] Use minimum start time to handle SelectedHitObjects not being sorted --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d80f899f90..64365bd512 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -454,7 +454,7 @@ namespace osu.Game.Screens.Edit Debug.Assert(objects.Any()); - double timeOffset = clock.CurrentTime - objects.First().StartTime; + double timeOffset = clock.CurrentTime - objects.Min(o => o.StartTime); foreach (var h in objects) h.StartTime += timeOffset; From 3e37f27a66f65d7a7b938fdb8c8bc1c5edc03d76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Sep 2020 23:22:19 +0900 Subject: [PATCH 13/21] Fix regressed tests due to schedule changes --- .../Beatmaps/TestSceneEditorBeatmap.cs | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index b7b48ec06a..902f0d7c23 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -23,15 +23,19 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestHitObjectAddEvent() { - var editorBeatmap = new EditorBeatmap(new OsuBeatmap()); - - HitObject addedObject = null; - editorBeatmap.HitObjectAdded += h => addedObject = h; - var hitCircle = new HitCircle(); - editorBeatmap.Add(hitCircle); - Assert.That(addedObject, Is.EqualTo(hitCircle)); + HitObject addedObject = null; + EditorBeatmap editorBeatmap = null; + + AddStep("add beatmap", () => + { + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap.HitObjectAdded += h => addedObject = h; + }); + + AddStep("add hitobject", () => editorBeatmap.Add(hitCircle)); + AddAssert("received add event", () => addedObject == hitCircle); } /// @@ -41,13 +45,15 @@ namespace osu.Game.Tests.Beatmaps public void HitObjectRemoveEvent() { var hitCircle = new HitCircle(); - var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); - HitObject removedObject = null; - editorBeatmap.HitObjectRemoved += h => removedObject = h; - - editorBeatmap.Remove(hitCircle); - Assert.That(removedObject, Is.EqualTo(hitCircle)); + EditorBeatmap editorBeatmap = null; + AddStep("add beatmap", () => + { + Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + editorBeatmap.HitObjectRemoved += h => removedObject = h; + }); + AddStep("remove hitobject", () => editorBeatmap.Remove(editorBeatmap.HitObjects.First())); + AddAssert("received remove event", () => removedObject == hitCircle); } /// @@ -58,9 +64,7 @@ namespace osu.Game.Tests.Beatmaps public void TestInitialHitObjectStartTimeChangeEvent() { var hitCircle = new HitCircle(); - HitObject changedObject = null; - AddStep("add beatmap", () => { EditorBeatmap editorBeatmap; @@ -68,7 +72,6 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); - AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("received change event", () => changedObject == hitCircle); } @@ -82,18 +85,14 @@ namespace osu.Game.Tests.Beatmaps { EditorBeatmap editorBeatmap = null; HitObject changedObject = null; - AddStep("add beatmap", () => { Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); - var hitCircle = new HitCircle(); - AddStep("add object", () => editorBeatmap.Add(hitCircle)); AddAssert("event not received", () => changedObject == null); - AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("event received", () => changedObject == hitCircle); } @@ -106,13 +105,10 @@ namespace osu.Game.Tests.Beatmaps { var hitCircle = new HitCircle(); var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); - HitObject changedObject = null; editorBeatmap.HitObjectUpdated += h => changedObject = h; - editorBeatmap.Remove(hitCircle); Assert.That(changedObject, Is.Null); - hitCircle.StartTime = 1000; Assert.That(changedObject, Is.Null); } @@ -147,6 +143,7 @@ namespace osu.Game.Tests.Beatmaps public void TestResortWhenStartTimeChanged() { var hitCircle = new HitCircle { StartTime = 1000 }; + var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = @@ -173,7 +170,6 @@ namespace osu.Game.Tests.Beatmaps var updatedObjects = new List(); var allHitObjects = new List(); EditorBeatmap editorBeatmap = null; - AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -187,11 +183,9 @@ namespace osu.Game.Tests.Beatmaps allHitObjects.Add(h); } }); - AddStep("change all start times", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); - for (int i = 0; i < 10; i++) allHitObjects[i].StartTime += 10; }); @@ -208,7 +202,6 @@ namespace osu.Game.Tests.Beatmaps { var updatedObjects = new List(); EditorBeatmap editorBeatmap = null; - AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -216,15 +209,12 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.Add(new HitCircle()); }); - AddStep("change start time twice", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); - editorBeatmap.HitObjects[0].StartTime = 10; editorBeatmap.HitObjects[0].StartTime = 20; }); - AddAssert("only updated once", () => updatedObjects.Count == 1); } } From 692f2c8489751852c0ed717d8f9373f3a34c5ffd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 14:45:49 +0900 Subject: [PATCH 14/21] Simplify debounced update pathway --- osu.Game/Screens/Edit/Editor.cs | 4 + osu.Game/Screens/Edit/EditorBeatmap.cs | 123 ++++++++++++------------- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 64365bd512..71340041f0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -459,10 +459,14 @@ namespace osu.Game.Screens.Edit foreach (var h in objects) h.StartTime += timeOffset; + changeHandler.BeginChange(); + editorBeatmap.SelectedHitObjects.Clear(); editorBeatmap.AddRange(objects); editorBeatmap.SelectedHitObjects.AddRange(objects); + + changeHandler.EndChange(); } protected void Undo() => changeHandler.RestoreState(-1); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 5272530228..3876fb0903 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; @@ -68,47 +67,6 @@ namespace osu.Game.Screens.Edit trackStartTime(obj); } - private readonly HashSet pendingUpdates = new HashSet(); - private ScheduledDelegate scheduledUpdate; - - /// - /// Updates a , invoking and re-processing the beatmap. - /// - /// The to update. - public void UpdateHitObject([NotNull] HitObject hitObject) => updateHitObject(hitObject, false); - - private void updateHitObject([CanBeNull] HitObject hitObject, bool silent, bool performAdd = false) - { - if (hitObject != null) - pendingUpdates.Add(hitObject); - - if (scheduledUpdate?.Completed == false) return; - - scheduledUpdate = Schedule(() => - { - beatmapProcessor?.PreProcess(); - - foreach (var obj in pendingUpdates) - obj.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); - - beatmapProcessor?.PostProcess(); - - if (performAdd) - { - foreach (var obj in pendingUpdates) - HitObjectAdded?.Invoke(obj); - } - - if (!silent) - { - foreach (var obj in pendingUpdates) - HitObjectUpdated?.Invoke(obj); - } - - pendingUpdates.Clear(); - }); - } - public BeatmapInfo BeatmapInfo { get => PlayableBeatmap.BeatmapInfo; @@ -131,6 +89,8 @@ namespace osu.Game.Screens.Edit private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; + private readonly HashSet pendingUpdates = new HashSet(); + /// /// Adds a collection of s to this . /// @@ -165,23 +125,40 @@ namespace osu.Game.Screens.Edit trackStartTime(hitObject); mutableHitObjects.Insert(index, hitObject); - updateHitObject(hitObject, true, true); + + // must be run after any change to hitobject ordering + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); + + HitObjectAdded?.Invoke(hitObject); + } + + /// + /// Updates a , invoking and re-processing the beatmap. + /// + /// The to update. + public void UpdateHitObject([NotNull] HitObject hitObject) + { + pendingUpdates.Add(hitObject); } /// /// Removes a from this . /// - /// The to add. + /// All to remove. /// True if the has been removed, false otherwise. - public bool Remove(HitObject hitObject) + public void Remove(params HitObject[] hitObjects) { - int index = FindIndex(hitObject); + foreach (var h in hitObjects) + { + int index = FindIndex(h); - if (index == -1) - return false; + if (index == -1) + continue; - RemoveAt(index); - return true; + RemoveAt(index); + } } /// @@ -203,11 +180,14 @@ namespace osu.Game.Screens.Edit var bindable = startTimeBindables[hitObject]; bindable.UnbindAll(); - startTimeBindables.Remove(hitObject); - HitObjectRemoved?.Invoke(hitObject); - updateHitObject(null, true); + // must be run after any change to hitobject ordering + beatmapProcessor?.PreProcess(); + processHitObject(hitObject); + beatmapProcessor?.PostProcess(); + + HitObjectRemoved?.Invoke(hitObject); } /// @@ -215,20 +195,35 @@ namespace osu.Game.Screens.Edit /// public void Clear() { - var removed = HitObjects.ToList(); + var removable = HitObjects.ToList(); - mutableHitObjects.Clear(); - - foreach (var b in startTimeBindables) - b.Value.UnbindAll(); - startTimeBindables.Clear(); - - foreach (var h in removed) - HitObjectRemoved?.Invoke(h); - - updateHitObject(null, true); + foreach (var h in removable) + Remove(h); } + protected override void Update() + { + base.Update(); + + // debounce updates as they are common and may come from input events, which can run needlessly many times per update frame. + if (pendingUpdates.Count > 0) + { + beatmapProcessor?.PreProcess(); + + foreach (var hitObject in pendingUpdates) + { + processHitObject(hitObject); + HitObjectUpdated?.Invoke(hitObject); + } + + pendingUpdates.Clear(); + + beatmapProcessor?.PostProcess(); + } + } + + private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); + private void trackStartTime(HitObject hitObject) { startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy(); From da02ee88283a1c9012d4a26dbb6aff5385280671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:26:57 +0900 Subject: [PATCH 15/21] Add ability to create a TestBeatmap with no HitObjects --- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index a375a17bcf..87b77f4616 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -15,14 +15,16 @@ namespace osu.Game.Tests.Beatmaps { public class TestBeatmap : Beatmap { - public TestBeatmap(RulesetInfo ruleset) + public TestBeatmap(RulesetInfo ruleset, bool withHitObjects = true) { var baseBeatmap = CreateBeatmap(); BeatmapInfo = baseBeatmap.BeatmapInfo; ControlPointInfo = baseBeatmap.ControlPointInfo; Breaks = baseBeatmap.Breaks; - HitObjects = baseBeatmap.HitObjects; + + if (withHitObjects) + HitObjects = baseBeatmap.HitObjects; BeatmapInfo.Ruleset = ruleset; BeatmapInfo.RulesetID = ruleset.ID ?? 0; From 0ef4dfc192a6725c9e62c7871bcb749bfdd3bb5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:27:08 +0900 Subject: [PATCH 16/21] Move more logic to base EditorTestScene --- .../Editing/TestSceneEditorChangeStates.cs | 75 ++++++------------- osu.Game/Tests/Visual/EditorTestScene.cs | 27 ++++++- 2 files changed, 49 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index dfe1e434dc..ab53f4fd93 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -1,41 +1,27 @@ // 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.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit; namespace osu.Game.Tests.Visual.Editing { public class TestSceneEditorChangeStates : EditorTestScene { - private EditorBeatmap editorBeatmap; - protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - protected new TestEditor Editor => (TestEditor)base.Editor; - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("get beatmap", () => editorBeatmap = Editor.ChildrenOfType().Single()); - } - [Test] public void TestSelectedObjects() { HitCircle obj = null; - AddStep("add hitobject", () => editorBeatmap.Add(obj = new HitCircle { StartTime = 1000 })); - AddStep("select hitobject", () => editorBeatmap.SelectedHitObjects.Add(obj)); - AddAssert("confirm 1 selected", () => editorBeatmap.SelectedHitObjects.Count == 1); - AddStep("deselect hitobject", () => editorBeatmap.SelectedHitObjects.Remove(obj)); - AddAssert("confirm 0 selected", () => editorBeatmap.SelectedHitObjects.Count == 0); + AddStep("add hitobject", () => EditorBeatmap.Add(obj = new HitCircle { StartTime = 1000 })); + AddStep("select hitobject", () => EditorBeatmap.SelectedHitObjects.Add(obj)); + AddAssert("confirm 1 selected", () => EditorBeatmap.SelectedHitObjects.Count == 1); + AddStep("deselect hitobject", () => EditorBeatmap.SelectedHitObjects.Remove(obj)); + AddAssert("confirm 0 selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); } [Test] @@ -43,11 +29,11 @@ namespace osu.Game.Tests.Visual.Editing { int hitObjectCount = 0; - AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count); + AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count); addUndoSteps(); - AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count); AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } @@ -56,11 +42,11 @@ namespace osu.Game.Tests.Visual.Editing { int hitObjectCount = 0; - AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count); + AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count); addRedoSteps(); - AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count); + AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count); AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges); } @@ -73,11 +59,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); AddAssert("hitobject added", () => addedObject == expectedObject); AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); @@ -95,11 +81,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); addUndoSteps(); AddStep("reset variables", () => @@ -117,7 +103,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestAddObjectThenSaveHasNoUnsavedChanges() { - AddStep("add hitobject", () => editorBeatmap.Add(new HitCircle { StartTime = 1000 })); + AddStep("add hitobject", () => EditorBeatmap.Add(new HitCircle { StartTime = 1000 })); AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); AddStep("save changes", () => Editor.Save()); @@ -133,12 +119,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); - AddStep("remove object", () => editorBeatmap.Remove(expectedObject)); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("remove object", () => EditorBeatmap.Remove(expectedObject)); AddStep("reset variables", () => { addedObject = null; @@ -160,12 +146,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("bind removal", () => { - editorBeatmap.HitObjectAdded += h => addedObject = h; - editorBeatmap.HitObjectRemoved += h => removedObject = h; + EditorBeatmap.HitObjectAdded += h => addedObject = h; + EditorBeatmap.HitObjectRemoved += h => removedObject = h; }); - AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); - AddStep("remove object", () => editorBeatmap.Remove(expectedObject)); + AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 })); + AddStep("remove object", () => EditorBeatmap.Remove(expectedObject)); addUndoSteps(); AddStep("reset variables", () => @@ -183,18 +169,5 @@ namespace osu.Game.Tests.Visual.Editing private void addUndoSteps() => AddStep("undo", () => Editor.Undo()); private void addRedoSteps() => AddStep("redo", () => Editor.Redo()); - - protected override Editor CreateEditor() => new TestEditor(); - - protected class TestEditor : Editor - { - public new void Undo() => base.Undo(); - - public new void Redo() => base.Redo(); - - public new void Save() => base.Save(); - - public new bool HasUnsavedChanges => base.HasUnsavedChanges; - } } } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index cd08f4712a..8f76f247cf 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -14,7 +14,11 @@ namespace osu.Game.Tests.Visual { public abstract class EditorTestScene : ScreenTestScene { - protected Editor Editor { get; private set; } + protected EditorBeatmap EditorBeatmap; + + protected TestEditor Editor { get; private set; } + + protected EditorClock EditorClock { get; private set; } [BackgroundDependencyLoader] private void load() @@ -29,6 +33,8 @@ namespace osu.Game.Tests.Visual AddStep("load editor", () => LoadScreen(Editor = CreateEditor())); AddUntilStep("wait for editor to load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType().Single()); + AddStep("get clock", () => EditorClock = Editor.ChildrenOfType().Single()); } /// @@ -39,6 +45,23 @@ namespace osu.Game.Tests.Visual protected sealed override Ruleset CreateRuleset() => CreateEditorRuleset(); - protected virtual Editor CreateEditor() => new Editor(); + protected virtual TestEditor CreateEditor() => new TestEditor(); + + protected class TestEditor : Editor + { + public new void Undo() => base.Undo(); + + public new void Redo() => base.Redo(); + + public new void Save() => base.Save(); + + public new void Cut() => base.Cut(); + + public new void Copy() => base.Copy(); + + public new void Paste() => base.Paste(); + + public new bool HasUnsavedChanges => base.HasUnsavedChanges; + } } } From 66faae2a6b9634fc8c7ed3502ae88e22032e480e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 15:27:16 +0900 Subject: [PATCH 17/21] Add basic clipboards tests --- .../Editing/TestSceneEditorClipboard.cs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs new file mode 100644 index 0000000000..284127d66b --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -0,0 +1,96 @@ +// 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; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorClipboard : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestCutRemovesObjects() + { + var addedObject = new HitCircle { StartTime = 1000 }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); + } + + [TestCase(1000)] + [TestCase(2000)] + public void TestCutPaste(double newTime) + { + var addedObject = new HitCircle { StartTime = 1000 }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddStep("move forward in time", () => EditorClock.Seek(newTime)); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1); + + AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == newTime); + } + + [Test] + public void TestCopyPaste() + { + var addedObject = new HitCircle { StartTime = 1000 }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("copy hitobject", () => Editor.Copy()); + + AddStep("move forward in time", () => EditorClock.Seek(2000)); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2); + + AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000); + } + + [Test] + public void TestCutNothing() + { + AddStep("cut hitobject", () => Editor.Cut()); + AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0); + } + + [Test] + public void TestCopyNothing() + { + AddStep("copy hitobject", () => Editor.Copy()); + AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0); + } + + [Test] + public void TestPasteNothing() + { + AddStep("paste hitobject", () => Editor.Paste()); + AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0); + } + } +} From 36a234e5d95c283ef1cbeed754eefe4e2bcd9874 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:43:27 +0900 Subject: [PATCH 18/21] Add slider specific clipboard test --- .../Editing/TestSceneEditorClipboard.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 284127d66b..808d471e36 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -5,9 +5,12 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; +using osuTK; namespace osu.Game.Tests.Visual.Editing { @@ -52,6 +55,39 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == newTime); } + [Test] + public void TestCutPasteSlider() + { + var addedObject = new Slider + { + StartTime = 1000, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100, 0), PathType.Bezier) + } + } + }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1); + + AddAssert("path matches", () => + { + var path = ((Slider)EditorBeatmap.HitObjects.Single()).Path; + return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints); + }); + } + [Test] public void TestCopyPaste() { From dafbeda68136ad134f5df3e09e7c93d902717264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:48:29 +0900 Subject: [PATCH 19/21] Add test coverage for spinners too --- .../Editing/TestSceneEditorClipboard.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 808d471e36..29046c82a6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -88,6 +88,28 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestCutPasteSpinner() + { + var addedObject = new Spinner + { + StartTime = 1000, + Duration = 5000 + }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("cut hitobject", () => Editor.Cut()); + + AddStep("paste hitobject", () => Editor.Paste()); + + AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1); + + AddAssert("duration matches", () => ((Spinner)EditorBeatmap.HitObjects.Single()).Duration == 5000); + } + [Test] public void TestCopyPaste() { From 70bc0b2bd025e234880bca5408b3ea6116ef8d7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:52:59 +0900 Subject: [PATCH 20/21] Add back inadvertently removed spacing --- .../Beatmaps/TestSceneEditorBeatmap.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs index 902f0d7c23..bf5b517603 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs @@ -64,7 +64,9 @@ namespace osu.Game.Tests.Beatmaps public void TestInitialHitObjectStartTimeChangeEvent() { var hitCircle = new HitCircle(); + HitObject changedObject = null; + AddStep("add beatmap", () => { EditorBeatmap editorBeatmap; @@ -72,6 +74,7 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); + AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("received change event", () => changedObject == hitCircle); } @@ -85,14 +88,18 @@ namespace osu.Game.Tests.Beatmaps { EditorBeatmap editorBeatmap = null; HitObject changedObject = null; + AddStep("add beatmap", () => { Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.HitObjectUpdated += h => changedObject = h; }); + var hitCircle = new HitCircle(); + AddStep("add object", () => editorBeatmap.Add(hitCircle)); AddAssert("event not received", () => changedObject == null); + AddStep("change start time", () => hitCircle.StartTime = 1000); AddAssert("event received", () => changedObject == hitCircle); } @@ -105,10 +112,13 @@ namespace osu.Game.Tests.Beatmaps { var hitCircle = new HitCircle(); var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } }); + HitObject changedObject = null; editorBeatmap.HitObjectUpdated += h => changedObject = h; + editorBeatmap.Remove(hitCircle); Assert.That(changedObject, Is.Null); + hitCircle.StartTime = 1000; Assert.That(changedObject, Is.Null); } @@ -170,6 +180,7 @@ namespace osu.Game.Tests.Beatmaps var updatedObjects = new List(); var allHitObjects = new List(); EditorBeatmap editorBeatmap = null; + AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -183,9 +194,11 @@ namespace osu.Game.Tests.Beatmaps allHitObjects.Add(h); } }); + AddStep("change all start times", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); + for (int i = 0; i < 10; i++) allHitObjects[i].StartTime += 10; }); @@ -202,6 +215,7 @@ namespace osu.Game.Tests.Beatmaps { var updatedObjects = new List(); EditorBeatmap editorBeatmap = null; + AddStep("add beatmap", () => { updatedObjects.Clear(); @@ -209,12 +223,15 @@ namespace osu.Game.Tests.Beatmaps Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.Add(new HitCircle()); }); + AddStep("change start time twice", () => { editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); + editorBeatmap.HitObjects[0].StartTime = 10; editorBeatmap.HitObjects[0].StartTime = 20; }); + AddAssert("only updated once", () => updatedObjects.Count == 1); } } From daf54c7eb962c6abe35c04202da6c7c9d71e12b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 17:55:41 +0900 Subject: [PATCH 21/21] Revert EditorBeatmap.Remove API --- osu.Game/Screens/Edit/EditorBeatmap.cs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3876fb0903..3a9bd85b0f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -146,19 +146,17 @@ namespace osu.Game.Screens.Edit /// /// Removes a from this . /// - /// All to remove. + /// The to remove. /// True if the has been removed, false otherwise. - public void Remove(params HitObject[] hitObjects) + public bool Remove(HitObject hitObject) { - foreach (var h in hitObjects) - { - int index = FindIndex(h); + int index = FindIndex(hitObject); - if (index == -1) - continue; + if (index == -1) + return false; - RemoveAt(index); - } + RemoveAt(index); + return true; } /// @@ -195,9 +193,7 @@ namespace osu.Game.Screens.Edit /// public void Clear() { - var removable = HitObjects.ToList(); - - foreach (var h in removable) + foreach (var h in HitObjects.ToArray()) Remove(h); }