From 06e5ef88c041c6298e3b2d1e8d64c5f28171d2ac Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 11 Jul 2023 02:30:16 +0200 Subject: [PATCH 001/136] legacy export broken --- osu.Game/Beatmaps/BeatmapManager.cs | 53 ++++++++++++++++++++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 13 +++++ osu.Game/Database/ModelManager.cs | 2 + osu.Game/Localisation/EditorStrings.cs | 5 ++ osu.Game/Rulesets/Objects/BezierConverter.cs | 7 +++ osu.Game/Screens/Edit/Editor.cs | 7 +++ 6 files changed, 87 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 305dc01844..7d8c47ea5a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -18,12 +18,15 @@ using osu.Framework.Platform; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.Extensions; +using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osu.Game.Utils; @@ -400,6 +403,56 @@ namespace osu.Game.Beatmaps public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm)); + /// + /// Creates a copy of the and converts all beatmaps to legacy format, then exports it as a legacy package. + /// + /// + /// + public Task ExportLegacy(BeatmapSetInfo beatmap) + { + var copy = beatmap.Clone(); // does the detach from realm + + // convert all beatmaps to legacy format + foreach (var beatmapInfo in copy.Beatmaps) + { + // Convert beatmap + var file = beatmapInfo.File; + + if (file == null) + continue; + + using var oldStream = new LineBufferedReader(ReadFile(file)); + var beatmapContent = new LegacyBeatmapDecoder().Decode(oldStream); + + foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints) + controlPoint.Time = Math.Floor(controlPoint.Time); + + foreach (var hitObject in beatmapContent.HitObjects) + { + hitObject.StartTime = Math.Floor(hitObject.StartTime); + + if (hitObject is IHasPath hasPath && BezierConverter.CountSegments(hasPath.Path.ControlPoints) > 1) + { + var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); + hasPath.Path.ControlPoints.Clear(); + hasPath.Path.ControlPoints.AddRange(newControlPoints); + } + } + + using var newStream = new MemoryStream(); + using var sw = new StreamWriter(newStream, Encoding.UTF8, 1024, true); + new LegacyBeatmapEncoder(beatmapContent, null).Encode(sw); + newStream.Seek(0, SeekOrigin.Begin); + + beatmapInfo.MD5Hash = newStream.ComputeMD5Hash(); + beatmapInfo.Hash = newStream.ComputeSHA2Hash(); + + AddFile(copy, newStream, file.Filename); + } + + return beatmapExporter.ExportAsync(new RealmLiveUnmanaged(copy)); + } + private void updateHashAndMarkDirty(BeatmapSetInfo setInfo) { setInfo.Hash = beatmapImporter.ComputeHash(setInfo); diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 59e413d935..923c498df8 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -90,6 +90,19 @@ namespace osu.Game.Beatmaps return ID == other.ID; } + public BeatmapSetInfo Clone() + { + var clone = (BeatmapSetInfo)this.Detach().MemberwiseClone(); + + for (int i = 0; i < clone.Beatmaps.Count; i++) + clone.Beatmaps[i] = clone.Beatmaps[i].Clone(); + + for (int i = 0; i < clone.Files.Count; i++) + clone.Files[i] = new RealmNamedFileUsage(clone.Files[i].File, clone.Files[i].Filename); + + return clone; + } + public override string ToString() => Metadata.GetDisplayString(); public bool Equals(IBeatmapSetInfo? other) => other is BeatmapSetInfo b && Equals(b); diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 7d1dc5239a..8dafe0874c 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -33,6 +33,8 @@ namespace osu.Game.Database Realm = realm; } + public Stream ReadFile(RealmNamedFileUsage file) => realmFileStore.Storage.GetStream(file.File.GetStoragePath()); + public void DeleteFile(TModel item, RealmNamedFileUsage file) => performFileOperation(item, managed => DeleteFile(managed, managed.Files.First(f => f.Filename == file.Filename), managed.Realm)); diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 20258b9c35..f1f650cf66 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -39,6 +39,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ExportPackage => new TranslatableString(getKey(@"export_package"), @"Export package"); + /// + /// "Export legacy package" + /// + public static LocalisableString ExportLegacyPackage => new TranslatableString(getKey(@"export_legacy_package"), @"Export legacy package"); + /// /// "Create new difficulty" /// diff --git a/osu.Game/Rulesets/Objects/BezierConverter.cs b/osu.Game/Rulesets/Objects/BezierConverter.cs index ebee36a7db..0c878fa1fd 100644 --- a/osu.Game/Rulesets/Objects/BezierConverter.cs +++ b/osu.Game/Rulesets/Objects/BezierConverter.cs @@ -39,6 +39,13 @@ namespace osu.Game.Rulesets.Objects new[] { new Vector2d(1, 0), new Vector2d(1, 1.2447058f), new Vector2d(-0.8526471f, 2.118367f), new Vector2d(-2.6211002f, 7.854936e-06f), new Vector2d(-0.8526448f, -2.118357f), new Vector2d(1, -1.2447058f), new Vector2d(1, 0) }) }; + /// + /// Counts the number of segments in a slider path. + /// + /// The control points of the path. + /// The number of segments in a slider path. + public static int CountSegments(IList controlPoints) => controlPoints.Where((t, i) => t.Type != null && i < controlPoints.Count - 1).Count(); + /// /// Converts a slider path to bezier control point positions compatible with the legacy osu! client. /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b8fa7f6579..bc5df37570 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -966,6 +966,7 @@ namespace osu.Game.Screens.Edit { new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), new EditorMenuItem(EditorStrings.ExportPackage, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new EditorMenuItem(EditorStrings.ExportLegacyPackage, MenuItemType.Standard, exportLegacyBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, new EditorMenuItemSpacer(), createDifficultyCreationMenu(), createDifficultySwitchMenu(), @@ -981,6 +982,12 @@ namespace osu.Game.Screens.Edit beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } + private void exportLegacyBeatmap() + { + Save(); + beatmapManager.ExportLegacy(Beatmap.Value.BeatmapSetInfo); + } + /// /// Beatmaps of the currently edited set, grouped by ruleset and ordered by difficulty. /// From 2db25722cbf29eb7d93e59df5d3e6b414e4d2dd4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 11 Jul 2023 20:18:54 +0200 Subject: [PATCH 002/136] It works now --- osu.Game/Beatmaps/BeatmapManager.cs | 89 +++++++++++++++++++---------- osu.Game/Beatmaps/BeatmapSetInfo.cs | 13 ----- osu.Game/Database/ModelManager.cs | 10 ++-- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7d8c47ea5a..7c21b87a97 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -29,6 +29,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osu.Game.Utils; +using osuTK; namespace osu.Game.Beatmaps { @@ -406,51 +407,77 @@ namespace osu.Game.Beatmaps /// /// Creates a copy of the and converts all beatmaps to legacy format, then exports it as a legacy package. /// - /// + /// /// - public Task ExportLegacy(BeatmapSetInfo beatmap) + public Task ExportLegacy(BeatmapSetInfo beatmapSetInfo) { - var copy = beatmap.Clone(); // does the detach from realm + // Create a clone of the original beatmap set which we will convert to legacy format and then export + var clone = new BeatmapSetInfo(beatmapSetInfo.Beatmaps.Select(b => b.Clone())); + clone.Files.AddRange(beatmapSetInfo.Files.Select(f => new RealmNamedFileUsage(f.File, f.Filename))); - // convert all beatmaps to legacy format - foreach (var beatmapInfo in copy.Beatmaps) + // convert all beatmaps in the cloned beatmap set to legacy format + foreach (var beatmapInfo in clone.Beatmaps) { - // Convert beatmap - var file = beatmapInfo.File; + beatmapInfo.BeatmapSet = clone; + beatmapInfo.ID = Guid.NewGuid(); + var file = beatmapInfo.File; if (file == null) continue; - using var oldStream = new LineBufferedReader(ReadFile(file)); - var beatmapContent = new LegacyBeatmapDecoder().Decode(oldStream); - - foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints) - controlPoint.Time = Math.Floor(controlPoint.Time); - - foreach (var hitObject in beatmapContent.HitObjects) + var beatmapContent = new LegacyBeatmapDecoder().Decode(new LineBufferedReader(RealmFileStore.Storage.GetStream(file.File.GetStoragePath()))); + var beatmapSkin = new LegacySkin(new SkinInfo(), null!) { - hitObject.StartTime = Math.Floor(hitObject.StartTime); + Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(RealmFileStore.Storage.GetStream(file.File.GetStoragePath()))) + }; - if (hitObject is IHasPath hasPath && BezierConverter.CountSegments(hasPath.Path.ControlPoints) > 1) - { - var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); - hasPath.Path.ControlPoints.Clear(); - hasPath.Path.ControlPoints.AddRange(newControlPoints); - } - } + Realm.Realm.Write(realm => + { + using var stream = new MemoryStream(); + convertAndEncodeLegacyBeatmap(beatmapContent, beatmapSkin, stream); - using var newStream = new MemoryStream(); - using var sw = new StreamWriter(newStream, Encoding.UTF8, 1024, true); - new LegacyBeatmapEncoder(beatmapContent, null).Encode(sw); - newStream.Seek(0, SeekOrigin.Begin); + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + beatmapInfo.Hash = stream.ComputeSHA2Hash(); - beatmapInfo.MD5Hash = newStream.ComputeMD5Hash(); - beatmapInfo.Hash = newStream.ComputeSHA2Hash(); - - AddFile(copy, newStream, file.Filename); + file.File = RealmFileStore.Add(stream, realm).Detach(); + }); } - return beatmapExporter.ExportAsync(new RealmLiveUnmanaged(copy)); + return beatmapExporter.ExportAsync(new RealmLiveUnmanaged(clone)); + } + + private void convertAndEncodeLegacyBeatmap(IBeatmap beatmapContent, ISkin beatmapSkin, Stream stream) + { + // Convert beatmap elements to be compatible with legacy format + // So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves + foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints) + controlPoint.Time = Math.Floor(controlPoint.Time); + + foreach (var hitObject in beatmapContent.HitObjects) + { + hitObject.StartTime = Math.Floor(hitObject.StartTime); + + if (hitObject is not IHasPath hasPath || BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue; + + var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); + + // Truncate control points to integer positions + foreach (var pathControlPoint in newControlPoints) + { + pathControlPoint.Position = new Vector2( + (float)Math.Floor(pathControlPoint.Position.X), + (float)Math.Floor(pathControlPoint.Position.Y)); + } + + hasPath.Path.ControlPoints.Clear(); + hasPath.Path.ControlPoints.AddRange(newControlPoints); + } + + // Encode to legacy format + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); + + stream.Seek(0, SeekOrigin.Begin); } private void updateHashAndMarkDirty(BeatmapSetInfo setInfo) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 923c498df8..59e413d935 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -90,19 +90,6 @@ namespace osu.Game.Beatmaps return ID == other.ID; } - public BeatmapSetInfo Clone() - { - var clone = (BeatmapSetInfo)this.Detach().MemberwiseClone(); - - for (int i = 0; i < clone.Beatmaps.Count; i++) - clone.Beatmaps[i] = clone.Beatmaps[i].Clone(); - - for (int i = 0; i < clone.Files.Count; i++) - clone.Files[i] = new RealmNamedFileUsage(clone.Files[i].File, clone.Files[i].Filename); - - return clone; - } - public override string ToString() => Metadata.GetDisplayString(); public bool Equals(IBeatmapSetInfo? other) => other is BeatmapSetInfo b && Equals(b); diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 8dafe0874c..172a2df5a3 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -25,16 +25,14 @@ namespace osu.Game.Database protected RealmAccess Realm { get; } - private readonly RealmFileStore realmFileStore; + protected readonly RealmFileStore RealmFileStore; public ModelManager(Storage storage, RealmAccess realm) { - realmFileStore = new RealmFileStore(realm, storage); + RealmFileStore = new RealmFileStore(realm, storage); Realm = realm; } - public Stream ReadFile(RealmNamedFileUsage file) => realmFileStore.Storage.GetStream(file.File.GetStoragePath()); - public void DeleteFile(TModel item, RealmNamedFileUsage file) => performFileOperation(item, managed => DeleteFile(managed, managed.Files.First(f => f.Filename == file.Filename), managed.Realm)); @@ -79,7 +77,7 @@ namespace osu.Game.Database /// public void ReplaceFile(RealmNamedFileUsage file, Stream contents, Realm realm) { - file.File = realmFileStore.Add(contents, realm); + file.File = RealmFileStore.Add(contents, realm); } /// @@ -95,7 +93,7 @@ namespace osu.Game.Database return; } - var file = realmFileStore.Add(contents, realm); + var file = RealmFileStore.Add(contents, realm); var namedUsage = new RealmNamedFileUsage(file, filename); item.Files.Add(namedUsage); From b577b6b6ae9ce8e4b047cfed8ba0820c04546db7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 11 Jul 2023 21:04:09 +0200 Subject: [PATCH 003/136] Export legacy converted beatmaps as .osz and non-converted beatmaps as .osz2 --- osu.Game/Beatmaps/BeatmapImporter.cs | 4 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 13 ++++++++++--- osu.Game/Database/BeatmapExporter.cs | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Database/BeatmapExporter.cs diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 7d367ef77d..a2d74d089f 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps /// public class BeatmapImporter : RealmArchiveModelImporter { - public override IEnumerable HandledExtensions => new[] { ".osz" }; + public override IEnumerable HandledExtensions => new[] { ".osz", ".osz2" }; protected override string[] HashableFileTypes => new[] { ".osu" }; @@ -145,7 +145,7 @@ namespace osu.Game.Beatmaps } } - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; + protected override bool ShouldDeleteArchive(string path) => HandledExtensions.Contains(Path.GetExtension(path).ToLowerInvariant()); protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7c21b87a97..ded26f79cd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -44,7 +44,9 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; - private readonly LegacyBeatmapExporter beatmapExporter; + private readonly BeatmapExporter beatmapExporter; + + private readonly LegacyBeatmapExporter legacyBeatmapExporter; public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; } @@ -81,7 +83,12 @@ namespace osu.Game.Beatmaps workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); - beatmapExporter = new LegacyBeatmapExporter(storage) + beatmapExporter = new BeatmapExporter(storage) + { + PostNotification = obj => PostNotification?.Invoke(obj) + }; + + legacyBeatmapExporter = new LegacyBeatmapExporter(storage) { PostNotification = obj => PostNotification?.Invoke(obj) }; @@ -443,7 +450,7 @@ namespace osu.Game.Beatmaps }); } - return beatmapExporter.ExportAsync(new RealmLiveUnmanaged(clone)); + return legacyBeatmapExporter.ExportAsync(new RealmLiveUnmanaged(clone)); } private void convertAndEncodeLegacyBeatmap(IBeatmap beatmapContent, ISkin beatmapSkin, Stream stream) diff --git a/osu.Game/Database/BeatmapExporter.cs b/osu.Game/Database/BeatmapExporter.cs new file mode 100644 index 0000000000..b3a85dd5d9 --- /dev/null +++ b/osu.Game/Database/BeatmapExporter.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Platform; +using osu.Game.Beatmaps; + +namespace osu.Game.Database +{ + public class BeatmapExporter : LegacyArchiveExporter + { + public BeatmapExporter(Storage storage) + : base(storage) + { + } + + protected override string FileExtension => @".osz2"; + } +} From 465cc759f02c84c6c3ae720d715623ac2b7596d6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 12 Jul 2023 14:49:49 +0200 Subject: [PATCH 004/136] Add xmldoc to clarify the purpose of BeatmapExporter --- osu.Game/Database/BeatmapExporter.cs | 4 ++++ osu.Game/Database/LegacyBeatmapExporter.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Database/BeatmapExporter.cs b/osu.Game/Database/BeatmapExporter.cs index b3a85dd5d9..9377065973 100644 --- a/osu.Game/Database/BeatmapExporter.cs +++ b/osu.Game/Database/BeatmapExporter.cs @@ -6,6 +6,10 @@ using osu.Game.Beatmaps; namespace osu.Game.Database { + /// + /// Exporter for beatmap archives. + /// This is not for legacy purposes and works for lazer only. + /// public class BeatmapExporter : LegacyArchiveExporter { public BeatmapExporter(Storage storage) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 4ee8c0636e..62096f862a 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -6,6 +6,9 @@ using osu.Game.Beatmaps; namespace osu.Game.Database { + /// + /// Exporter for osu!stable legacy beatmap archives. + /// public class LegacyBeatmapExporter : LegacyArchiveExporter { public LegacyBeatmapExporter(Storage storage) From 3052c317e10c801e79a45542bcc247ee59379ce5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 12 Jul 2023 15:04:06 +0200 Subject: [PATCH 005/136] change .osz2 to .olz (osu lazer zip) --- osu.Game/Beatmaps/BeatmapImporter.cs | 2 +- osu.Game/Database/BeatmapExporter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index a2d74d089f..e500f87984 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps /// public class BeatmapImporter : RealmArchiveModelImporter { - public override IEnumerable HandledExtensions => new[] { ".osz", ".osz2" }; + public override IEnumerable HandledExtensions => new[] { ".osz", ".olz" }; protected override string[] HashableFileTypes => new[] { ".osu" }; diff --git a/osu.Game/Database/BeatmapExporter.cs b/osu.Game/Database/BeatmapExporter.cs index 9377065973..f37c57dea5 100644 --- a/osu.Game/Database/BeatmapExporter.cs +++ b/osu.Game/Database/BeatmapExporter.cs @@ -17,6 +17,6 @@ namespace osu.Game.Database { } - protected override string FileExtension => @".osz2"; + protected override string FileExtension => @".olz"; } } From 8ca801a2240b6749c35dc98a33f73ba229d080fe Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 12 Jul 2023 15:18:16 +0200 Subject: [PATCH 006/136] dispose the streams --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ded26f79cd..248e97d6ee 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -432,10 +432,15 @@ namespace osu.Game.Beatmaps if (file == null) continue; - var beatmapContent = new LegacyBeatmapDecoder().Decode(new LineBufferedReader(RealmFileStore.Storage.GetStream(file.File.GetStoragePath()))); + using var contentStream = RealmFileStore.Storage.GetStream(file.File.GetStoragePath()); + using var contentStreamReader = new LineBufferedReader(contentStream); + var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader); + + using var skinStream = RealmFileStore.Storage.GetStream(file.File.GetStoragePath()); + using var skinStreamReader = new LineBufferedReader(contentStream); var beatmapSkin = new LegacySkin(new SkinInfo(), null!) { - Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(RealmFileStore.Storage.GetStream(file.File.GetStoragePath()))) + Configuration = new LegacySkinDecoder().Decode(skinStreamReader) }; Realm.Realm.Write(realm => From 1d837a8725308a7f6e83ced3713a8796ad464930 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 13 Jul 2023 00:20:01 +0200 Subject: [PATCH 007/136] Move all conversion code to LegacyBeatmapExporter --- osu.Game/Beatmaps/BeatmapManager.cs | 85 +--------------------- osu.Game/Database/LegacyArchiveExporter.cs | 4 +- osu.Game/Database/LegacyBeatmapExporter.cs | 70 ++++++++++++++++++ osu.Game/Database/ModelManager.cs | 8 +- 4 files changed, 78 insertions(+), 89 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 248e97d6ee..ffdff13845 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -18,18 +18,14 @@ using osu.Framework.Platform; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.Extensions; -using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osu.Game.Utils; -using osuTK; namespace osu.Game.Beatmaps { @@ -411,86 +407,7 @@ namespace osu.Game.Beatmaps public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm)); - /// - /// Creates a copy of the and converts all beatmaps to legacy format, then exports it as a legacy package. - /// - /// - /// - public Task ExportLegacy(BeatmapSetInfo beatmapSetInfo) - { - // Create a clone of the original beatmap set which we will convert to legacy format and then export - var clone = new BeatmapSetInfo(beatmapSetInfo.Beatmaps.Select(b => b.Clone())); - clone.Files.AddRange(beatmapSetInfo.Files.Select(f => new RealmNamedFileUsage(f.File, f.Filename))); - - // convert all beatmaps in the cloned beatmap set to legacy format - foreach (var beatmapInfo in clone.Beatmaps) - { - beatmapInfo.BeatmapSet = clone; - beatmapInfo.ID = Guid.NewGuid(); - - var file = beatmapInfo.File; - if (file == null) - continue; - - using var contentStream = RealmFileStore.Storage.GetStream(file.File.GetStoragePath()); - using var contentStreamReader = new LineBufferedReader(contentStream); - var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader); - - using var skinStream = RealmFileStore.Storage.GetStream(file.File.GetStoragePath()); - using var skinStreamReader = new LineBufferedReader(contentStream); - var beatmapSkin = new LegacySkin(new SkinInfo(), null!) - { - Configuration = new LegacySkinDecoder().Decode(skinStreamReader) - }; - - Realm.Realm.Write(realm => - { - using var stream = new MemoryStream(); - convertAndEncodeLegacyBeatmap(beatmapContent, beatmapSkin, stream); - - beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); - beatmapInfo.Hash = stream.ComputeSHA2Hash(); - - file.File = RealmFileStore.Add(stream, realm).Detach(); - }); - } - - return legacyBeatmapExporter.ExportAsync(new RealmLiveUnmanaged(clone)); - } - - private void convertAndEncodeLegacyBeatmap(IBeatmap beatmapContent, ISkin beatmapSkin, Stream stream) - { - // Convert beatmap elements to be compatible with legacy format - // So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves - foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints) - controlPoint.Time = Math.Floor(controlPoint.Time); - - foreach (var hitObject in beatmapContent.HitObjects) - { - hitObject.StartTime = Math.Floor(hitObject.StartTime); - - if (hitObject is not IHasPath hasPath || BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue; - - var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); - - // Truncate control points to integer positions - foreach (var pathControlPoint in newControlPoints) - { - pathControlPoint.Position = new Vector2( - (float)Math.Floor(pathControlPoint.Position.X), - (float)Math.Floor(pathControlPoint.Position.Y)); - } - - hasPath.Path.ControlPoints.Clear(); - hasPath.Path.ControlPoints.AddRange(newControlPoints); - } - - // Encode to legacy format - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); - - stream.Seek(0, SeekOrigin.Begin); - } + public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm)); private void updateHashAndMarkDirty(BeatmapSetInfo setInfo) { diff --git a/osu.Game/Database/LegacyArchiveExporter.cs b/osu.Game/Database/LegacyArchiveExporter.cs index 7689ffc13d..9805207591 100644 --- a/osu.Game/Database/LegacyArchiveExporter.cs +++ b/osu.Game/Database/LegacyArchiveExporter.cs @@ -39,7 +39,7 @@ namespace osu.Game.Database { cancellationToken.ThrowIfCancellationRequested(); - using (var stream = UserFileStorage.GetStream(file.File.GetStoragePath())) + using (var stream = GetFileContents(model, file)) { if (stream == null) { @@ -65,5 +65,7 @@ namespace osu.Game.Database } } } + + protected virtual Stream? GetFileContents(TModel model, INamedFileUsage file) => UserFileStorage.GetStream(file.File.GetStoragePath()); } } diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 62096f862a..42d8a72073 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -1,13 +1,25 @@ // 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 System.Text; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.Extensions; +using osu.Game.IO; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using osuTK; namespace osu.Game.Database { /// /// Exporter for osu!stable legacy beatmap archives. + /// Converts all beatmaps in the set to legacy format and exports it as a legacy package. /// public class LegacyBeatmapExporter : LegacyArchiveExporter { @@ -16,6 +28,64 @@ namespace osu.Game.Database { } + protected override Stream? GetFileContents(BeatmapSetInfo model, INamedFileUsage file) + { + bool isBeatmap = model.Beatmaps.Any(o => o.Hash == file.File.Hash); + + if (!isBeatmap) + return base.GetFileContents(model, file); + + // Read the beatmap contents and skin + using var contentStream = UserFileStorage.GetStream(file.File.GetStoragePath()); + + if (contentStream == null) + return null; + + using var contentStreamReader = new LineBufferedReader(contentStream); + var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader); + + using var skinStream = UserFileStorage.GetStream(file.File.GetStoragePath()); + using var skinStreamReader = new LineBufferedReader(contentStream); + var beatmapSkin = new LegacySkin(new SkinInfo(), null!) + { + Configuration = new LegacySkinDecoder().Decode(skinStreamReader) + }; + + // Convert beatmap elements to be compatible with legacy format + // So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves + foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints) + controlPoint.Time = Math.Floor(controlPoint.Time); + + foreach (var hitObject in beatmapContent.HitObjects) + { + hitObject.StartTime = Math.Floor(hitObject.StartTime); + + if (hitObject is not IHasPath hasPath || BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue; + + var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); + + // Truncate control points to integer positions + foreach (var pathControlPoint in newControlPoints) + { + pathControlPoint.Position = new Vector2( + (float)Math.Floor(pathControlPoint.Position.X), + (float)Math.Floor(pathControlPoint.Position.Y)); + } + + hasPath.Path.ControlPoints.Clear(); + hasPath.Path.ControlPoints.AddRange(newControlPoints); + } + + // Encode to legacy format + var stream = new MemoryStream(); + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); + + stream.Seek(0, SeekOrigin.Begin); + + return stream; + } + protected override string FileExtension => @".osz"; } } diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 172a2df5a3..7d1dc5239a 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -25,11 +25,11 @@ namespace osu.Game.Database protected RealmAccess Realm { get; } - protected readonly RealmFileStore RealmFileStore; + private readonly RealmFileStore realmFileStore; public ModelManager(Storage storage, RealmAccess realm) { - RealmFileStore = new RealmFileStore(realm, storage); + realmFileStore = new RealmFileStore(realm, storage); Realm = realm; } @@ -77,7 +77,7 @@ namespace osu.Game.Database /// public void ReplaceFile(RealmNamedFileUsage file, Stream contents, Realm realm) { - file.File = RealmFileStore.Add(contents, realm); + file.File = realmFileStore.Add(contents, realm); } /// @@ -93,7 +93,7 @@ namespace osu.Game.Database return; } - var file = RealmFileStore.Add(contents, realm); + var file = realmFileStore.Add(contents, realm); var namedUsage = new RealmNamedFileUsage(file, filename); item.Files.Add(namedUsage); From dd8774a64006792556b357d78756438324a34b37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 13:21:28 +0900 Subject: [PATCH 008/136] Vertically centre the editor osu! playfield --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index aac5f6ffb1..8d93613156 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Osu.Edit PlayfieldContentContainer.Padding = new MarginPadding { Vertical = 10, - Left = TOOLBOX_CONTRACTED_SIZE_LEFT + 10, - Right = TOOLBOX_CONTRACTED_SIZE_RIGHT + 10, + // Intentionally use the left toolbox size for both sides to vertically centre the playfield. + Horizontal = TOOLBOX_CONTRACTED_SIZE_LEFT + 10, }; LayerBelowRuleset.AddRange(new Drawable[] From 56acc9e3dde3f161c79f7fafc312d9800e0fc420 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 14:02:48 +0900 Subject: [PATCH 009/136] Change `BeatDivisorControl` to retrive bindable divisor via DI --- .../Visual/Editing/TestSceneBeatDivisorControl.cs | 9 +++++++-- .../Visual/Editing/TestSceneTimelineTickDisplay.cs | 2 +- .../Edit/Compose/Components/BeatDivisorControl.cs | 8 ++------ osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 10 ++-------- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index 88b959a2a0..f2b3351533 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -22,7 +22,6 @@ namespace osu.Game.Tests.Visual.Editing public partial class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene { private BeatDivisorControl beatDivisorControl = null!; - private BindableBeatDivisor bindableBeatDivisor = null!; private SliderBar tickSliderBar => beatDivisorControl.ChildrenOfType>().Single(); private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType().Single(); @@ -30,13 +29,19 @@ namespace osu.Game.Tests.Visual.Editing [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Cached] + private readonly BindableBeatDivisor bindableBeatDivisor = new BindableBeatDivisor(16); + [SetUp] public void SetUp() => Schedule(() => { + bindableBeatDivisor.ValidDivisors.SetDefault(); + bindableBeatDivisor.SetDefault(); + Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = beatDivisorControl = new BeatDivisorControl(bindableBeatDivisor = new BindableBeatDivisor(16)) + Child = beatDivisorControl = new BeatDivisorControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs index 1376ba23fb..18bd6d840a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing { BeatDivisor.Value = 4; - Add(new BeatDivisorControl(BeatDivisor) + Add(new BeatDivisorControl { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index f7159f8670..59b0bd1785 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -33,12 +33,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { private int? lastCustomDivisor; - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - - public BeatDivisorControl(BindableBeatDivisor beatDivisor) - { - this.beatDivisor.BindTo(beatDivisor); - } + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index b8cbff047e..0c5f28c2d6 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -3,7 +3,6 @@ #nullable disable -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,8 +18,6 @@ namespace osu.Game.Screens.Edit { private const float padding = 10; - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private Container timelineContainer; protected EditorScreenWithTimeline(EditorScreenMode type) @@ -33,11 +30,8 @@ namespace osu.Game.Screens.Edit private LoadingSpinner spinner; [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider colourProvider, [CanBeNull] BindableBeatDivisor beatDivisor) + private void load(OverlayColourProvider colourProvider) { - if (beatDivisor != null) - this.beatDivisor.BindTo(beatDivisor); - Child = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -82,7 +76,7 @@ namespace osu.Game.Screens.Edit AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Right = 5 }, }, - new BeatDivisorControl(this.beatDivisor) { RelativeSizeAxes = Axes.Both } + new BeatDivisorControl { RelativeSizeAxes = Axes.Both } }, }, RowDimensions = new[] From ebaf63b7644425dd06f9e1ba00e8159070813ccf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 14:04:12 +0900 Subject: [PATCH 010/136] Apply NRT to timeline related classes --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 8 +++----- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 12 +++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 0b83258f8b..02c773fd4b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,16 +16,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class TimelineArea : CompositeDrawable { - public Timeline Timeline; + public Timeline Timeline = null!; private readonly Drawable userContent; - public TimelineArea(Drawable content = null) + public TimelineArea(Drawable? content = null) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - userContent = content ?? Drawable.Empty(); + userContent = content ?? Empty(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 0c5f28c2d6..bd57ea42f0 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,17 +16,17 @@ namespace osu.Game.Screens.Edit { private const float padding = 10; - private Container timelineContainer; + private Container timelineContainer = null!; + + private Container mainContent = null!; + + private LoadingSpinner spinner = null!; protected EditorScreenWithTimeline(EditorScreenMode type) : base(type) { } - private Container mainContent; - - private LoadingSpinner spinner; - [BackgroundDependencyLoader(true)] private void load(OverlayColourProvider colourProvider) { From 5b2e70426488a295df83f83345326d7181b83568 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 14:19:04 +0900 Subject: [PATCH 011/136] Move beat divisor control inside of `TimelineArea` and adjust metrics to match design --- .../Components/Timeline/TimelineArea.cs | 44 +++++++++---------- .../Screens/Edit/EditorScreenWithTimeline.cs | 5 ++- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 02c773fd4b..3969a002db 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -48,14 +48,24 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 140), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 110), + }, Content = new[] { new Drawable[] { new Container { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, Name = @"Toggle controls", Children = new Drawable[] { @@ -92,31 +102,31 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, + Timeline = new Timeline(userContent), new Container { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, Name = @"Zoom controls", + Padding = new MarginPadding { Right = 5 }, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3, + Colour = colourProvider.Background2, }, new Container { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, Masking = true, Children = new[] { new TimelineButton { - RelativeSizeAxes = Axes.Y, - Height = 0.5f, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1, 0.5f), Icon = FontAwesome.Solid.SearchPlus, Action = () => Timeline.AdjustZoomRelatively(1) }, @@ -124,8 +134,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Y, - Height = 0.5f, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1, 0.5f), Icon = FontAwesome.Solid.SearchMinus, Action = () => Timeline.AdjustZoomRelatively(-1) }, @@ -133,19 +143,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, - Timeline = new Timeline(userContent), + new BeatDivisorControl { RelativeSizeAxes = Axes.Both } }, }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } } }; diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index bd57ea42f0..a2367dd66c 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit @@ -30,6 +29,9 @@ namespace osu.Game.Screens.Edit [BackgroundDependencyLoader(true)] private void load(OverlayColourProvider colourProvider) { + // Grid with only two rows. + // First is the timeline area, which should be allowed to expand as required. + // Second is the main editor content, including the playfield and side toolbars (but not the bottom). Child = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -74,7 +76,6 @@ namespace osu.Game.Screens.Edit AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Right = 5 }, }, - new BeatDivisorControl { RelativeSizeAxes = Axes.Both } }, }, RowDimensions = new[] From 1dc293ed619adc285ac5554b4ea6a65e04049f77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 14:24:53 +0900 Subject: [PATCH 012/136] Allow specifying a custom width for nubs in `OsuCheckbox`es --- osu.Game/Graphics/UserInterface/Nub.cs | 6 +++--- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 8 ++++---- osu.Game/Graphics/UserInterface/RoundedSliderBar.cs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 28a2eb40c3..4b953718bc 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -20,16 +20,16 @@ namespace osu.Game.Graphics.UserInterface { public const float HEIGHT = 15; - public const float EXPANDED_SIZE = 50; + public const float DEFAULT_EXPANDED_SIZE = 50; private const float border_width = 3; private readonly Box fill; private readonly Container main; - public Nub() + public Nub(float expandedSize = DEFAULT_EXPANDED_SIZE) { - Size = new Vector2(EXPANDED_SIZE, HEIGHT); + Size = new Vector2(expandedSize, HEIGHT); InternalChildren = new[] { diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 160105af1a..b7b405a7e8 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -47,7 +47,7 @@ namespace osu.Game.Graphics.UserInterface private Sample sampleChecked; private Sample sampleUnchecked; - public OsuCheckbox(bool nubOnRight = true) + public OsuCheckbox(bool nubOnRight = true, float nubSize = Nub.DEFAULT_EXPANDED_SIZE) { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -61,7 +61,7 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, }, - Nub = new Nub(), + Nub = new Nub(nubSize), new HoverSounds() }; @@ -70,14 +70,14 @@ namespace osu.Game.Graphics.UserInterface Nub.Anchor = Anchor.CentreRight; Nub.Origin = Anchor.CentreRight; Nub.Margin = new MarginPadding { Right = nub_padding }; - LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding * 2 }; + LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 }; } else { Nub.Anchor = Anchor.CentreLeft; Nub.Origin = Anchor.CentreLeft; Nub.Margin = new MarginPadding { Left = nub_padding }; - LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 }; + LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 }; } Nub.Current.BindTo(Current); diff --git a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs index a666b83c05..56af23ff10 100644 --- a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs @@ -51,7 +51,7 @@ namespace osu.Game.Graphics.UserInterface public RoundedSliderBar() { Height = Nub.HEIGHT; - RangePadding = Nub.EXPANDED_SIZE / 2; + RangePadding = Nub.DEFAULT_EXPANDED_SIZE / 2; Children = new Drawable[] { new Container From 01750dd0914e13b6ce4ee1dda8c878639b7d9972 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 14:24:40 +0900 Subject: [PATCH 013/136] Update metrics of checkboxes and backgrounds to match design better --- .../Components/Timeline/TimelineArea.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 3969a002db..70322bd3e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -39,11 +39,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline InternalChildren = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5 - }, new GridContainer { RelativeSizeAxes = Axes.X, @@ -76,24 +71,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, new FillFlowContainer { - AutoSizeAxes = Axes.Y, - Width = 160, + RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(10), Direction = FillDirection.Vertical, Spacing = new Vector2(0, 4), Children = new[] { - waveformCheckbox = new OsuCheckbox + waveformCheckbox = new OsuCheckbox(nubSize: 30f) { LabelText = EditorStrings.TimelineWaveform, Current = { Value = true }, }, - ticksCheckbox = new OsuCheckbox + ticksCheckbox = new OsuCheckbox(nubSize: 30f) { LabelText = EditorStrings.TimelineTicks, Current = { Value = true }, }, - controlPointsCheckbox = new OsuCheckbox + controlPointsCheckbox = new OsuCheckbox(nubSize: 30f) { LabelText = BeatmapsetsStrings.ShowStatsBpm, Current = { Value = true }, @@ -102,7 +96,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, - Timeline = new Timeline(userContent), + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + Colour = colourProvider.Background5 + }, + Timeline = new Timeline(userContent), + } + }, new Container { RelativeSizeAxes = Axes.Both, From 6b222cfafdedf6bb7299e189f30b533aa83b659f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 14:29:13 +0900 Subject: [PATCH 014/136] Fix slight misalignment so timeline is now completely centered --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index a2367dd66c..104d9dd58f 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit { public abstract partial class EditorScreenWithTimeline : EditorScreen { - private const float padding = 10; + private const float padding = 15; private Container timelineContainer = null!; @@ -74,7 +74,6 @@ namespace osu.Game.Screens.Edit { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 5 }, }, }, }, From 00e9746174b842bcec7cb6ea2c08bd2e55932580 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 15:17:24 +0900 Subject: [PATCH 015/136] Implement longer design for timing point piece --- .../Timeline/TimelineControlPointGroup.cs | 2 +- .../Components/Timeline/TopPointPiece.cs | 64 ++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index fc3ef92bf5..c1b6069523 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; - Origin = Anchor.TopCentre; + Origin = Anchor.TopLeft; X = (float)group.Time; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs index 69fb001a66..243cdc6ddd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,24 +8,25 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class TopPointPiece : CompositeDrawable { - private readonly ControlPoint point; + protected readonly ControlPoint Point; - protected OsuSpriteText Label { get; private set; } + protected OsuSpriteText Label { get; private set; } = null!; + + private const float width = 80; public TopPointPiece(ControlPoint point) { - this.point = point; - AutoSizeAxes = Axes.X; + Point = point; + Width = width; Height = 16; - Margin = new MarginPadding(4); - - Masking = true; - CornerRadius = Height / 2; + Margin = new MarginPadding { Vertical = 4 }; Origin = Anchor.TopCentre; Anchor = Anchor.TopCentre; @@ -36,17 +35,52 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load(OsuColour colours) { + const float corner_radius = 4; + const float arrow_extension = 3; + const float triangle_portion = 15; + InternalChildren = new Drawable[] { - new Box + // This is a triangle, trust me. + // Doing it this way looks okay. Doing it using Triangle primitive is basically impossible. + new Container { - Colour = point.GetRepresentingColour(colours), - RelativeSizeAxes = Axes.Both, + Colour = Point.GetRepresentingColour(colours), + X = -corner_radius, + Size = new Vector2(triangle_portion * arrow_extension, Height), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Masking = true, + CornerRadius = Height, + CornerExponent = 1.4f, + Children = new Drawable[] + { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + } + }, + new Container + { + RelativeSizeAxes = Axes.Y, + Width = width - triangle_portion, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Point.GetRepresentingColour(colours), + Masking = true, + CornerRadius = corner_radius, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, }, Label = new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Padding = new MarginPadding(3), Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold), Colour = colours.B5, From 57abb15724e537bee9a97ffdbd810b632b675671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 15:58:53 +0900 Subject: [PATCH 016/136] Update design of timeline centre marker and adjust surrounding paddings --- .../Components/Timeline/CentreMarker.cs | 20 +++++++------------ .../Components/Timeline/TimelineArea.cs | 20 +++++++++++++------ .../Screens/Edit/EditorScreenWithTimeline.cs | 4 ++-- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index aee3cffbfd..74786cc0c9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -12,17 +12,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class CentreMarker : CompositeDrawable { - private const float triangle_width = 15; - private const float triangle_height = 10; - private const float bar_width = 2; + private const float triangle_width = 8; + + private const float bar_width = 1.6f; public CentreMarker() { RelativeSizeAxes = Axes.Y; Size = new Vector2(triangle_width, 1); - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; InternalChildren = new Drawable[] { @@ -37,22 +37,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, - Size = new Vector2(triangle_width, triangle_height), + Size = new Vector2(triangle_width, triangle_width * 0.8f), Scale = new Vector2(1, -1) }, - new Triangle - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(triangle_width, triangle_height), - } }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - Colour = colours.RedDark; + Colour = colours.Red1; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 70322bd3e0..fb7ce8e423 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays; @@ -29,10 +30,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - Masking = true; - OsuCheckbox waveformCheckbox; OsuCheckbox controlPointsCheckbox; OsuCheckbox ticksCheckbox; @@ -51,7 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { new Dimension(GridSizeMode.Absolute, 140), new Dimension(), - new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 35), new Dimension(GridSizeMode.Absolute, 110), }, Content = new[] @@ -102,6 +101,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.Y, Children = new Drawable[] { + // the out-of-bounds portion of the centre marker. + new Box + { + Width = 24, + Height = EditorScreenWithTimeline.PADDING, + Depth = float.MaxValue, + Colour = colours.Red1, + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }, new Box { RelativeSizeAxes = Axes.Both, @@ -115,7 +124,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Both, Name = @"Zoom controls", - Padding = new MarginPadding { Right = 5 }, + Padding = new MarginPadding { Right = 10 }, Children = new Drawable[] { new Box @@ -128,7 +137,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Masking = true, Children = new[] { new TimelineButton diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 104d9dd58f..ea2790b50a 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit { public abstract partial class EditorScreenWithTimeline : EditorScreen { - private const float padding = 15; + public const float PADDING = 10; private Container timelineContainer = null!; @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit Name = "Timeline content", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = padding, Top = padding }, + Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING }, Child = new GridContainer { RelativeSizeAxes = Axes.X, From e6b8cd0c06abce3e02806c570b4316a484c2d817 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 16:55:12 +0900 Subject: [PATCH 017/136] Add editor header --- .../Edit/Components/Menus/EditorMenuBar.cs | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index a911b4e1d8..fa4e52d6a6 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -3,6 +3,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -14,19 +17,56 @@ namespace osu.Game.Screens.Edit.Components.Menus { public partial class EditorMenuBar : OsuMenu { + private const float heading_area = 114; + public EditorMenuBar() : base(Direction.Horizontal, true) { RelativeSizeAxes = Axes.X; MaskingContainer.CornerRadius = 0; - ItemsContainer.Padding = new MarginPadding { Left = 100 }; + ItemsContainer.Padding = new MarginPadding { Left = heading_area }; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, TextureStore textures) { BackgroundColour = colourProvider.Background3; + + TextFlowContainer text; + + AddRangeInternal(new[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + Width = heading_area, + Padding = new MarginPadding(8), + Children = new Drawable[] + { + new Sprite + { + Size = new Vector2(26), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Texture = textures.Get("Icons/Hexacons/editor"), + }, + text = new TextFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + } + } + }, + }); + + text.AddText("osu!", t => t.Font = OsuFont.TorusAlternate); + text.AddText("editor", t => + { + t.Font = OsuFont.TorusAlternate; + t.Colour = colourProvider.Highlight1; + }); } protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); From fe70f2492538533148fd3ee28a2749901736bf43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 Jul 2023 17:00:42 +0900 Subject: [PATCH 018/136] Update design of summary timeline current time marker --- .../Timelines/Summary/Parts/MarkerPart.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index d42c02e03d..ff707407dd 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -73,6 +73,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public MarkerVisualisation() { + const float box_height = 4; + Anchor = Anchor.CentreLeft; Origin = Anchor.Centre; RelativePositionAxes = Axes.X; @@ -80,32 +82,46 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts AutoSizeAxes = Axes.X; InternalChildren = new Drawable[] { + new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(14, box_height), + }, new Triangle { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Scale = new Vector2(1, -1), Size = new Vector2(10, 5), + Y = box_height, }, new Triangle { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Size = new Vector2(10, 5) + Size = new Vector2(10, 5), + Y = -box_height, + }, + new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(14, box_height), }, new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, - Width = 2, + Width = 1.4f, EdgeSmoothness = new Vector2(1, 0) } }; } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Red; + private void load(OsuColour colours) => Colour = colours.Red1; } } } From 3888471148e0db66654e7ce4b34886671abe2828 Mon Sep 17 00:00:00 2001 From: NiceAesth Date: Sun, 16 Jul 2023 23:03:21 +0300 Subject: [PATCH 019/136] Add break length and bounds checks --- .../Editing/Checks/CheckBreaksTest.cs | 10 +++ osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 3 + osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs | 84 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs new file mode 100644 index 0000000000..664f72c5f8 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tests.Editing.Checks +{ + public class CheckBreaksTest + { + + } +} diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 3988f29e13..4bcf74db45 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -38,6 +38,9 @@ namespace osu.Game.Rulesets.Edit // Timing new CheckPreviewTime(), + + // Events + new CheckBreaks() }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs new file mode 100644 index 0000000000..12dc5554f4 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckBreaks : ICheck + { + // Breaks may be off by 1 ms. + private const int leniency_threshold = 1; + private const double min_start_threshold = 200; + + // Break end time depends on the upcoming object's pre-empt time. + // As things stand, "pre-empt time" is only defined for osu! standard + // This is a generic value representing AR=10 + // Relevant: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551 + private const double min_end_threshold = 450; + public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Events, "Breaks not achievable using the editor"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateEarlyStart(this), + new IssueTemplateLateEnd(this), + new IssueTemplateTooShort(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + foreach (var breakPeriod in context.Beatmap.Breaks) + { + if (breakPeriod.Duration < BreakPeriod.MIN_BREAK_DURATION) + yield return new IssueTemplateTooShort(this).Create(breakPeriod.StartTime); + } + + foreach (var hitObject in context.Beatmap.HitObjects) + { + foreach (var breakPeriod in context.Beatmap.Breaks) + { + double diffStart = breakPeriod.StartTime - hitObject.GetEndTime(); + double diffEnd = hitObject.StartTime - breakPeriod.EndTime; + + if (diffStart < min_start_threshold - leniency_threshold && diffStart > 0) + yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, min_start_threshold - diffStart); + else if (diffEnd < min_end_threshold - leniency_threshold && diffEnd > 0) + yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - diffEnd); + } + } + } + + public class IssueTemplateEarlyStart : IssueTemplate + { + public IssueTemplateEarlyStart(ICheck check) + : base(check, IssueType.Problem, "Break starts {0} ms early.") + { + } + + public Issue Create(double startTime, double diff) => new Issue(startTime, this, (int)diff); + } + + public class IssueTemplateLateEnd : IssueTemplate + { + public IssueTemplateLateEnd(ICheck check) + : base(check, IssueType.Problem, "Break ends {0} ms late.") + { + } + + public Issue Create(double startTime, double diff) => new Issue(startTime, this, (int)diff); + } + + public class IssueTemplateTooShort : IssueTemplate + { + public IssueTemplateTooShort(ICheck check) + : base(check, IssueType.Warning, "Break is non-functional due to being less than 650ms.") + { + } + + public Issue Create(double startTime) => new Issue(startTime, this); + } + } +} From b3974b34e7cd519d6576b0f01c8788976789f35c Mon Sep 17 00:00:00 2001 From: NiceAesth Date: Sun, 16 Jul 2023 23:03:30 +0300 Subject: [PATCH 020/136] Test break checks --- .../Editing/Checks/CheckBreaksTest.cs | 105 +++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs index 664f72c5f8..39e414827a 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs @@ -1,10 +1,113 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + namespace osu.Game.Tests.Editing.Checks { public class CheckBreaksTest { - + private CheckBreaks check = null!; + + [SetUp] + public void Setup() + { + check = new CheckBreaks(); + } + + [Test] + public void TestBreakTooShort() + { + var beatmap = new Beatmap + { + Breaks = new List + { + new BreakPeriod(0, 649) + } + }; + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateTooShort); + } + + [Test] + public void TestBreakStartsEarly() + { + var beatmap = new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 1_200 } + }, + Breaks = new List + { + new BreakPeriod(100, 751) + } + }; + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateEarlyStart); + } + + [Test] + public void TestBreakEndsLate() + { + var beatmap = new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 1_298 } + }, + Breaks = new List + { + new BreakPeriod(200, 850) + } + }; + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateLateEnd); + } + + [Test] + public void TestBreaksCorrect() + { + var beatmap = new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 1_300 } + }, + Breaks = new List + { + new BreakPeriod(200, 850) + } + }; + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + + var issues = check.Run(context).ToList(); + + Assert.That(issues, Is.Empty); + } } } From ff529d9df7d66dc3191c6f75b3691df451c7aeb9 Mon Sep 17 00:00:00 2001 From: NiceAesth Date: Mon, 17 Jul 2023 20:48:53 +0300 Subject: [PATCH 021/136] Rename variables, fix check message formatting --- osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs index 12dc5554f4..54dfb557fe 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Edit.Checks { // Breaks may be off by 1 ms. private const int leniency_threshold = 1; - private const double min_start_threshold = 200; + private const double minimum_gap_before_break = 200; // Break end time depends on the upcoming object's pre-empt time. // As things stand, "pre-empt time" is only defined for osu! standard @@ -40,13 +40,13 @@ namespace osu.Game.Rulesets.Edit.Checks { foreach (var breakPeriod in context.Beatmap.Breaks) { - double diffStart = breakPeriod.StartTime - hitObject.GetEndTime(); - double diffEnd = hitObject.StartTime - breakPeriod.EndTime; + double gapBeforeBreak = breakPeriod.StartTime - hitObject.GetEndTime(); + double gapAfterBreak = hitObject.StartTime - breakPeriod.EndTime; - if (diffStart < min_start_threshold - leniency_threshold && diffStart > 0) - yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, min_start_threshold - diffStart); - else if (diffEnd < min_end_threshold - leniency_threshold && diffEnd > 0) - yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - diffEnd); + if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold && gapBeforeBreak > 0) + yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, minimum_gap_before_break - gapBeforeBreak); + else if (gapAfterBreak < min_end_threshold - leniency_threshold && gapAfterBreak > 0) + yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - gapAfterBreak); } } } @@ -74,11 +74,11 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateTooShort : IssueTemplate { public IssueTemplateTooShort(ICheck check) - : base(check, IssueType.Warning, "Break is non-functional due to being less than 650ms.") + : base(check, IssueType.Warning, "Break is non-functional due to being less than {0} ms.") { } - public Issue Create(double startTime) => new Issue(startTime, this); + public Issue Create(double startTime) => new Issue(startTime, this, BreakPeriod.MIN_BREAK_DURATION); } } } From c02684d985e426b8dab1bf175204026cff2845e3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 18 Jul 2023 12:18:43 +0200 Subject: [PATCH 022/136] truncate hit object end time --- osu.Game/Database/LegacyBeatmapExporter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 42d8a72073..3b2282c234 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -60,6 +60,9 @@ namespace osu.Game.Database { hitObject.StartTime = Math.Floor(hitObject.StartTime); + if (hitObject is IHasDuration hasDuration && hitObject is not IHasPath) + hasDuration.Duration = Math.Floor(hasDuration.Duration); + if (hitObject is not IHasPath hasPath || BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue; var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); From bcdf5310390021d2b5b4996c6d88293846a12004 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 18 Jul 2023 12:28:35 +0200 Subject: [PATCH 023/136] truncate end time before start time --- osu.Game/Database/LegacyBeatmapExporter.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 3b2282c234..983d25a25a 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -58,10 +58,11 @@ namespace osu.Game.Database foreach (var hitObject in beatmapContent.HitObjects) { - hitObject.StartTime = Math.Floor(hitObject.StartTime); - + // Truncate end time before truncating start time because end time is dependent on start time if (hitObject is IHasDuration hasDuration && hitObject is not IHasPath) - hasDuration.Duration = Math.Floor(hasDuration.Duration); + hasDuration.Duration = Math.Floor(hasDuration.EndTime) - Math.Floor(hitObject.StartTime); + + hitObject.StartTime = Math.Floor(hitObject.StartTime); if (hitObject is not IHasPath hasPath || BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue; From 395dd23966d0ae3fb15b13b8e0b8c6628e1a310b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 18 Jul 2023 12:37:37 +0200 Subject: [PATCH 024/136] Put 'Export package' and 'Export legacy package' in one nested menu --- osu.Game/Localisation/EditorStrings.cs | 9 +++++++-- osu.Game/Screens/Edit/Editor.cs | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 70392d5c83..07b4dddc05 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -40,9 +40,14 @@ namespace osu.Game.Localisation public static LocalisableString ExportPackage => new TranslatableString(getKey(@"export_package"), @"Export package"); /// - /// "Export legacy package" + /// "Legacy format (.osz)" /// - public static LocalisableString ExportLegacyPackage => new TranslatableString(getKey(@"export_legacy_package"), @"Export legacy package"); + public static LocalisableString ExportLegacyFormat => new TranslatableString(getKey(@"export_legacy_format"), @"Legacy format (.osz)"); + + /// + /// "New format (.olz)" + /// + public static LocalisableString ExportNewFormat => new TranslatableString(getKey(@"export_new_format"), @"New format (.olz)"); /// /// "Create new difficulty" diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a48c57f991..16cd7768fa 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -998,8 +998,7 @@ namespace osu.Game.Screens.Edit private List createFileMenuItems() => new List { new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), - new EditorMenuItem(EditorStrings.ExportPackage, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new EditorMenuItem(EditorStrings.ExportLegacyPackage, MenuItemType.Standard, exportLegacyBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + createExportPackageMenu(), new EditorMenuItemSpacer(), createDifficultyCreationMenu(), createDifficultySwitchMenu(), @@ -1009,6 +1008,17 @@ namespace osu.Game.Screens.Edit new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit) }; + private EditorMenuItem createExportPackageMenu() + { + var exportItems = new List + { + new EditorMenuItem(EditorStrings.ExportNewFormat, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new EditorMenuItem(EditorStrings.ExportLegacyFormat, MenuItemType.Standard, exportLegacyBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + }; + + return new EditorMenuItem(EditorStrings.ExportPackage) { Items = exportItems }; + } + private void exportBeatmap() { Save(); From 63dd8bd991835344cbbf3e0c88b8f4b749ea1029 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 18 Jul 2023 12:40:48 +0200 Subject: [PATCH 025/136] use base.GetFileContents to get file stream --- osu.Game/Database/LegacyBeatmapExporter.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 983d25a25a..b90ea73244 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -8,7 +8,6 @@ using System.Text; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; -using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -36,7 +35,7 @@ namespace osu.Game.Database return base.GetFileContents(model, file); // Read the beatmap contents and skin - using var contentStream = UserFileStorage.GetStream(file.File.GetStoragePath()); + using var contentStream = base.GetFileContents(model, file); if (contentStream == null) return null; @@ -44,7 +43,7 @@ namespace osu.Game.Database using var contentStreamReader = new LineBufferedReader(contentStream); var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader); - using var skinStream = UserFileStorage.GetStream(file.File.GetStoragePath()); + using var skinStream = base.GetFileContents(model, file); using var skinStreamReader = new LineBufferedReader(contentStream); var beatmapSkin = new LegacySkin(new SkinInfo(), null!) { From e7a9175aeae253a515c8a8ab0d40985853de28ec Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 18 Jul 2023 13:08:05 +0200 Subject: [PATCH 026/136] fix skin using wrong stream --- osu.Game/Database/LegacyBeatmapExporter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index b90ea73244..e054652efa 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -44,7 +44,11 @@ namespace osu.Game.Database var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader); using var skinStream = base.GetFileContents(model, file); - using var skinStreamReader = new LineBufferedReader(contentStream); + + if (skinStream == null) + return null; + + using var skinStreamReader = new LineBufferedReader(skinStream); var beatmapSkin = new LegacySkin(new SkinInfo(), null!) { Configuration = new LegacySkinDecoder().Decode(skinStreamReader) From b9a66ad7b346aa26d49a875a522deb8932b8dca9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 14:58:45 +0900 Subject: [PATCH 027/136] Add test coverage of incorrect selection behaviour --- .../Editor/TestSceneOsuComposerSelection.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 8641663ce8..623cefff6b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -25,6 +25,35 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + [Test] + public void TestSelectAfterFadedOut() + { + var slider = new Slider + { + StartTime = 0, + Position = new Vector2(100, 100), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + AddStep("add slider", () => EditorBeatmap.Add(slider)); + + moveMouseToObject(() => slider); + + AddStep("seek after end", () => EditorClock.Seek(750)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); + + AddStep("seek to visible", () => EditorClock.Seek(650)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider); + } + [Test] public void TestContextMenuShownCorrectlyForSelectedSlider() { From 4a6a5b174a3255b8479edb2ab4683fd779040925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 14:52:31 +0900 Subject: [PATCH 028/136] Fix editor blueprints being selectable for too long when hit markers are enabled Addresses https://github.com/ppy/osu/discussions/24163. --- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index bdd19f9106..178b809d8b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -22,7 +22,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints protected override bool AlwaysShowWhenSelected => true; protected override bool ShouldBeAlive => base.ShouldBeAlive - || (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION); + || (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime + && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION); + + public override bool HandlePositionalInput => + // Bypass fade out extension from hit markers for input handling purposes. + // This is to match stable, where even when the afterimage hit markers are still visible, objects are not selectable. + // + // Note that we are intentionally overriding HandlePositionalInput here and not ReceivePositionalInputAt + // as individual blueprint implementations override that. + base.ShouldBeAlive; protected OsuSelectionBlueprint(T hitObject) : base(hitObject) From 871056790b4bfb56fb5d5fad4e3e224673b53987 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 15:01:20 +0900 Subject: [PATCH 029/136] Mark editor tile as non-localisable --- osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index fa4e52d6a6..e958849bb0 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -61,8 +61,8 @@ namespace osu.Game.Screens.Edit.Components.Menus }, }); - text.AddText("osu!", t => t.Font = OsuFont.TorusAlternate); - text.AddText("editor", t => + text.AddText(@"osu!", t => t.Font = OsuFont.TorusAlternate); + text.AddText(@"editor", t => { t.Font = OsuFont.TorusAlternate; t.Colour = colourProvider.Highlight1; From 55a41b0887ff6e7d78502f40dc61204eda4c0306 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 15:13:20 +0900 Subject: [PATCH 030/136] Fix overlap between header text and menu items --- osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index e958849bb0..b9385ff0c3 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -25,7 +25,10 @@ namespace osu.Game.Screens.Edit.Components.Menus RelativeSizeAxes = Axes.X; MaskingContainer.CornerRadius = 0; - ItemsContainer.Padding = new MarginPadding { Left = heading_area }; + ItemsContainer.Padding = new MarginPadding(); + + ContentContainer.Margin = new MarginPadding { Left = heading_area }; + ContentContainer.Masking = true; } [BackgroundDependencyLoader] From 4e4dcc9846cdb1c9c052b021d3d90681ce877bff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 16:32:04 +0900 Subject: [PATCH 031/136] Add test coverage of selection preferring closest objects --- .../Editing/TestSceneComposerSelection.cs | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index d6934a3770..be5dd59206 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -6,21 +6,21 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Testing; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Tests.Beatmaps; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Input; @@ -217,6 +217,75 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); } + [Test] + public void TestNearestSelection() + { + var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 }; + var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject })); + + moveMouseToObject(() => firstObject); + + AddStep("seek near first", () => EditorClock.Seek(100)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + + AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); + + AddStep("seek near second", () => EditorClock.Seek(500)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject)); + + AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); + + AddStep("seek halfway", () => EditorClock.Seek(300)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + } + + [Test] + public void TestNearestSelectionWithEndTime() + { + var firstObject = new Slider + { + Position = new Vector2(256, 192), + StartTime = 0, + Path = new SliderPath(new[] + { + new PathControlPoint(), + new PathControlPoint(new Vector2(50, 0)), + }) + }; + + var secondObject = new HitCircle + { + Position = new Vector2(256, 192), + StartTime = 600 + }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new HitObject[] { firstObject, secondObject })); + + moveMouseToObject(() => firstObject); + + AddStep("seek near first", () => EditorClock.Seek(100)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + + AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); + + AddStep("seek near second", () => EditorClock.Seek(500)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject)); + + AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); + + AddStep("seek roughly halfway", () => EditorClock.Seek(350)); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + // Slider gets priority due to end time. + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + } + [TestCase(false)] [TestCase(true)] public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag) From 5ade093c5a70f32971ed26422c7c53f29b6529bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 15:55:38 +0900 Subject: [PATCH 032/136] Change editor to always perform selection of closest object --- .../Compose/Components/BlueprintContainer.cs | 27 ++++++++++++++----- .../Components/EditorBlueprintContainer.cs | 5 ++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 56a6b18433..5fdd2634c4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -381,6 +381,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private bool selectedBlueprintAlreadySelectedOnMouseDown; + protected virtual IEnumerable> ApplySelectionOrder(IEnumerable> blueprints) => blueprints.Reverse(); + /// /// Attempts to select any hovered blueprints. /// @@ -390,15 +392,28 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Where(b => b.IsSelected)) { - if (!blueprint.IsHovered) continue; + if (runForBlueprint(blueprint)) + return true; + } - selectedBlueprintAlreadySelectedOnMouseDown = blueprint.State == SelectionState.Selected; - return clickSelectionHandled = SelectionHandler.MouseDownSelectionRequested(blueprint, e); + foreach (SelectionBlueprint blueprint in ApplySelectionOrder(SelectionBlueprints.AliveChildren)) + { + if (runForBlueprint(blueprint)) + return true; } return false; + + bool runForBlueprint(SelectionBlueprint blueprint) + { + if (!blueprint.IsHovered) return false; + + selectedBlueprintAlreadySelectedOnMouseDown = blueprint.State == SelectionState.Selected; + clickSelectionHandled = SelectionHandler.MouseDownSelectionRequested(blueprint, e); + return true; + } } /// @@ -432,13 +447,13 @@ namespace osu.Game.Screens.Edit.Compose.Components // The depth of blueprints is constantly changing (see above where selected blueprints are brought to the front). // For this logic, we want a stable sort order so we can correctly cycle, thus using the blueprintMap instead. - IEnumerable> cyclingSelectionBlueprints = blueprintMap.Values; + IEnumerable> cyclingSelectionBlueprints = ApplySelectionOrder(blueprintMap.Values); // If there's already a selection, let's start from the blueprint after the selection. cyclingSelectionBlueprints = cyclingSelectionBlueprints.SkipWhile(b => !b.IsSelected).Skip(1); // Add the blueprints from before the selection to the end of the enumerable to allow for cyclic selection. - cyclingSelectionBlueprints = cyclingSelectionBlueprints.Concat(blueprintMap.Values.TakeWhile(b => !b.IsSelected)); + cyclingSelectionBlueprints = cyclingSelectionBlueprints.Concat(ApplySelectionOrder(blueprintMap.Values).TakeWhile(b => !b.IsSelected)); foreach (SelectionBlueprint blueprint in cyclingSelectionBlueprints) { diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 65797a968d..ad0e8b124b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -129,6 +130,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } + protected override IEnumerable> ApplySelectionOrder(IEnumerable> blueprints) => + base.ApplySelectionOrder(blueprints) + .OrderBy(b => Math.Min(Math.Abs(EditorClock.CurrentTime - b.Item.GetEndTime()), Math.Abs(EditorClock.CurrentTime - b.Item.StartTime))); + protected override Container> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; protected override SelectionHandler CreateSelectionHandler() => new EditorSelectionHandler(); From 9d46d00294cda3ea10fca9507d008975efcc91d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 16:17:42 +0900 Subject: [PATCH 033/136] Update skin editor cyclic test to match expectations better --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 7b37b6624d..4e5db5d46e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -138,24 +138,28 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCyclicSelection() { - SkinBlueprint[] blueprints = null!; + List blueprints = new List(); - AddStep("Add big black boxes", () => + AddStep("clear list", () => blueprints.Clear()); + + for (int i = 0; i < 3; i++) { - InputManager.MoveMouseTo(skinEditor.ChildrenOfType().First()); - InputManager.Click(MouseButton.Left); - InputManager.Click(MouseButton.Left); - InputManager.Click(MouseButton.Left); - }); + AddStep("Add big black box", () => + { + InputManager.MoveMouseTo(skinEditor.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("store box", () => + { + // Add blueprints one-by-one so we have a stable order for testing reverse cyclic selection against. + blueprints.Add(skinEditor.ChildrenOfType().Single(s => s.IsSelected)); + }); + } AddAssert("Three black boxes added", () => targetContainer.Components.OfType().Count(), () => Is.EqualTo(3)); - AddStep("Store black box blueprints", () => - { - blueprints = skinEditor.ChildrenOfType().Where(b => b.Item is BigBlackBox).ToArray(); - }); - - AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item)); + AddAssert("Selection is last", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item)); AddStep("move cursor to black box", () => { @@ -164,13 +168,13 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left)); - AddAssert("Selection is black box 2", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item)); + AddAssert("Selection is second last", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item)); AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left)); - AddAssert("Selection is black box 3", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item)); + AddAssert("Selection is last", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item)); AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left)); - AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item)); + AddAssert("Selection is first", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item)); AddStep("select all boxes", () => { From d33b174243ef6fe98b3c9ac64cfb6f4ccc91d91c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 17:21:17 +0900 Subject: [PATCH 034/136] Add test coverage of beatmap editor cyclic selection --- .../Editing/TestSceneComposerSelection.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index be5dd59206..d7fb13390f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -286,6 +286,85 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); } + [Test] + public void TestCyclicSelection() + { + var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 }; + var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 }; + var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject })); + + moveMouseToObject(() => firstObject); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject)); + + // cycle around + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + } + + [Test] + public void TestCyclicSelectionOutwards() + { + var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 }; + var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 }; + var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject })); + + moveMouseToObject(() => firstObject); + + AddStep("seek near first", () => EditorClock.Seek(320)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + + // cycle around + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject)); + } + + [Test] + public void TestCyclicSelectionBackwards() + { + var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 }; + var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 }; + var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject })); + + moveMouseToObject(() => firstObject); + + AddStep("seek near first", () => EditorClock.Seek(600)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject)); + + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); + + // cycle around + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject)); + } + [TestCase(false)] [TestCase(true)] public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag) From cf3949c9e21b3fe4f085a8a43d56b60ecdde5ae6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 15:35:40 +0900 Subject: [PATCH 035/136] Fix double-click handling when cyclic selection is enabled Removes the limitations of cyclic selection as a result. --- .../SkinEditor/SkinBlueprintContainer.cs | 2 -- .../Compose/Components/BlueprintContainer.cs | 20 +++++++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs b/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs index db27e20010..3f8d9f80d4 100644 --- a/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs +++ b/osu.Game/Overlays/SkinEditor/SkinBlueprintContainer.cs @@ -25,8 +25,6 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private SkinEditor editor { get; set; } = null!; - protected override bool AllowCyclicSelection => true; - public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer) { this.targetContainer = targetContainer; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 5fdd2634c4..c8df999d37 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -46,15 +46,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly BindableList SelectedItems = new BindableList(); - /// - /// Whether to allow cyclic selection on clicking multiple times. - /// - /// - /// Disabled by default as it does not work well with editors that support double-clicking or other advanced interactions. - /// Can probably be made to work with more thought. - /// - protected virtual bool AllowCyclicSelection => false; - protected BlueprintContainer() { RelativeSizeAxes = Axes.Both; @@ -167,6 +158,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint) return false; + doubleClickHandled = true; return true; } @@ -177,6 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { endClickSelection(e); clickSelectionHandled = false; + doubleClickHandled = false; isDraggingBlueprint = false; wasDragStarted = false; }); @@ -376,6 +369,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private bool clickSelectionHandled; + /// + /// Whether a blueprint was double-clicked since last mouse down. + /// + private bool doubleClickHandled; + /// /// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case. /// @@ -424,7 +422,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool endClickSelection(MouseButtonEvent e) { // If already handled a selection or drag, we don't want to perform a mouse up / click action. - if (clickSelectionHandled || isDraggingBlueprint) return true; + if (clickSelectionHandled || doubleClickHandled || isDraggingBlueprint) return true; if (e.Button != MouseButton.Left) return false; @@ -440,7 +438,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1 && AllowCyclicSelection) + if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1) { // If a click occurred and was handled by the currently selected blueprint but didn't result in a drag, // cycle between other blueprints which are also under the cursor. From 94c5b8ed32a322d58b04b894eb65449b81bb9bf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 17:22:25 +0900 Subject: [PATCH 036/136] Apply NRT to `TestSceneComposerSelection` --- .../Visual/Editing/TestSceneComposerSelection.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index d7fb13390f..51e75939fe 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -82,7 +80,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestNudgeSelection() { - HitCircle[] addedObjects = null; + HitCircle[] addedObjects = null!; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] { @@ -104,7 +102,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestRotateHotkeys() { - HitCircle[] addedObjects = null; + HitCircle[] addedObjects = null!; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] { @@ -136,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestGlobalFlipHotkeys() { - HitCircle addedObject = null; + HitCircle addedObject = null!; AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 })); @@ -369,7 +367,7 @@ namespace osu.Game.Tests.Visual.Editing [TestCase(true)] public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag) { - HitCircle[] addedObjects = null; + HitCircle[] addedObjects = null!; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] { @@ -468,7 +466,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestQuickDeleteRemovesSliderControlPoint() { - Slider slider = null; + Slider slider = null!; PathControlPoint[] points = { From e283845b7144d1e16039841af85f3441a3be2a26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 17:45:07 +0900 Subject: [PATCH 037/136] Adjust legacy skin elements to better align with skinning expectations --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 4 ++-- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index c99cdba91c..326257c25f 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -17,8 +17,8 @@ namespace osu.Game.Skinning Anchor = Anchor.TopRight; Origin = Anchor.TopRight; - Scale = new Vector2(0.6f); - Margin = new MarginPadding(10); + Scale = new Vector2(0.6f * 0.96f); + Margin = new MarginPadding { Vertical = 9, Horizontal = 17 }; } protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index d8ee6b21de..d238369be1 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -21,7 +21,7 @@ namespace osu.Game.Skinning Origin = Anchor.TopRight; Scale = new Vector2(0.96f); - Margin = new MarginPadding(10); + Margin = new MarginPadding { Horizontal = 10 }; } protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 79f13686e8..00c18bef3d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -368,7 +368,7 @@ namespace osu.Game.Skinning { songProgress.Anchor = Anchor.TopRight; songProgress.Origin = Anchor.CentreRight; - songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 10; + songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 20; songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); } From 61ff3d08d45f2667215c46d1412cd9d3f02e3d5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 17:48:19 +0900 Subject: [PATCH 038/136] Change depth of `LegacySongProgress` to allow "skinning" via health bar background --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 00c18bef3d..b72a757e6c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -397,8 +397,8 @@ namespace osu.Game.Skinning new LegacyComboCounter(), new LegacyScoreCounter(), new LegacyAccuracyCounter(), - new LegacyHealthDisplay(), new LegacySongProgress(), + new LegacyHealthDisplay(), new BarHitErrorMeter(), new DefaultKeyCounterDisplay() } From eb149942e59b9995cfe31d0287289eb4842a0742 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Jul 2023 19:08:32 +0900 Subject: [PATCH 039/136] Add ability to toggle all free mods quickly at multiplayer song select --- .../TestSceneMultiplayerMatchSongSelect.cs | 8 ++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 + .../OnlinePlay/FooterButtonFreeMods.cs | 106 ++++++++++++++---- .../OnlinePlay/OnlinePlaySongSelect.cs | 9 +- 4 files changed, 104 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 947b7e5be6..8dc41cd707 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -67,6 +67,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); } + [Test] + public void TestSelectFreeMods() + { + AddStep("set some freemods", () => songSelect.FreeMods.Value = new OsuRuleset().GetModsFor(ModType.Fun).ToArray()); + AddStep("set all freemods", () => songSelect.FreeMods.Value = new OsuRuleset().CreateAllMods().ToArray()); + AddStep("set no freemods", () => songSelect.FreeMods.Value = Array.Empty()); + } + [Test] public void TestBeatmapConfirmed() { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c09668850a..ba3f01a688 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -111,6 +111,10 @@ namespace osu.Game.Overlays.Mods private readonly Bindable>> globalAvailableMods = new Bindable>>(); + public IEnumerable AllAvailableAndValidMods => allAvailableMods + .Select(s => s.Mod) + .Where(m => isValidMod(m)); + private IEnumerable allAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); private readonly BindableBool customisationVisible = new BindableBool(); diff --git a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs index 920aff13a8..56a69be741 100644 --- a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs +++ b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs @@ -1,15 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; +using System.Linq; using osu.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.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; using osuTK; @@ -17,28 +23,60 @@ namespace osu.Game.Screens.OnlinePlay { public partial class FooterButtonFreeMods : FooterButton, IHasCurrentValue> { - public Bindable> Current + public Bindable> Current { get; set; } = new BindableWithCurrent>(); + + private OsuSpriteText count = null!; + + private Circle circle = null!; + + private readonly FreeModSelectOverlay freeModSelectOverlay; + + public FooterButtonFreeMods(FreeModSelectOverlay freeModSelectOverlay) { - get => modDisplay.Current; - set => modDisplay.Current = value; + this.freeModSelectOverlay = freeModSelectOverlay; } - private readonly ModDisplay modDisplay; - - public FooterButtonFreeMods() - { - ButtonContentContainer.Add(modDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - ExpansionMode = ExpansionMode.AlwaysContracted, - }); - } + [Resolved] + private OsuColour colours { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { + ButtonContentContainer.AddRange(new[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + circle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + }, + count = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + UseFullGlyphHeight = false, + } + } + }, + new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + Icon = FontAwesome.Solid.Bars, + Action = () => freeModSelectOverlay.ToggleVisibility() + } + }); + SelectedColour = colours.Yellow; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"freemods"; @@ -49,14 +87,42 @@ namespace osu.Game.Screens.OnlinePlay base.LoadComplete(); Current.BindValueChanged(_ => updateModDisplay(), true); + + // Overwrite any external behaviour as we delegate the main toggle action to a sub-button. + Action = toggleAllFreeMods; + } + + /// + /// Immediately toggle all free mods on/off. + /// + private void toggleAllFreeMods() + { + var availableMods = freeModSelectOverlay.AllAvailableAndValidMods.ToArray(); + + Current.Value = Current.Value.Count == availableMods.Length + ? Array.Empty() + : availableMods; } private void updateModDisplay() { - if (Current.Value?.Count > 0) - modDisplay.FadeIn(); + int current = Current.Value.Count; + + if (current == freeModSelectOverlay.AllAvailableAndValidMods.Count()) + { + count.Text = "all"; + circle.FadeColour(colours.Yellow, 200, Easing.OutQuint); + } + else if (current > 0) + { + count.Text = $"{current} mods"; + circle.FadeColour(colours.YellowDark, 200, Easing.OutQuint); + } else - modDisplay.FadeOut(); + { + count.Text = "off"; + circle.FadeColour(colours.Gray4, 200, Easing.OutQuint); + } } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index e0ae437d49..622ffddba6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -175,9 +175,12 @@ namespace osu.Game.Screens.OnlinePlay protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateFooterButtons() { - var buttons = base.CreateFooterButtons().ToList(); - buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay)); - return buttons; + var baseButtons = base.CreateFooterButtons().ToList(); + var freeModsButton = new FooterButtonFreeMods(freeModSelectOverlay) { Current = FreeMods }; + + baseButtons.Insert(baseButtons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (freeModsButton, freeModSelectOverlay)); + + return baseButtons; } /// From fa29c25097a178ad04a08c5c8ab26a3c918e847e Mon Sep 17 00:00:00 2001 From: NiceAesth Date: Thu, 20 Jul 2023 00:32:35 +0300 Subject: [PATCH 040/136] Change check to use binary search --- osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs | 57 ++++++++++++++++---- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs index 54dfb557fe..44d2c18dad 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBreaks.cs @@ -34,23 +34,62 @@ namespace osu.Game.Rulesets.Edit.Checks { if (breakPeriod.Duration < BreakPeriod.MIN_BREAK_DURATION) yield return new IssueTemplateTooShort(this).Create(breakPeriod.StartTime); - } - foreach (var hitObject in context.Beatmap.HitObjects) - { - foreach (var breakPeriod in context.Beatmap.Breaks) + var previousObject = getPreviousObject(breakPeriod.StartTime, context.Beatmap.HitObjects); + var nextObject = getNextObject(breakPeriod.EndTime, context.Beatmap.HitObjects); + + if (previousObject != null) { - double gapBeforeBreak = breakPeriod.StartTime - hitObject.GetEndTime(); - double gapAfterBreak = hitObject.StartTime - breakPeriod.EndTime; - - if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold && gapBeforeBreak > 0) + double gapBeforeBreak = breakPeriod.StartTime - previousObject.GetEndTime(); + if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold) yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, minimum_gap_before_break - gapBeforeBreak); - else if (gapAfterBreak < min_end_threshold - leniency_threshold && gapAfterBreak > 0) + } + + if (nextObject != null) + { + double gapAfterBreak = nextObject.StartTime - breakPeriod.EndTime; + if (gapAfterBreak < min_end_threshold - leniency_threshold) yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - gapAfterBreak); } } } + private HitObject? getPreviousObject(double time, IReadOnlyList hitObjects) + { + int left = 0; + int right = hitObjects.Count - 1; + + while (left <= right) + { + int mid = left + (right - left) / 2; + + if (hitObjects[mid].GetEndTime() < time) + left = mid + 1; + else + right = mid - 1; + } + + return right >= 0 ? hitObjects[right] : null; + } + + private HitObject? getNextObject(double time, IReadOnlyList hitObjects) + { + int left = 0; + int right = hitObjects.Count - 1; + + while (left <= right) + { + int mid = left + (right - left) / 2; + + if (hitObjects[mid].StartTime <= time) + left = mid + 1; + else + right = mid - 1; + } + + return left < hitObjects.Count ? hitObjects[left] : null; + } + public class IssueTemplateEarlyStart : IssueTemplate { public IssueTemplateEarlyStart(ICheck check) From ce78bb549f2e5d9c09bf91402f55a7d26555f191 Mon Sep 17 00:00:00 2001 From: NiceAesth Date: Thu, 20 Jul 2023 00:32:54 +0300 Subject: [PATCH 041/136] Add test for multiple too early objects in break --- .../Editing/Checks/CheckBreaksTest.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs index 39e414827a..aaa536f9b9 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBreaksTest.cs @@ -88,6 +88,30 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateLateEnd); } + [Test] + public void TestBreakMultipleObjectsEarly() + { + var beatmap = new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 1_297 }, + new HitCircle { StartTime = 1_298 } + }, + Breaks = new List + { + new BreakPeriod(200, 850) + } + }; + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBreaks.IssueTemplateLateEnd); + } + [Test] public void TestBreaksCorrect() { From 18c5fc689f4dc799ab433eb041ca9c09482668ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jul 2023 12:58:13 +0900 Subject: [PATCH 042/136] Don't expose such specific information from `ModSelectOverlay` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 +++++--------- .../Screens/OnlinePlay/FooterButtonFreeMods.cs | 8 ++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ba3f01a688..7ec108e3ec 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -111,11 +111,7 @@ namespace osu.Game.Overlays.Mods private readonly Bindable>> globalAvailableMods = new Bindable>>(); - public IEnumerable AllAvailableAndValidMods => allAvailableMods - .Select(s => s.Mod) - .Where(m => isValidMod(m)); - - private IEnumerable allAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); + public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); private readonly BindableBool customisationVisible = new BindableBool(); @@ -386,7 +382,7 @@ namespace osu.Game.Overlays.Mods private void filterMods() { - foreach (var modState in allAvailableMods) + foreach (var modState in AllAvailableMods) modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } @@ -411,7 +407,7 @@ namespace osu.Game.Overlays.Mods bool anyCustomisableModActive = false; bool anyModPendingConfiguration = false; - foreach (var modState in allAvailableMods) + foreach (var modState in AllAvailableMods) { anyCustomisableModActive |= modState.Active.Value && modState.Mod.GetSettingsSourceProperties().Any(); anyModPendingConfiguration |= modState.PendingConfiguration; @@ -468,7 +464,7 @@ namespace osu.Game.Overlays.Mods var newSelection = new List(); - foreach (var modState in allAvailableMods) + foreach (var modState in AllAvailableMods) { var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); @@ -495,7 +491,7 @@ namespace osu.Game.Overlays.Mods if (externalSelectionUpdateInProgress) return; - var candidateSelection = allAvailableMods.Where(modState => modState.Active.Value) + var candidateSelection = AllAvailableMods.Where(modState => modState.Active.Value) .Select(modState => modState.Mod) .ToArray(); diff --git a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs index 56a69be741..294c80677d 100644 --- a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs +++ b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.OnlinePlay /// private void toggleAllFreeMods() { - var availableMods = freeModSelectOverlay.AllAvailableAndValidMods.ToArray(); + var availableMods = allAvailableAndValidMods.ToArray(); Current.Value = Current.Value.Count == availableMods.Length ? Array.Empty() @@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay { int current = Current.Value.Count; - if (current == freeModSelectOverlay.AllAvailableAndValidMods.Count()) + if (current == allAvailableAndValidMods.Count()) { count.Text = "all"; circle.FadeColour(colours.Yellow, 200, Easing.OutQuint); @@ -124,5 +124,9 @@ namespace osu.Game.Screens.OnlinePlay circle.FadeColour(colours.Gray4, 200, Easing.OutQuint); } } + + private IEnumerable allAvailableAndValidMods => freeModSelectOverlay.AllAvailableMods + .Where(state => state.ValidForSelection.Value) + .Select(state => state.Mod); } } From c93d6a4008639b68a9f6508d6b5e46f9e800df9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jul 2023 13:04:21 +0900 Subject: [PATCH 043/136] Invert text colour when freemods is enabled for better contrast --- osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs index 294c80677d..3825cf18b9 100644 --- a/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs +++ b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs @@ -111,16 +111,19 @@ namespace osu.Game.Screens.OnlinePlay if (current == allAvailableAndValidMods.Count()) { count.Text = "all"; + count.FadeColour(colours.Gray2, 200, Easing.OutQuint); circle.FadeColour(colours.Yellow, 200, Easing.OutQuint); } else if (current > 0) { count.Text = $"{current} mods"; + count.FadeColour(colours.Gray2, 200, Easing.OutQuint); circle.FadeColour(colours.YellowDark, 200, Easing.OutQuint); } else { count.Text = "off"; + count.FadeColour(colours.GrayF, 200, Easing.OutQuint); circle.FadeColour(colours.Gray4, 200, Easing.OutQuint); } } From d9d055361aa956e5233d38c689aa9077033d104a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jul 2023 17:49:23 +0900 Subject: [PATCH 044/136] More realm analytic disables --- .gitignore | 1 - osu.Game/FodyWeavers.xml | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game/FodyWeavers.xml diff --git a/.gitignore b/.gitignore index 0c7a18b437..525b3418cd 100644 --- a/.gitignore +++ b/.gitignore @@ -339,6 +339,5 @@ inspectcode # Fody (pulled in by Realm) - schema file FodyWeavers.xsd -**/FodyWeavers.xml .idea/.idea.osu.Desktop/.idea/misc.xml \ No newline at end of file diff --git a/osu.Game/FodyWeavers.xml b/osu.Game/FodyWeavers.xml new file mode 100644 index 0000000000..7ff486f40c --- /dev/null +++ b/osu.Game/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + From f791f21dcb3bc22c439838f2f7bee5d84310b757 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jul 2023 20:05:35 +0900 Subject: [PATCH 045/136] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 059c5f26e7..ed97f609cc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + - true $(NoWarn);MT7091 + + + true + + + + false + -all + ios-arm64 From 97075b0726f82cec03e4bb5a533e9e6d11bc8c1b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jul 2023 20:31:26 +0300 Subject: [PATCH 110/136] Fix iOS visual tests having unusual bundle identifiers --- osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 4 ++-- osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 4 ++-- osu.Game.Tests.iOS/Info.plist | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 5ace6c07f5..f87043e1d1 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -5,7 +5,7 @@ CFBundleName osu.Game.Rulesets.Catch.Tests.iOS CFBundleIdentifier - ppy.osu-Game-Rulesets-Catch-Tests-iOS + sh.ppy.catch-ruleset-tests CFBundleShortVersionString 1.0 CFBundleVersion @@ -42,4 +42,4 @@ CADisableMinimumFrameDurationOnPhone - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index ff5dde856e..740036309f 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -5,7 +5,7 @@ CFBundleName osu.Game.Rulesets.Mania.Tests.iOS CFBundleIdentifier - ppy.osu-Game-Rulesets-Mania-Tests-iOS + sh.ppy.mania-ruleset-tests CFBundleShortVersionString 1.0 CFBundleVersion @@ -42,4 +42,4 @@ CADisableMinimumFrameDurationOnPhone - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index 1e33f2ff16..7f489874e7 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -5,7 +5,7 @@ CFBundleName osu.Game.Rulesets.Osu.Tests.iOS CFBundleIdentifier - ppy.osu-Game-Rulesets-Osu-Tests-iOS + sh.ppy.osu-ruleset-tests CFBundleShortVersionString 1.0 CFBundleVersion @@ -42,4 +42,4 @@ CADisableMinimumFrameDurationOnPhone - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index 76cb3c0db0..162ee75c22 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -5,7 +5,7 @@ CFBundleName osu.Game.Rulesets.Taiko.Tests.iOS CFBundleIdentifier - ppy.osu-Game-Rulesets-Taiko-Tests-iOS + sh.ppy.taiko-ruleset-tests CFBundleShortVersionString 1.0 CFBundleVersion @@ -42,4 +42,4 @@ CADisableMinimumFrameDurationOnPhone - + \ No newline at end of file diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index ac661f6263..d2d0583e46 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -5,7 +5,7 @@ CFBundleName osu.Game.Tests.iOS CFBundleIdentifier - ppy.osu-Game-Tests-iOS + sh.ppy.osu-tests CFBundleShortVersionString 1.0 CFBundleVersion @@ -42,4 +42,4 @@ CADisableMinimumFrameDurationOnPhone - + \ No newline at end of file From bf89fbcd817aab251797b40d8a4942103c360109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jul 2023 16:38:37 +0900 Subject: [PATCH 111/136] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index ed97f609cc..7f15d9fafd 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - +