From 259ad8ae0f55f7802d252529b0cc80373c5504a5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 23:28:22 -0500 Subject: [PATCH 01/25] Add failing test cases --- .../Editing/TestSceneEditorBeatmapCreation.cs | 131 +++++++++++++++--- 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index db8798781564..b15ee0cab893 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -104,28 +104,12 @@ public void TestAddAudioTrack() { var setup = Editor.ChildrenOfType().First(); - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => { - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - - // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename. Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - return success; - } - finally - { - File.Delete(temp); - Directory.Delete(extractedFolder, true); - } + }); }); AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); @@ -530,5 +514,116 @@ public void TestCreateNewDifficultyForInconvertibleRuleset() return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3); }); } + + [Test] + public void TestMultipleBackgroundFiles() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set background", () => setBackground(expected: "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set background", () => setBackground(expected: "bg.jpg")); + AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + + bool setBackground(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + } + + [Test] + public void TestMultipleAudioFiles() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set audio", () => setAudio(expected: "audio.mp3")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio", () => setAudio(expected: "audio (1).mp3")); + AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set audio", () => setAudio(expected: "audio.mp3")); + AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + + bool setAudio(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); + Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); + return success; + }); + } + } + + private bool setFile(string archivePath, Func func) + { + string temp = archivePath; + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + return func(extractedFolder); + } + finally + { + File.Delete(temp); + Directory.Delete(extractedFolder, true); + } + } } } From 871c365fd8d0ff1525f07462bf2173e3848c7de9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:32:27 -0500 Subject: [PATCH 02/25] Preserve existing beatmap background/audio files if used elsewhere --- .../Screens/Edit/Setup/ResourcesSection.cs | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 845c21b598cd..8ab26a74a2ae 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -77,27 +77,35 @@ public bool ChangeBackgroundImage(FileInfo source) if (!source.Exists) return false; + var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - var destination = new FileInfo($@"bg{source.Extension}"); + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - // remove the previous background for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.GetFile(working.Value.Metadata.BackgroundFile); + string currentFilename = working.Value.Metadata.BackgroundFile; + string? newFilename = null; - using (var stream = source.OpenRead()) - { - if (oldFile != null) - beatmaps.DeleteFile(set, oldFile); + var oldFile = set.GetFile(currentFilename); - beatmaps.AddFile(set, stream, destination.Name); + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; } + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); + + using (var stream = source.OpenRead()) + beatmaps.AddFile(set, stream, newFilename); + + working.Value.Metadata.BackgroundFile = newFilename; + updateAllDifficultiesButton.Enabled.Value = true; + editorBeatmap.SaveState(); - working.Value.Metadata.BackgroundFile = destination.Name; headerBackground.UpdateBackground(); - editor?.ApplyToBackground(bg => bg.RefreshBackground()); return true; @@ -108,23 +116,31 @@ public bool ChangeAudioTrack(FileInfo source) if (!source.Exists) return false; + var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - var destination = new FileInfo($@"audio{source.Extension}"); + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - // remove the previous audio track for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.GetFile(working.Value.Metadata.AudioFile); + string currentFilename = working.Value.Metadata.AudioFile; + string? newFilename = null; - using (var stream = source.OpenRead()) - { - if (oldFile != null) - beatmaps.DeleteFile(set, oldFile); + var oldFile = set.GetFile(currentFilename); - beatmaps.AddFile(set, stream, destination.Name); + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; } - working.Value.Metadata.AudioFile = destination.Name; + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); + + using (var stream = source.OpenRead()) + beatmaps.AddFile(set, stream, newFilename); + + working.Value.Metadata.AudioFile = newFilename; + updateAllDifficultiesButton.Enabled.Value = true; editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From 8e20dc7e9def53de9cc45706fec3e9d77a2c00d4 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:32:50 -0500 Subject: [PATCH 03/25] Add option to update all difficulties with new background/audio file --- .../Screens/Edit/Setup/ResourcesSection.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8ab26a74a2ae..50c7072f84d5 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -1,7 +1,9 @@ // 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.IO; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,6 +12,8 @@ using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; +using osu.Game.Models; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Setup { @@ -36,6 +40,7 @@ public partial class ResourcesSection : SetupSection private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; + private RoundedButton updateAllDifficultiesButton = null!; [BackgroundDependencyLoader] private void load() @@ -58,6 +63,13 @@ private void load() Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, + updateAllDifficultiesButton = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = "Update all difficulties", + Action = updateAllDifficulties, + Enabled = { Value = false }, + } }; backgroundChooser.PreviewContainer.Add(headerBackground); @@ -148,6 +160,41 @@ public bool ChangeAudioTrack(FileInfo source) return true; } + private void updateAllDifficulties() + { + var beatmap = working.Value.BeatmapInfo; + var set = working.Value.BeatmapSetInfo; + + string backgroundFile = working.Value.Metadata.BackgroundFile; + string audioFile = working.Value.Metadata.AudioFile; + + foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) + { + var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); + + if (!string.Equals(otherBeatmap.Metadata.BackgroundFile, backgroundFile, StringComparison.OrdinalIgnoreCase)) + { + if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) + beatmaps.DeleteFile(set, file); + + otherBeatmap.Metadata.BackgroundFile = backgroundFile; + } + + if (!string.Equals(otherBeatmap.Metadata.AudioFile, audioFile, StringComparison.OrdinalIgnoreCase)) + { + if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) + beatmaps.DeleteFile(set, file); + + otherBeatmap.Metadata.AudioFile = audioFile; + } + + beatmaps.Save(otherBeatmap, otherWorking.Beatmap); + } + + editorBeatmap.SaveState(); + updateAllDifficultiesButton.Enabled.Value = false; + } + private void backgroundChanged(ValueChangedEvent file) { if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) From e348b3a7aa929b31116751e665c2b1bf402a3df9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:34:03 -0500 Subject: [PATCH 04/25] Only enable button if there are multiple difficulties --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 50c7072f84d5..5c904f6ce12d 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -113,7 +113,7 @@ public bool ChangeBackgroundImage(FileInfo source) beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.BackgroundFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = true; + updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -152,7 +152,7 @@ public bool ChangeAudioTrack(FileInfo source) beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.AudioFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = true; + updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From dc210d59b5a4b4954cd87194c941857d20637d9b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:45:11 -0500 Subject: [PATCH 05/25] Add test coverage for sync button --- .../Editing/TestSceneEditorBeatmapCreation.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b15ee0cab893..9fabed346b03 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -15,6 +15,8 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -605,6 +607,60 @@ bool setAudio(string expected) } } + [Test] + public void TestUpdateBackgroundOnAllDifficulties() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("button disabled", () => !getButton().Enabled.Value); + AddAssert("set background", () => setBackground(expected: "bg.jpg")); + + // there is only one diff so this should still be disabled. + AddAssert("button still disabled", () => !getButton().Enabled.Value); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("button disabled", () => !getButton().Enabled.Value); + AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("new background added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + AddAssert("button enabled", () => getButton().Enabled.Value); + AddStep("press button", () => getButton().TriggerClick()); + + AddAssert("new difficulty still uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[1].Metadata.BackgroundFile == "bg (1).jpg"); + AddAssert("old difficulty uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[0].Metadata.BackgroundFile == "bg (1).jpg"); + AddAssert("old background removed", () => Beatmap.Value.BeatmapSetInfo.Files.All(f => f.Filename != "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty still uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + bool setBackground(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + + RoundedButton getButton() => Editor.ChildrenOfType().Single(b => b.Text == EditorSetupStrings.ResourcesUpdateAllDifficulties); + } + private bool setFile(string archivePath, Func func) { string temp = archivePath; From c8b13b726dc23feebffa628deab6fa0f2252813a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:45:17 -0500 Subject: [PATCH 06/25] Add localisation support --- osu.Game/Localisation/EditorSetupStrings.cs | 5 +++++ osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index 350517734f7c..60e677757e82 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -188,6 +188,11 @@ public static class EditorSetupStrings /// public static LocalisableString AudioTrack => new TranslatableString(getKey(@"audio_track"), @"Audio Track"); + /// + /// "Update all difficulties" + /// + public static LocalisableString ResourcesUpdateAllDifficulties => new TranslatableString(getKey(@"resources_update_all_difficulties"), @"Update all difficulties"); + /// /// "Click to select a track" /// diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 5c904f6ce12d..aa28e5621835 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -66,7 +66,7 @@ private void load() updateAllDifficultiesButton = new RoundedButton { RelativeSizeAxes = Axes.X, - Text = "Update all difficulties", + Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, Action = updateAllDifficulties, Enabled = { Value = false }, } From a872f749740b8f1bcf755add2cbe0d7298fa489e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:02:34 -0500 Subject: [PATCH 07/25] Make sync button only affect changed resource type --- .../Screens/Edit/Setup/ResourcesSection.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index aa28e5621835..8c9b9796ed76 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -84,6 +84,9 @@ private void load() audioTrackChooser.Current.BindValueChanged(audioTrackChanged); } + private string? newBackgroundFile; + private string? newAudioFile; + public bool ChangeBackgroundImage(FileInfo source) { if (!source.Exists) @@ -112,7 +115,7 @@ public bool ChangeBackgroundImage(FileInfo source) using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - working.Value.Metadata.BackgroundFile = newFilename; + working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -151,7 +154,7 @@ public bool ChangeAudioTrack(FileInfo source) using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - working.Value.Metadata.AudioFile = newFilename; + working.Value.Metadata.AudioFile = newAudioFile = newFilename; updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -165,27 +168,24 @@ private void updateAllDifficulties() var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string backgroundFile = working.Value.Metadata.BackgroundFile; - string audioFile = working.Value.Metadata.AudioFile; - foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) { var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); - if (!string.Equals(otherBeatmap.Metadata.BackgroundFile, backgroundFile, StringComparison.OrdinalIgnoreCase)) + if (newBackgroundFile != null && !string.Equals(otherBeatmap.Metadata.BackgroundFile, newBackgroundFile, StringComparison.OrdinalIgnoreCase)) { if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) beatmaps.DeleteFile(set, file); - otherBeatmap.Metadata.BackgroundFile = backgroundFile; + otherBeatmap.Metadata.BackgroundFile = newBackgroundFile; } - if (!string.Equals(otherBeatmap.Metadata.AudioFile, audioFile, StringComparison.OrdinalIgnoreCase)) + if (newAudioFile != null && !string.Equals(otherBeatmap.Metadata.AudioFile, newAudioFile, StringComparison.OrdinalIgnoreCase)) { if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) beatmaps.DeleteFile(set, file); - otherBeatmap.Metadata.AudioFile = audioFile; + otherBeatmap.Metadata.AudioFile = newAudioFile; } beatmaps.Save(otherBeatmap, otherWorking.Beatmap); From 95a6226413a29f82b64040ebd081939a354d7a06 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:11:28 -0500 Subject: [PATCH 08/25] Only enable button if there are different filenames --- .../Screens/Edit/Setup/ResourcesSection.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8c9b9796ed76..863cf9f24144 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -40,7 +40,7 @@ public partial class ResourcesSection : SetupSection private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; - private RoundedButton updateAllDifficultiesButton = null!; + private RoundedButton syncResourcesButton = null!; [BackgroundDependencyLoader] private void load() @@ -63,11 +63,11 @@ private void load() Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, - updateAllDifficultiesButton = new RoundedButton + syncResourcesButton = new RoundedButton { RelativeSizeAxes = Axes.X, Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, - Action = updateAllDifficulties, + Action = syncResources, Enabled = { Value = false }, } }; @@ -116,7 +116,7 @@ public bool ChangeBackgroundImage(FileInfo source) beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; + syncResourcesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -155,7 +155,7 @@ public bool ChangeAudioTrack(FileInfo source) beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.AudioFile = newAudioFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; + updateSyncResourcesButton(); editorBeatmap.SaveState(); music.ReloadCurrentTrack(); @@ -163,7 +163,16 @@ public bool ChangeAudioTrack(FileInfo source) return true; } - private void updateAllDifficulties() + private void updateSyncResourcesButton() + { + var set = working.Value.BeatmapSetInfo; + + syncResourcesButton.Enabled.Value = + (newBackgroundFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.BackgroundFile, StringComparer.OrdinalIgnoreCase).Count() > 1) || + (newAudioFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.AudioFile, StringComparer.OrdinalIgnoreCase).Count() > 1); + } + + private void syncResources() { var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; @@ -192,7 +201,7 @@ private void updateAllDifficulties() } editorBeatmap.SaveState(); - updateAllDifficultiesButton.Enabled.Value = false; + syncResourcesButton.Enabled.Value = false; } private void backgroundChanged(ValueChangedEvent file) From 3480da22d2dcde78dd23cb20d8131784ac9d5522 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:13:39 -0500 Subject: [PATCH 09/25] Remove no-op `SaveState` call --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 863cf9f24144..a52e42c7c097 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -200,7 +200,6 @@ private void syncResources() beatmaps.Save(otherBeatmap, otherWorking.Beatmap); } - editorBeatmap.SaveState(); syncResourcesButton.Enabled.Value = false; } From 146838555999a69386aebb3d67f6af4bc860e1ba Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 22:38:48 -0500 Subject: [PATCH 10/25] Reset new file states after syncing --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index a52e42c7c097..90603a636664 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -200,6 +200,8 @@ private void syncResources() beatmaps.Save(otherBeatmap, otherWorking.Beatmap); } + newAudioFile = null; + newBackgroundFile = null; syncResourcesButton.Enabled.Value = false; } From b70fb4b0fe747977871c04a49feaad1a9b175654 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:52:43 -0500 Subject: [PATCH 11/25] Add `FormBeatmapFileSelector` for intermediate user-choice step --- .../UserInterfaceV2/FormFileSelector.cs | 30 +++- .../Edit/Setup/FormBeatmapFileSelector.cs | 161 ++++++++++++++++++ 2 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index 81023417a517..5fdf453fc4c2 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -242,20 +242,26 @@ Task ICanAcceptFiles.Import(params string[] paths) Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); + protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) => new FileChooserPopover(handledExtensions, current, chooserPath); + public Popover GetPopover() { - var popover = new FileChooserPopover(handledExtensions, Current, initialChooserPath); + var popover = CreatePopover(handledExtensions, Current, initialChooserPath); popoverState.UnbindBindings(); popoverState.BindTo(popover.State); return popover; } - private partial class FileChooserPopover : OsuPopover + protected partial class FileChooserPopover : OsuPopover { protected override string PopInSampleName => "UI/overlay-big-pop-in"; protected override string PopOutSampleName => "UI/overlay-big-pop-out"; - public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) + private readonly Bindable current = new Bindable(); + + protected OsuFileSelector FileSelector; + + public FileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath) : base(false) { Child = new Container @@ -264,12 +270,13 @@ public FileChooserPopover(string[] handledExtensions, Bindable curren // simplest solution to avoid underlying text to bleed through the bottom border // https://github.com/ppy/osu/pull/30005#issuecomment-2378884430 Padding = new MarginPadding { Bottom = 1 }, - Child = new OsuFileSelector(chooserPath, handledExtensions) + Child = FileSelector = new OsuFileSelector(chooserPath, handledExtensions) { RelativeSizeAxes = Axes.Both, - CurrentFile = { BindTarget = currentFile } }, }; + + this.current.BindTo(current); } [BackgroundDependencyLoader] @@ -292,6 +299,19 @@ private void load(OverlayColourProvider colourProvider) } }); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + FileSelector.CurrentFile.ValueChanged += f => + { + if (f.NewValue != null) + OnFileSelected(f.NewValue); + }; + } + + protected virtual void OnFileSelected(FileInfo file) => current.Value = file; } } } diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs new file mode 100644 index 000000000000..317ed1b90376 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Setup +{ + /// + /// A type of dedicated to beatmap resources. + /// + /// + /// This expands on by adding an intermediate step before finalisation + /// to choose whether the selected file should be applied to the current difficulty or all difficulties in the set, + /// the user's choice is saved in before the file selection is finalised and propagated to . + /// + public partial class FormBeatmapFileSelector : FormFileSelector + { + private readonly bool multipleDifficulties; + + public readonly Bindable ApplyToAllDifficulties = new Bindable(true); + + public FormBeatmapFileSelector(bool multipleDifficulties, params string[] handledExtensions) + : base(handledExtensions) + { + this.multipleDifficulties = multipleDifficulties; + } + + protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) + { + var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, multipleDifficulties); + + popover.ApplyToAllDifficulties.ValueChanged += v => + { + Debug.Assert(v.NewValue != null); + ApplyToAllDifficulties.Value = v.NewValue.Value; + }; + + return popover; + } + + private partial class BeatmapFileChooserPopover : FileChooserPopover + { + private readonly bool multipleDifficulties; + + public readonly Bindable ApplyToAllDifficulties = new Bindable(); + + private Container changeScopeContainer = null!; + + public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool multipleDifficulties) + : base(handledExtensions, current, chooserPath) + { + this.multipleDifficulties = multipleDifficulties; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) + { + Add(changeScopeContainer = new InputBlockingContainer + { + Alpha = 0f, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background6.Opacity(0.9f), + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 10f, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Margin = new MarginPadding(30), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Apply this change to all difficulties?", + Margin = new MarginPadding { Bottom = 20f }, + }, + new RoundedButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300f, + Text = "Apply to all difficulties", + Action = () => ApplyToAllDifficulties.Value = true, + BackgroundColour = colours.Red2, + }, + new RoundedButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300f, + Text = "Only apply to this difficulty", + Action = () => ApplyToAllDifficulties.Value = false, + }, + } + } + } + }, + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + ApplyToAllDifficulties.ValueChanged += onChangeScopeSelected; + } + + protected override void OnFileSelected(FileInfo file) + { + if (multipleDifficulties) + changeScopeContainer.FadeIn(200, Easing.InQuint); + else + base.OnFileSelected(file); + } + + private void onChangeScopeSelected(ValueChangedEvent c) + { + if (c.NewValue == null) + return; + + Debug.Assert(FileSelector.CurrentFile.Value != null); + base.OnFileSelected(FileSelector.CurrentFile.Value); + } + } + } +} From efb68e423268a289898c1a5967d20fe73a58b78d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:53:22 -0500 Subject: [PATCH 12/25] Refactor `ResourcesSection` to support new form of selection --- .../Screens/Edit/Setup/ResourcesSection.cs | 172 ++++++++---------- 1 file changed, 80 insertions(+), 92 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 90603a636664..70282878e0b3 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; using osu.Game.Models; @@ -19,8 +18,8 @@ namespace osu.Game.Screens.Edit.Setup { public partial class ResourcesSection : SetupSection { - private FormFileSelector audioTrackChooser = null!; - private FormFileSelector backgroundChooser = null!; + private FormBeatmapFileSelector audioTrackChooser = null!; + private FormBeatmapFileSelector backgroundChooser = null!; public override LocalisableString Title => EditorSetupStrings.ResourcesHeader; @@ -40,7 +39,6 @@ public partial class ResourcesSection : SetupSection private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; - private RoundedButton syncResourcesButton = null!; [BackgroundDependencyLoader] private void load() @@ -51,25 +49,20 @@ private void load() Height = 110, }; + bool multipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; + Children = new Drawable[] { - backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png") + backgroundChooser = new FormBeatmapFileSelector(multipleDifficulties, ".jpg", ".jpeg", ".png") { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormFileSelector(".mp3", ".ogg") + audioTrackChooser = new FormBeatmapFileSelector(multipleDifficulties, ".mp3", ".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, - syncResourcesButton = new RoundedButton - { - RelativeSizeAxes = Axes.X, - Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, - Action = syncResources, - Enabled = { Value = false }, - } }; backgroundChooser.PreviewContainer.Add(headerBackground); @@ -84,39 +77,56 @@ private void load() audioTrackChooser.Current.BindValueChanged(audioTrackChanged); } - private string? newBackgroundFile; - private string? newAudioFile; - - public bool ChangeBackgroundImage(FileInfo source) + public bool ChangeBackgroundImage(FileInfo source, bool applyToAllDifficulties) { if (!source.Exists) return false; - var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); + if (applyToAllDifficulties) + { + string newFilename = $@"bg{source.Extension}"; - string currentFilename = working.Value.Metadata.BackgroundFile; - string? newFilename = null; + foreach (var beatmapInSet in set.Beatmaps) + { + if (set.GetFile(beatmapInSet.Metadata.BackgroundFile) is RealmNamedFileUsage existingFile) + beatmaps.DeleteFile(set, existingFile); - var oldFile = set.GetFile(currentFilename); + if (beatmapInSet.Metadata.BackgroundFile != newFilename) + { + beatmapInSet.Metadata.BackgroundFile = newFilename; - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) - { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; + if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) + beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); + } + } } + else + { + var beatmap = working.Value.BeatmapInfo; - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, newFilename); + string currentFilename = working.Value.Metadata.BackgroundFile; + + var oldFile = set.GetFile(currentFilename); + string? newFilename = null; + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; + } + + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); + working.Value.Metadata.BackgroundFile = newFilename; + } - working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; - syncResourcesButton.Enabled.Value = set.Beatmaps.Count > 1; + using (var stream = source.OpenRead()) + beatmaps.AddFile(set, stream, working.Value.Metadata.BackgroundFile); editorBeatmap.SaveState(); @@ -126,94 +136,72 @@ public bool ChangeBackgroundImage(FileInfo source) return true; } - public bool ChangeAudioTrack(FileInfo source) + public bool ChangeAudioTrack(FileInfo source, bool applyToAllDifficulties) { if (!source.Exists) return false; - var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.AudioFile; - string? newFilename = null; - - var oldFile = set.GetFile(currentFilename); - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + if (applyToAllDifficulties) { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; - } - - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); - - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, newFilename); + string newFilename = $@"audio{source.Extension}"; - working.Value.Metadata.AudioFile = newAudioFile = newFilename; - updateSyncResourcesButton(); - - editorBeatmap.SaveState(); - music.ReloadCurrentTrack(); + foreach (var beatmapInSet in set.Beatmaps) + { + if (set.GetFile(beatmapInSet.Metadata.AudioFile) is RealmNamedFileUsage existingFile) + beatmaps.DeleteFile(set, existingFile); - return true; - } + if (beatmapInSet.Metadata.AudioFile != newFilename) + { + beatmapInSet.Metadata.AudioFile = newFilename; - private void updateSyncResourcesButton() - { - var set = working.Value.BeatmapSetInfo; + if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) + beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); + } + } + } + else + { + var beatmap = working.Value.BeatmapInfo; - syncResourcesButton.Enabled.Value = - (newBackgroundFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.BackgroundFile, StringComparer.OrdinalIgnoreCase).Count() > 1) || - (newAudioFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.AudioFile, StringComparer.OrdinalIgnoreCase).Count() > 1); - } + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - private void syncResources() - { - var beatmap = working.Value.BeatmapInfo; - var set = working.Value.BeatmapSetInfo; + string currentFilename = working.Value.Metadata.AudioFile; - foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) - { - var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); + var oldFile = set.GetFile(currentFilename); + string? newFilename = null; - if (newBackgroundFile != null && !string.Equals(otherBeatmap.Metadata.BackgroundFile, newBackgroundFile, StringComparison.OrdinalIgnoreCase)) + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) { - if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) - beatmaps.DeleteFile(set, file); - - otherBeatmap.Metadata.BackgroundFile = newBackgroundFile; + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; } - if (newAudioFile != null && !string.Equals(otherBeatmap.Metadata.AudioFile, newAudioFile, StringComparison.OrdinalIgnoreCase)) - { - if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) - beatmaps.DeleteFile(set, file); + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); + working.Value.Metadata.AudioFile = newFilename; + } - otherBeatmap.Metadata.AudioFile = newAudioFile; - } + using (var stream = source.OpenRead()) + beatmaps.AddFile(set, stream, working.Value.Metadata.AudioFile); - beatmaps.Save(otherBeatmap, otherWorking.Beatmap); - } + editorBeatmap.SaveState(); + music.ReloadCurrentTrack(); - newAudioFile = null; - newBackgroundFile = null; - syncResourcesButton.Enabled.Value = false; + return true; } private void backgroundChanged(ValueChangedEvent file) { - if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) + if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue, backgroundChooser.ApplyToAllDifficulties.Value)) backgroundChooser.Current.Value = file.OldValue; } private void audioTrackChanged(ValueChangedEvent file) { - if (file.NewValue == null || !ChangeAudioTrack(file.NewValue)) + if (file.NewValue == null || !ChangeAudioTrack(file.NewValue, audioTrackChooser.ApplyToAllDifficulties.Value)) audioTrackChooser.Current.Value = file.OldValue; } } From 4b8094d0dbc5fd4d9e63511b0f59d62d7e7257d9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:53:30 -0500 Subject: [PATCH 13/25] Update test coverage --- .../Editing/TestSceneEditorBeatmapCreation.cs | 198 ++++++++++-------- .../UserInterface/TestSceneFormControls.cs | 10 +- 2 files changed, 124 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 9fabed346b03..2817225f2b89 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -15,8 +15,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -102,17 +100,7 @@ public void TestAddAudioTrack() AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); - AddAssert("switch track to real track", () => - { - var setup = Editor.ChildrenOfType().First(); - - return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - return success; - }); - }); + AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); AddUntilStep("track length changed", () => Beatmap.Value.Track.Length > 60000); @@ -518,10 +506,10 @@ public void TestCreateNewDifficultyForInconvertibleRuleset() } [Test] - public void TestMultipleBackgroundFiles() + public void TestSingleBackgroundFile() { AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("set background", () => setBackground(expected: "bg.jpg")); + AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); AddStep("save", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); @@ -533,40 +521,42 @@ public void TestMultipleBackgroundFiles() return difficultyName != null && difficultyName == "New Difficulty"; }); - AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty (1)"; + }); + + AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); - AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); - + AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); - AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set background", () => setBackground(expected: "bg.jpg")); - AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - - bool setBackground(string expected) - { - var setup = Editor.ChildrenOfType().First(); + AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg")); + AddStep("save", () => Editor.Save()); - return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); - Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); - return success; - }); - } + AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg")); } [Test] - public void TestMultipleAudioFiles() + public void TestSingleAudioFile() { AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("set audio", () => setAudio(expected: "audio.mp3")); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); AddStep("save", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); @@ -578,44 +568,42 @@ public void TestMultipleAudioFiles() return difficultyName != null && difficultyName == "New Difficulty"; }); - AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty (1)"; + }); + + AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set audio", () => setAudio(expected: "audio (1).mp3")); - AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); - + AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set audio", () => setAudio(expected: "audio.mp3")); - AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); - - bool setAudio(string expected) - { - var setup = Editor.ChildrenOfType().First(); + AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3")); + AddStep("save", () => Editor.Save()); - return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); - return success; - }); - } + AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); + AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3" || f.Filename == "audio (2).mp3")); } [Test] - public void TestUpdateBackgroundOnAllDifficulties() + public void TestMultipleBackgroundFiles() { AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("button disabled", () => !getButton().Enabled.Value); - AddAssert("set background", () => setBackground(expected: "bg.jpg")); - - // there is only one diff so this should still be disabled. - AddAssert("button still disabled", () => !getButton().Enabled.Value); + AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddStep("save", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); @@ -630,35 +618,81 @@ public void TestUpdateBackgroundOnAllDifficulties() AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("button disabled", () => !getButton().Enabled.Value); - AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); - AddAssert("new background added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); - AddAssert("button enabled", () => getButton().Enabled.Value); - AddStep("press button", () => getButton().TriggerClick()); + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); + AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + } + + [Test] + public void TestMultipleAudioFiles() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); - AddAssert("new difficulty still uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[1].Metadata.BackgroundFile == "bg (1).jpg"); - AddAssert("old difficulty uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[0].Metadata.BackgroundFile == "bg (1).jpg"); - AddAssert("old background removed", () => Beatmap.Value.BeatmapSetInfo.Files.All(f => f.Filename != "bg.jpg")); + AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); + AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); AddStep("save", () => Editor.Save()); AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty still uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + } - bool setBackground(string expected) + private bool setBackground(bool applyToAllDifficulties, string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => { - var setup = Editor.ChildrenOfType().First(); + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage( + new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpg")), + applyToAllDifficulties); - return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); - Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); - return success; - }); - } + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + + private bool setAudio(bool applyToAllDifficulties, string expected) + { + var setup = Editor.ChildrenOfType().First(); - RoundedButton getButton() => Editor.ChildrenOfType().Single(b => b.Text == EditorSetupStrings.ResourcesUpdateAllDifficulties); + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeAudioTrack( + new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")), + applyToAllDifficulties); + + Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); + return success; + }); } private bool setFile(string archivePath, Func func) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index c6fd65b9731e..b9ff78b49f31 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Screens.Edit.Setup; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -89,8 +90,13 @@ public TestSceneFormControls() }, new FormFileSelector { - Caption = "Audio file", - PlaceholderText = "Select an audio file", + Caption = "File selector", + PlaceholderText = "Select a file", + }, + new FormBeatmapFileSelector(true) + { + Caption = "File selector with intermediate choice dialog", + PlaceholderText = "Select a file", }, new FormColourPalette { From 238a1ce284ce0b77fda9823c2255f3525fe6c8e9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 15:27:18 -0500 Subject: [PATCH 14/25] Fix tests reliability and improve code Shaved off lots of copypasta so the test actually shows what it's testing. --- .../Editing/TestSceneEditorBeatmapCreation.cs | 141 +++++++----------- 1 file changed, 54 insertions(+), 87 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2817225f2b89..c7d745b6e0df 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -98,7 +98,7 @@ public void TestAddAudioTrack() AddStep("enter compose mode", () => InputManager.Key(Key.F1)); AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); @@ -508,43 +508,21 @@ public void TestCreateNewDifficultyForInconvertibleRuleset() [Test] public void TestSingleBackgroundFile() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); + createNewDifficulty(); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty (1)"; - }); + switchToDifficulty(1); - AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - AddStep("save", () => Editor.Save()); - AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + switchToDifficulty(0); + AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg")); - AddStep("save", () => Editor.Save()); AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg")); @@ -555,43 +533,21 @@ public void TestSingleBackgroundFile() [Test] public void TestSingleAudioFile() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); + createNewDifficulty(); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty (1)"; - }); + switchToDifficulty(1); - AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); - AddStep("save", () => Editor.Save()); - AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + switchToDifficulty(0); + AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3")); - AddStep("save", () => Editor.Save()); AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3")); @@ -602,32 +558,19 @@ public void TestSingleAudioFile() [Test] public void TestMultipleBackgroundFiles() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); - AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + switchToDifficulty(0); + AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); - - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); } @@ -635,34 +578,58 @@ public void TestMultipleBackgroundFiles() [Test] public void TestMultipleAudioFiles() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + createNewDifficulty(); + + AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); + AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + + switchToDifficulty(0); + + AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + } + + private void createNewDifficulty() + { + string? currentDifficulty = null; + AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddStep("create new difficulty", () => + { + currentDifficulty = EditorBeatmap.BeatmapInfo.DifficultyName; + Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo); + }); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); AddUntilStep("wait for created", () => { string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; + return difficultyName != null && difficultyName != currentDifficulty; }); - AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); - AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + } + private void switchToDifficulty(int index) + { AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddStep($"switch to difficulty #{index + 1}", () => + Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(index))); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); - AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); } private bool setBackground(bool applyToAllDifficulties, string expected) From 4d9d5adbf441ecc8286ca4dc512f96063ddf19bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:13:32 +0900 Subject: [PATCH 15/25] Rename parameter to be more clear --- .../Edit/Setup/FormBeatmapFileSelector.cs | 16 ++++++++-------- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 317ed1b90376..ae368a7b7e25 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -27,19 +27,19 @@ namespace osu.Game.Screens.Edit.Setup /// public partial class FormBeatmapFileSelector : FormFileSelector { - private readonly bool multipleDifficulties; + private readonly bool beatmapHasMultipleDifficulties; public readonly Bindable ApplyToAllDifficulties = new Bindable(true); - public FormBeatmapFileSelector(bool multipleDifficulties, params string[] handledExtensions) + public FormBeatmapFileSelector(bool beatmapHasMultipleDifficulties, params string[] handledExtensions) : base(handledExtensions) { - this.multipleDifficulties = multipleDifficulties; + this.beatmapHasMultipleDifficulties = beatmapHasMultipleDifficulties; } protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) { - var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, multipleDifficulties); + var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, beatmapHasMultipleDifficulties); popover.ApplyToAllDifficulties.ValueChanged += v => { @@ -52,16 +52,16 @@ protected override FileChooserPopover CreatePopover(string[] handledExtensions, private partial class BeatmapFileChooserPopover : FileChooserPopover { - private readonly bool multipleDifficulties; + private readonly bool beatmapHasMultipleDifficulties; public readonly Bindable ApplyToAllDifficulties = new Bindable(); private Container changeScopeContainer = null!; - public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool multipleDifficulties) + public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool beatmapHasMultipleDifficulties) : base(handledExtensions, current, chooserPath) { - this.multipleDifficulties = multipleDifficulties; + this.beatmapHasMultipleDifficulties = beatmapHasMultipleDifficulties; } [BackgroundDependencyLoader] @@ -142,7 +142,7 @@ protected override void LoadComplete() protected override void OnFileSelected(FileInfo file) { - if (multipleDifficulties) + if (beatmapHasMultipleDifficulties) changeScopeContainer.FadeIn(200, Easing.InQuint); else base.OnFileSelected(file); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 70282878e0b3..1ce944b5a419 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -49,16 +49,16 @@ private void load() Height = 110, }; - bool multipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; + bool beatmapHasMultipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; Children = new Drawable[] { - backgroundChooser = new FormBeatmapFileSelector(multipleDifficulties, ".jpg", ".jpeg", ".png") + backgroundChooser = new FormBeatmapFileSelector(beatmapHasMultipleDifficulties, ".jpg", ".jpeg", ".png") { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormBeatmapFileSelector(multipleDifficulties, ".mp3", ".ogg") + audioTrackChooser = new FormBeatmapFileSelector(beatmapHasMultipleDifficulties, ".mp3", ".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, From 32b34c1967172bab39c5b2f05975e23dee76cdcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:20:51 +0900 Subject: [PATCH 16/25] Rename container to make more sense --- osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index ae368a7b7e25..6af78f24f88b 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -56,7 +56,7 @@ private partial class BeatmapFileChooserPopover : FileChooserPopover public readonly Bindable ApplyToAllDifficulties = new Bindable(); - private Container changeScopeContainer = null!; + private Container selectApplicationScopeContainer = null!; public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool beatmapHasMultipleDifficulties) : base(handledExtensions, current, chooserPath) @@ -67,7 +67,7 @@ public BeatmapFileChooserPopover(string[] handledExtensions, Bindable [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { - Add(changeScopeContainer = new InputBlockingContainer + Add(selectApplicationScopeContainer = new InputBlockingContainer { Alpha = 0f, RelativeSizeAxes = Axes.Both, @@ -143,7 +143,7 @@ protected override void LoadComplete() protected override void OnFileSelected(FileInfo file) { if (beatmapHasMultipleDifficulties) - changeScopeContainer.FadeIn(200, Easing.InQuint); + selectApplicationScopeContainer.FadeIn(200, Easing.InQuint); else base.OnFileSelected(file); } From 4a1401a33df7c3b489c6c0db05b68f6f1fe31079 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 02:37:27 -0500 Subject: [PATCH 17/25] Rewrite bindable flow to make more sense --- .../Edit/Setup/FormBeatmapFileSelector.cs | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 6af78f24f88b..3e5f0f4306ba 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -40,13 +40,7 @@ public FormBeatmapFileSelector(bool beatmapHasMultipleDifficulties, params strin protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) { var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, beatmapHasMultipleDifficulties); - - popover.ApplyToAllDifficulties.ValueChanged += v => - { - Debug.Assert(v.NewValue != null); - ApplyToAllDifficulties.Value = v.NewValue.Value; - }; - + popover.ApplyToAllDifficulties.BindTo(ApplyToAllDifficulties); return popover; } @@ -54,7 +48,7 @@ private partial class BeatmapFileChooserPopover : FileChooserPopover { private readonly bool beatmapHasMultipleDifficulties; - public readonly Bindable ApplyToAllDifficulties = new Bindable(); + public readonly Bindable ApplyToAllDifficulties = new Bindable(true); private Container selectApplicationScopeContainer = null!; @@ -115,7 +109,11 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) Origin = Anchor.Centre, Width = 300f, Text = "Apply to all difficulties", - Action = () => ApplyToAllDifficulties.Value = true, + Action = () => + { + ApplyToAllDifficulties.Value = true; + updateFileSelection(); + }, BackgroundColour = colours.Red2, }, new RoundedButton @@ -124,7 +122,11 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) Origin = Anchor.Centre, Width = 300f, Text = "Only apply to this difficulty", - Action = () => ApplyToAllDifficulties.Value = false, + Action = () => + { + ApplyToAllDifficulties.Value = false; + updateFileSelection(); + }, }, } } @@ -134,12 +136,6 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) }); } - protected override void LoadComplete() - { - base.LoadComplete(); - ApplyToAllDifficulties.ValueChanged += onChangeScopeSelected; - } - protected override void OnFileSelected(FileInfo file) { if (beatmapHasMultipleDifficulties) @@ -148,11 +144,8 @@ protected override void OnFileSelected(FileInfo file) base.OnFileSelected(file); } - private void onChangeScopeSelected(ValueChangedEvent c) + private void updateFileSelection() { - if (c.NewValue == null) - return; - Debug.Assert(FileSelector.CurrentFile.Value != null); base.OnFileSelected(FileSelector.CurrentFile.Value); } From b1d0939142f62ea2d43401bb7bd4bc0d32191479 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 02:37:31 -0500 Subject: [PATCH 18/25] Add localisation support --- osu.Game/Localisation/EditorSetupStrings.cs | 20 ++++++++++++++----- .../Edit/Setup/FormBeatmapFileSelector.cs | 7 ++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index 60e677757e82..8597b7d9a143 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -188,11 +188,6 @@ public static class EditorSetupStrings /// public static LocalisableString AudioTrack => new TranslatableString(getKey(@"audio_track"), @"Audio Track"); - /// - /// "Update all difficulties" - /// - public static LocalisableString ResourcesUpdateAllDifficulties => new TranslatableString(getKey(@"resources_update_all_difficulties"), @"Update all difficulties"); - /// /// "Click to select a track" /// @@ -203,6 +198,21 @@ public static class EditorSetupStrings /// public static LocalisableString ClickToSelectBackground => new TranslatableString(getKey(@"click_to_select_background"), @"Click to select a background image"); + /// + /// "Apply this change to all difficulties?" + /// + public static LocalisableString ApplicationScopeSelectionTitle => new TranslatableString(getKey(@"application_scope_selection_title"), @"Apply this change to all difficulties?"); + + /// + /// "Apply to all difficulties" + /// + public static LocalisableString ApplyToAllDifficulties => new TranslatableString(getKey(@"apply_to_all_difficulties"), @"Apply to all difficulties"); + + /// + /// "Only apply to this difficulty" + /// + public static LocalisableString ApplyToThisDifficulty => new TranslatableString(getKey(@"apply_to_this_difficulty"), @"Only apply to this difficulty"); + /// /// "Ruleset ({0})" /// diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 3e5f0f4306ba..53287383ec2a 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Screens.Edit.Setup { @@ -100,7 +101,7 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "Apply this change to all difficulties?", + Text = EditorSetupStrings.ApplicationScopeSelectionTitle, Margin = new MarginPadding { Bottom = 20f }, }, new RoundedButton @@ -108,7 +109,7 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300f, - Text = "Apply to all difficulties", + Text = EditorSetupStrings.ApplyToAllDifficulties, Action = () => { ApplyToAllDifficulties.Value = true; @@ -121,7 +122,7 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300f, - Text = "Only apply to this difficulty", + Text = EditorSetupStrings.ApplyToThisDifficulty, Action = () => { ApplyToAllDifficulties.Value = false; From 311f0947e41b44aaf5a08397138a8b3d57bc59d7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:57:47 -0500 Subject: [PATCH 19/25] Abstractify resource change logic and share between background and audio --- .../Screens/Edit/Setup/ResourcesSection.cs | 91 ++++++------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 1ce944b5a419..a02900a2040d 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -82,57 +82,12 @@ public bool ChangeBackgroundImage(FileInfo source, bool applyToAllDifficulties) if (!source.Exists) return false; - var set = working.Value.BeatmapSetInfo; - - if (applyToAllDifficulties) - { - string newFilename = $@"bg{source.Extension}"; - - foreach (var beatmapInSet in set.Beatmaps) - { - if (set.GetFile(beatmapInSet.Metadata.BackgroundFile) is RealmNamedFileUsage existingFile) - beatmaps.DeleteFile(set, existingFile); - - if (beatmapInSet.Metadata.BackgroundFile != newFilename) - { - beatmapInSet.Metadata.BackgroundFile = newFilename; - - if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) - beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); - } - } - } - else - { - var beatmap = working.Value.BeatmapInfo; - - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.BackgroundFile; - - var oldFile = set.GetFile(currentFilename); - string? newFilename = null; - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) - { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; - } - - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); - working.Value.Metadata.BackgroundFile = newFilename; - } - - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, working.Value.Metadata.BackgroundFile); - - editorBeatmap.SaveState(); + changeResource(source, applyToAllDifficulties, @"bg", + metadata => metadata.BackgroundFile, + (metadata, name) => metadata.BackgroundFile = name); headerBackground.UpdateBackground(); editor?.ApplyToBackground(bg => bg.RefreshBackground()); - return true; } @@ -141,20 +96,34 @@ public bool ChangeAudioTrack(FileInfo source, bool applyToAllDifficulties) if (!source.Exists) return false; + changeResource(source, applyToAllDifficulties, @"audio", + metadata => metadata.AudioFile, + (metadata, name) => metadata.AudioFile = name); + + music.ReloadCurrentTrack(); + return true; + } + + private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) + { var set = working.Value.BeatmapSetInfo; + string newFilename = string.Empty; + if (applyToAllDifficulties) { - string newFilename = $@"audio{source.Extension}"; + newFilename = $"{baseFilename}{source.Extension}"; foreach (var beatmapInSet in set.Beatmaps) { - if (set.GetFile(beatmapInSet.Metadata.AudioFile) is RealmNamedFileUsage existingFile) + string filenameInBeatmap = readFilename(beatmapInSet.Metadata); + + if (set.GetFile(filenameInBeatmap) is RealmNamedFileUsage existingFile) beatmaps.DeleteFile(set, existingFile); - if (beatmapInSet.Metadata.AudioFile != newFilename) + if (filenameInBeatmap != newFilename) { - beatmapInSet.Metadata.AudioFile = newFilename; + writeFilename(beatmapInSet.Metadata, newFilename); if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); @@ -166,31 +135,29 @@ public bool ChangeAudioTrack(FileInfo source, bool applyToAllDifficulties) var beatmap = working.Value.BeatmapInfo; string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - string currentFilename = working.Value.Metadata.AudioFile; + string currentFilename = readFilename(working.Value.Metadata); var oldFile = set.GetFile(currentFilename); - string? newFilename = null; - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => readFilename(b.Metadata) != currentFilename)) { beatmaps.DeleteFile(set, oldFile); newFilename = currentFilename; } - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); - working.Value.Metadata.AudioFile = newFilename; + if (string.IsNullOrEmpty(newFilename)) + newFilename = NamingUtils.GetNextBestFilename(filenames, $@"{baseFilename}{source.Extension}"); + + writeFilename(working.Value.Metadata, newFilename); } using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, working.Value.Metadata.AudioFile); + beatmaps.AddFile(set, stream, newFilename); editorBeatmap.SaveState(); - music.ReloadCurrentTrack(); - - return true; } private void backgroundChanged(ValueChangedEvent file) From 489d7a30ec093152cd838cfba9b64c6f235bfe66 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 18:32:03 -0500 Subject: [PATCH 20/25] Perform a single `Save` call rather than doing it in each difficulty --- .../Editing/TestSceneEditorBeatmapCreation.cs | 3 --- .../Screens/Edit/Setup/ResourcesSection.cs | 27 +++++++------------ 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index c7d745b6e0df..7a390ac13173 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -107,9 +107,6 @@ public void TestAddAudioTrack() AddStep("test play", () => Editor.TestGameplay()); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); - AddStep("confirm save", () => InputManager.Key(Key.Number1)); - AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen()); AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index a02900a2040d..4d2bbb035e72 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -32,9 +32,6 @@ public partial class ResourcesSection : SetupSection [Resolved] private IBindable working { get; set; } = null!; - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } = null!; - [Resolved] private Editor? editor { get; set; } @@ -114,25 +111,17 @@ private void changeResource(FileInfo source, bool applyToAllDifficulties, string { newFilename = $"{baseFilename}{source.Extension}"; - foreach (var beatmapInSet in set.Beatmaps) + foreach (var beatmap in set.Beatmaps) { - string filenameInBeatmap = readFilename(beatmapInSet.Metadata); - - if (set.GetFile(filenameInBeatmap) is RealmNamedFileUsage existingFile) - beatmaps.DeleteFile(set, existingFile); - - if (filenameInBeatmap != newFilename) - { - writeFilename(beatmapInSet.Metadata, newFilename); + if (set.GetFile(readFilename(beatmap.Metadata)) is RealmNamedFileUsage otherExistingFile) + beatmaps.DeleteFile(set, otherExistingFile); - if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) - beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); - } + writeFilename(beatmap.Metadata, newFilename); } } else { - var beatmap = working.Value.BeatmapInfo; + var thisBeatmap = working.Value.BeatmapInfo; string[] filenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && @@ -142,7 +131,7 @@ private void changeResource(FileInfo source, bool applyToAllDifficulties, string var oldFile = set.GetFile(currentFilename); - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => readFilename(b.Metadata) != currentFilename)) + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(thisBeatmap)).All(b => readFilename(b.Metadata) != currentFilename)) { beatmaps.DeleteFile(set, oldFile); newFilename = currentFilename; @@ -157,7 +146,9 @@ private void changeResource(FileInfo source, bool applyToAllDifficulties, string using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - editorBeatmap.SaveState(); + // editor change handler cannot be aware of any file changes or other difficulties having their metadata modified. + // for simplicity's sake, trigger a save when changing any resource to ensure the change is correctly saved. + editor?.Save(); } private void backgroundChanged(ValueChangedEvent file) From 06824c1658c714849276f13e614edc084db05536 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:20:09 -0500 Subject: [PATCH 21/25] Add failing test case --- .../Editing/TestSceneEditorBeatmapCreation.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 7a390ac13173..75759edaeade 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -4,15 +4,20 @@ using System; using System.IO; using System.Linq; +using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; @@ -27,6 +32,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Setup; +using osu.Game.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Resources; using osuTK; @@ -527,6 +533,32 @@ public void TestSingleBackgroundFile() AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg")); } + [Test] + public void TestBackgroundFileChangesPreserveOnEncode() + { + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); + AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + + createNewDifficulty(); + createNewDifficulty(); + + switchToDifficulty(0); + + AddAssert("set different background on all diff", () => setBackgroundDifferentExtension(applyToAllDifficulties: true, expected: "bg.jpeg")); + AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpeg")); + AddAssert("all diff encode same background", () => + { + return Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => + { + var files = new RealmFileStore(Realm, Dependencies.Get().Storage); + using var store = new RealmBackedResourceStore(b.BeatmapSet!.ToLive(Realm), files.Store, Realm); + string[] osu = Encoding.UTF8.GetString(store.Get(b.File!.Filename)).Split(Environment.NewLine); + Assert.That(osu, Does.Contain("0,0,\"bg.jpeg\",0,0")); + return true; + }); + }); + } + [Test] public void TestSingleAudioFile() { @@ -644,6 +676,25 @@ private bool setBackground(bool applyToAllDifficulties, string expected) }); } + private bool setBackgroundDifferentExtension(bool applyToAllDifficulties, string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + File.Move( + Path.Combine(extractedFolder, @"machinetop_background.jpg"), + Path.Combine(extractedFolder, @"machinetop_background.jpeg")); + + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage( + new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpeg")), + applyToAllDifficulties); + + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + private bool setAudio(bool applyToAllDifficulties, string expected) { var setup = Editor.ChildrenOfType().First(); From 8e0f6fc12dc04a224a9aefb0121f990a8b007af2 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:36:00 -0500 Subject: [PATCH 22/25] Re-encode difficulties on resource change --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 3 --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 10 ++++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 75759edaeade..157deef80aac 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -8,16 +8,13 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 098877ebe7c0..84107a57e991 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -103,6 +103,7 @@ public bool ChangeAudioTrack(FileInfo source, bool applyToAllDifficulties) private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) { + var thisBeatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; string newFilename = string.Empty; @@ -117,12 +118,17 @@ private void changeResource(FileInfo source, bool applyToAllDifficulties, string beatmaps.DeleteFile(set, otherExistingFile); writeFilename(beatmap.Metadata, newFilename); + + if (!beatmap.Equals(thisBeatmap)) + { + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + var beatmapWorking = beatmaps.GetWorkingBeatmap(beatmap); + beatmaps.Save(beatmap, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); + } } } else { - var thisBeatmap = working.Value.BeatmapInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); From 92dfcae6eba4a545c6f2bdab0fdb6ddb6536ff0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 14:35:09 +0900 Subject: [PATCH 23/25] Adjust bad grammar --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0b5450e5aceb..975f962f7fe0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -51,7 +51,7 @@ public static void Register() } /// - /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. + /// Whether beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. /// public bool ApplyOffsets = true; From 3cac5837547f5ca08baa9f615948d4a4166a4505 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 16:40:47 +0900 Subject: [PATCH 24/25] Rewrite resource changing code to be more legible (to my eye) --- .../Screens/Edit/Setup/ResourcesSection.cs | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 84107a57e991..6cde0e6792a7 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -103,55 +103,64 @@ public bool ChangeAudioTrack(FileInfo source, bool applyToAllDifficulties) private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) { - var thisBeatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; + var beatmap = working.Value.BeatmapInfo; - string newFilename = string.Empty; + var otherBeatmaps = set.Beatmaps.Where(b => !b.Equals(beatmap)); + // First, clean up files which will no longer be used. if (applyToAllDifficulties) { - newFilename = $"{baseFilename}{source.Extension}"; - - foreach (var beatmap in set.Beatmaps) + foreach (var b in set.Beatmaps) { - if (set.GetFile(readFilename(beatmap.Metadata)) is RealmNamedFileUsage otherExistingFile) + if (set.GetFile(readFilename(b.Metadata)) is RealmNamedFileUsage otherExistingFile) beatmaps.DeleteFile(set, otherExistingFile); - - writeFilename(beatmap.Metadata, newFilename); - - if (!beatmap.Equals(thisBeatmap)) - { - // save the difficulty to re-encode the .osu file, updating any reference of the old filename. - var beatmapWorking = beatmaps.GetWorkingBeatmap(beatmap); - beatmaps.Save(beatmap, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); - } } } else { - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = readFilename(working.Value.Metadata); - - var oldFile = set.GetFile(currentFilename); + RealmNamedFileUsage? oldFile = set.GetFile(readFilename(working.Value.Metadata)); - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(thisBeatmap)).All(b => readFilename(b.Metadata) != currentFilename)) + if (oldFile != null) { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; + bool oldFileUsedInOtherDiff = otherBeatmaps + .Any(b => readFilename(b.Metadata) == oldFile.Filename); + if (!oldFileUsedInOtherDiff) + beatmaps.DeleteFile(set, oldFile); } + } - if (string.IsNullOrEmpty(newFilename)) - newFilename = NamingUtils.GetNextBestFilename(filenames, $@"{baseFilename}{source.Extension}"); + // Choose a new filename that doesn't clash with any other existing files. + string newFilename = $"{baseFilename}{source.Extension}"; - writeFilename(working.Value.Metadata, newFilename); + if (set.GetFile(newFilename) != null) + { + string[] existingFilenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); + newFilename = NamingUtils.GetNextBestFilename(existingFilenames, $@"{baseFilename}{source.Extension}"); } using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); + if (applyToAllDifficulties) + { + foreach (var b in otherBeatmaps) + { + if (readFilename(b.Metadata) != newFilename) + { + writeFilename(b.Metadata, newFilename); + + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + var beatmapWorking = beatmaps.GetWorkingBeatmap(b); + beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); + } + } + } + + writeFilename(beatmap.Metadata, newFilename); + // editor change handler cannot be aware of any file changes or other difficulties having their metadata modified. // for simplicity's sake, trigger a save when changing any resource to ensure the change is correctly saved. editor?.Save(); From bbaa542d4a376e490c7e58d794ff49ca7e1bdddb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 16:44:35 +0900 Subject: [PATCH 25/25] Add note about expensive operation --- .../Screens/Edit/Setup/ResourcesSection.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 6cde0e6792a7..7fcd09d7e77e 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -148,14 +148,17 @@ private void changeResource(FileInfo source, bool applyToAllDifficulties, string { foreach (var b in otherBeatmaps) { - if (readFilename(b.Metadata) != newFilename) - { - writeFilename(b.Metadata, newFilename); - - // save the difficulty to re-encode the .osu file, updating any reference of the old filename. - var beatmapWorking = beatmaps.GetWorkingBeatmap(b); - beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); - } + // This operation is quite expensive, so only perform it if required. + if (readFilename(b.Metadata) == newFilename) continue; + + writeFilename(b.Metadata, newFilename); + + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + // + // note that this triggers a full save flow, including triggering a difficulty calculation. + // this is not a cheap operation and should be reconsidered in the future. + var beatmapWorking = beatmaps.GetWorkingBeatmap(b); + beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); } }