From e6f39c4cada3e180d524be7c5fc0a8d3e7e6b206 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 8 Nov 2021 21:38:01 +0100 Subject: [PATCH 01/41] Fix settings header text overflowing with some locales. --- osu.Game/Overlays/Settings/SettingsHeader.cs | 43 ++++++++------------ 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index 69b7b69a29..8d6e93be9d 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings { @@ -29,36 +29,27 @@ namespace osu.Game.Overlays.Settings Children = new Drawable[] { - new FillFlowContainer + new OsuTextFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] + Margin = new MarginPadding { - new OsuSpriteText - { - Text = heading, - Font = OsuFont.TorusAlternate.With(size: 40), - Margin = new MarginPadding - { - Left = SettingsPanel.CONTENT_MARGINS, - Top = Toolbar.Toolbar.TOOLTIP_HEIGHT - }, - }, - new OsuSpriteText - { - Colour = colourProvider.Content2, - Text = subheading, - Font = OsuFont.GetFont(size: 18), - Margin = new MarginPadding - { - Left = SettingsPanel.CONTENT_MARGINS, - Bottom = 30 - }, - }, + Left = SettingsPanel.CONTENT_MARGINS, + Top = Toolbar.Toolbar.TOOLTIP_HEIGHT, + Bottom = 30 } - } + }.With(flow => + { + flow.AddText(heading, header => header.Font = OsuFont.TorusAlternate.With(size: 40)); + flow.NewLine(); + flow.AddText(subheading, subheader => + { + subheader.Colour = colourProvider.Content2; + subheader.Font = OsuFont.GetFont(size: 18); + }); + + }) }; } } From b7e7b0f850766a568e1e08aa985826402f27d1cb Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 8 Nov 2021 21:42:51 +0100 Subject: [PATCH 02/41] Trim whitespace. --- osu.Game/Overlays/Settings/SettingsHeader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index 8d6e93be9d..53b3895390 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -48,7 +48,6 @@ namespace osu.Game.Overlays.Settings subheader.Colour = colourProvider.Content2; subheader.Font = OsuFont.GetFont(size: 18); }); - }) }; } From 6dc13ef396e8dce1198200cc2b61fd195977a340 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:07:06 +0900 Subject: [PATCH 03/41] Use default hover sample instead of song-ping (they're the same sample anyway) --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 40ca3e0764..ed3aea3445 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, }; - sampleHover = audio.Samples.Get("SongSelect/song-ping"); + sampleHover = audio.Samples.Get("UI/default-hover"); } public bool InsetForBorder From 8d0f5b0d8201c0a382816ffcd52d2f97eec29e93 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:08:51 +0900 Subject: [PATCH 04/41] Add high-pass filter when song selection is confirmed --- osu.Game/Screens/Play/PlayerLoader.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d852ac2940..4e5ccde579 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; using JetBrains.Annotations; +using ManagedBass.Fx; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -67,6 +68,7 @@ namespace osu.Game.Screens.Play private readonly BindableDouble volumeAdjustment = new BindableDouble(1); private AudioFilter lowPassFilter; + private AudioFilter highPassFilter; protected bool BackgroundBrightnessReduction { @@ -168,7 +170,8 @@ namespace osu.Game.Screens.Play }, idleTracker = new IdleTracker(750), }), - lowPassFilter = new AudioFilter(audio.TrackMixer) + lowPassFilter = new AudioFilter(audio.TrackMixer), + highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass) }; if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) @@ -239,6 +242,7 @@ namespace osu.Game.Screens.Play Beatmap.Value.Track.Stop(); Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); + highPassFilter.CutoffTo(0); } public override bool OnExiting(IScreen next) @@ -352,6 +356,7 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); + highPassFilter.CutoffTo(300); ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } @@ -364,6 +369,7 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint); content.FadeOut(content_out_duration, Easing.OutQuint); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration); + highPassFilter.CutoffTo(0, content_out_duration); } private void pushWhenLoaded() From 5f462b6441c25cc30069ca1f13a02d2aa42de936 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:12:49 +0900 Subject: [PATCH 05/41] Move beatmap/difficulty change sample playback to outside of debounce --- osu.Game/Screens/Select/SongSelect.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a2dea355ac..4943aac77c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -435,6 +435,7 @@ namespace osu.Game.Screens.Select } // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. + private BeatmapInfo beatmapInfoPrevious; private BeatmapInfo beatmapInfoNoDebounce; private RulesetInfo rulesetNoDebounce; @@ -477,6 +478,19 @@ namespace osu.Game.Screens.Select else selectionChangedDebounce = Scheduler.AddDelayed(run, 200); + if (beatmap != beatmapInfoPrevious) + { + if (beatmap != null && beatmapInfoPrevious != null) + { + if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID) + sampleChangeDifficulty.Play(); + else + sampleChangeBeatmap.Play(); + } + + beatmapInfoPrevious = beatmap; + } + void run() { // clear pending task immediately to track any potential nested debounce operation. @@ -508,18 +522,7 @@ namespace osu.Game.Screens.Select if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - - int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); - - if (beatmap != null) - { - if (beatmap.BeatmapSetInfoID == lastSetID) - sampleChangeDifficulty.Play(); - else - sampleChangeBeatmap.Play(); - } } if (this.IsCurrentScreen()) From b13d020a499dee4f34becb6dc4e2be9a0fc37a05 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:13:47 +0900 Subject: [PATCH 06/41] Don't re-use confim-selection sample for the skip intro button --- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index b04fcba0c6..c35548c6b4 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Play colourNormal = colours.Yellow; colourHover = colours.YellowDark; - sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); + sampleConfirm = audio.Samples.Get(@"UI/submit-select"); Children = new Drawable[] { From e33c1f9a41df5ea52373ffc453a05fec8056c135 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Nov 2021 11:00:55 +0900 Subject: [PATCH 07/41] Lower filter cutoff frequency --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 4e5ccde579..734caa43d4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -356,7 +356,7 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); - highPassFilter.CutoffTo(300); + highPassFilter.CutoffTo(150); ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } From 069ee6980f4297e0bec727ac0949e2a5eac3277f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Nov 2021 19:20:50 +0900 Subject: [PATCH 08/41] Add debounce to sample playback --- osu.Game/Screens/Select/SongSelect.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4943aac77c..4da15ee53c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -105,6 +105,8 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); + private double audioFeedbackLastPlaybackTime; + [Resolved] private MusicController music { get; set; } @@ -480,12 +482,14 @@ namespace osu.Game.Screens.Select if (beatmap != beatmapInfoPrevious) { - if (beatmap != null && beatmapInfoPrevious != null) + if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); + + audioFeedbackLastPlaybackTime = Time.Current; } beatmapInfoPrevious = beatmap; From 113c95f3f55104856c44fc2a0ee0ba82f747f168 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 12 Nov 2021 14:22:43 +0900 Subject: [PATCH 09/41] Only apply high-pass temporarily --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 734caa43d4..fd54c10d86 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -356,7 +356,7 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); - highPassFilter.CutoffTo(150); + highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } From 9fc4bb70553dda53c8b56e1cdeaea39e6f193f32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 15:09:01 +0900 Subject: [PATCH 10/41] Fix incorrect xmldoc --- osu.Game/IO/Archives/LegacyByteArrayReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/Archives/LegacyByteArrayReader.cs b/osu.Game/IO/Archives/LegacyByteArrayReader.cs index 0c3620403f..ea8ff3bbe0 100644 --- a/osu.Game/IO/Archives/LegacyByteArrayReader.cs +++ b/osu.Game/IO/Archives/LegacyByteArrayReader.cs @@ -7,7 +7,7 @@ using System.IO; namespace osu.Game.IO.Archives { /// - /// Allows reading a single file from the provided stream. + /// Allows reading a single file from the provided byte array. /// public class LegacyByteArrayReader : ArchiveReader { From adf81d7fcd024e30a943a59d3f7ed9cc3584c3c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 16:39:17 +0900 Subject: [PATCH 11/41] Add pathway to correctly handle stream-based imports which are not zip archives --- osu.Game/Database/ImportTask.cs | 26 +++++++++++++++++++++++--- osu.Game/Utils/ZipUtils.cs | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 1433a567a9..1fb5a42630 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -47,10 +47,30 @@ namespace osu.Game.Database /// public ArchiveReader GetReader() { - if (Stream != null) - return new ZipArchiveReader(Stream, Path); + return Stream != null + ? getReaderFrom(Stream) + : getReaderFrom(Path); + } - return getReaderFrom(Path); + /// + /// Creates an from a stream. + /// + /// A seekable stream containing the archive content. + /// A reader giving access to the archive's content. + private ArchiveReader getReaderFrom(Stream stream) + { + if (!(stream is MemoryStream memoryStream)) + { + // This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out). + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, (int)stream.Length); + memoryStream = new MemoryStream(buffer); + } + + if (ZipUtils.IsZipArchive(memoryStream)) + return new ZipArchiveReader(memoryStream, Path); + + return new LegacyByteArrayReader(memoryStream.ToArray(), Path); } /// diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index cd4d876451..16eb1c7e4e 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -9,6 +9,34 @@ namespace osu.Game.Utils { public static class ZipUtils { + public static bool IsZipArchive(Stream stream) + { + try + { + stream.Seek(0, SeekOrigin.Begin); + + using (var arc = ZipArchive.Open(stream)) + { + foreach (var entry in arc.Entries) + { + using (entry.OpenEntryStream()) + { + } + } + } + + return true; + } + catch (Exception) + { + return false; + } + finally + { + stream.Seek(0, SeekOrigin.Begin); + } + } + public static bool IsZipArchive(string path) { if (!File.Exists(path)) From f02b57c37121a77523e4c54aa1a2fa5fc14a3cc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 19:16:22 +0900 Subject: [PATCH 12/41] Limit new `IsZipArchive` method to `MemoryStream` for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Utils/ZipUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index 16eb1c7e4e..eb2d2d3b80 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -9,7 +9,7 @@ namespace osu.Game.Utils { public static class ZipUtils { - public static bool IsZipArchive(Stream stream) + public static bool IsZipArchive(MemoryStream stream) { try { From 338e5a78b89ca0153d4acc7877b1ce1a6906c698 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 19:30:04 +0900 Subject: [PATCH 13/41] Adjust easing of logo to better match the sound I'm sure we can come up with something better, but giving it a bit more speed definitely feels closer to what the sound is portraying. --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fd54c10d86..ba34df0e59 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -270,9 +270,9 @@ namespace osu.Game.Screens.Play const double duration = 300; - if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In); + if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint); - logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); + logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint); logo.FadeIn(350); Scheduler.AddDelayed(() => From d2c1bc107218ef2447dd54b10069a11f5c89666e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 19:33:35 +0900 Subject: [PATCH 14/41] Update resources --- 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 752eb160ed..f7b7b6fb23 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index df3c9b355a..93fb729f46 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1852957e87..3a2a3fd65e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 9000d19c9eedca47292e4f283fdd0ab48a8dd9f3 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Fri, 12 Nov 2021 14:58:18 +0100 Subject: [PATCH 15/41] Update difficulty colour spectrum --- osu.Game/Graphics/OsuColour.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 3aa4dbf1d8..d3afb21933 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -52,15 +52,18 @@ namespace osu.Game.Graphics public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[] { - (1.5f, Color4Extensions.FromHex("4fc0ff")), + (0.1f, Color4Extensions.FromHex("aaaaaa")), + (0.1f, Color4Extensions.FromHex("4290fb")), + (1.25f, Color4Extensions.FromHex("4fc0ff")), (2.0f, Color4Extensions.FromHex("4fffd5")), (2.5f, Color4Extensions.FromHex("7cff4f")), - (3.25f, Color4Extensions.FromHex("f6f05c")), - (4.5f, Color4Extensions.FromHex("ff8068")), - (6.0f, Color4Extensions.FromHex("ff3c71")), - (7.0f, Color4Extensions.FromHex("6563de")), - (8.0f, Color4Extensions.FromHex("18158e")), - (8.0f, Color4.Black), + (3.3f, Color4Extensions.FromHex("f6f05c")), + (4.2f, Color4Extensions.FromHex("ff8068")), + (4.9f, Color4Extensions.FromHex("ff4e6f")), + (5.8f, Color4Extensions.FromHex("c645b8")), + (6.7f, Color4Extensions.FromHex("6563de")), + (7.7f, Color4Extensions.FromHex("18158e")), + (9.0f, Color4.Black), }, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero)); /// From cb2d1f3f04ae864b330bd2470e5769bb6839536b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 20:28:42 +0100 Subject: [PATCH 16/41] Use horizontally symmetrical padding rather than margin --- osu.Game/Overlays/Settings/SettingsHeader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index 53b3895390..f9ee8df0bd 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -33,9 +33,9 @@ namespace osu.Game.Overlays.Settings { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding + Padding = new MarginPadding { - Left = SettingsPanel.CONTENT_MARGINS, + Horizontal = SettingsPanel.CONTENT_MARGINS, Top = Toolbar.Toolbar.TOOLTIP_HEIGHT, Bottom = 30 } From 7ba93aac2799be99595ef4d49db3266c60b15d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 21:36:03 +0100 Subject: [PATCH 17/41] Add test coverage for difficulty point piece operation --- ...ceneHitObjectDifficultyPointAdjustments.cs | 123 ++++++++++++++++++ .../Timeline/DifficultyPointPiece.cs | 6 +- 2 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs new file mode 100644 index 0000000000..bbae10e5ff --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -0,0 +1,123 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Humanizer; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneHitObjectDifficultyPointAdjustments : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("add test objects", () => + { + EditorBeatmap.Add(new Slider + { + StartTime = 0, + Position = (OsuPlayfield.BASE_SIZE - new Vector2(0, 100)) / 2, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(0, 100)) + } + } + }); + + EditorBeatmap.Add(new Slider + { + StartTime = 500, + Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(100, 0)) + } + }, + DifficultyControlPoint = new DifficultyControlPoint + { + SliderVelocity = 2 + } + }); + }); + } + + [Test] + public void TestSingleSelection() + { + clickDifficultyPiece(0); + velocityPopoverHasSingleValue(1); + + dismissPopover(); + + // select first object to ensure that difficulty pieces for unselected objects + // work independently from selection state. + AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); + + clickDifficultyPiece(1); + velocityPopoverHasSingleValue(2); + + setVelocityViaPopover(5); + hitObjectHasVelocity(1, 5); + } + + private void clickDifficultyPiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => + { + var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); + + InputManager.MoveMouseTo(difficultyPiece); + InputManager.Click(MouseButton.Left); + }); + + private void velocityPopoverHasSingleValue(double velocity) => AddUntilStep($"velocity popover has {velocity}", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider?.Current.Value == velocity; + }); + + private void dismissPopover() + { + AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); + AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); + } + + private void setVelocityViaPopover(double velocity) => AddStep($"set {velocity} via popover", () => + { + var popover = this.ChildrenOfType().Single(); + var slider = popover.ChildrenOfType>().Single(); + slider.Current.Value = velocity; + }); + + private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.DifficultyControlPoint.SliderVelocity == velocity; + }); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 21457ea273..ae1884d295 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -19,14 +19,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover { - private readonly HitObject hitObject; + public readonly HitObject HitObject; private readonly BindableNumber speedMultiplier; public DifficultyPointPiece(HitObject hitObject) : base(hitObject.DifficultyControlPoint) { - this.hitObject = hitObject; + HitObject = hitObject; speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy(); } @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } - public Popover GetPopover() => new DifficultyEditPopover(hitObject); + public Popover GetPopover() => new DifficultyEditPopover(HitObject); public class DifficultyEditPopover : OsuPopover { From b8b86cbd2ae50baafae55158e9bb65187a76467c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 21:41:17 +0100 Subject: [PATCH 18/41] Add failing tests for desired multiple selection behaviour --- ...ceneHitObjectDifficultyPointAdjustments.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index bbae10e5ff..a678582dd6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -85,6 +85,46 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasVelocity(1, 5); } + [Test] + public void TestMultipleSelectionWithSameSliderVelocity() + { + AddStep("unify slider velocity", () => + { + foreach (var h in EditorBeatmap.HitObjects) + h.DifficultyControlPoint.SliderVelocity = 1.5; + }); + + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickDifficultyPiece(0); + velocityPopoverHasSingleValue(1.5); + + dismissPopover(); + + clickDifficultyPiece(1); + velocityPopoverHasSingleValue(1.5); + + setVelocityViaPopover(5); + hitObjectHasVelocity(0, 5); + hitObjectHasVelocity(1, 5); + } + + [Test] + public void TestMultipleSelectionWithDifferentSliderVelocity() + { + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickDifficultyPiece(0); + velocityPopoverHasIndeterminateValue(); + + dismissPopover(); + + clickDifficultyPiece(1); + velocityPopoverHasIndeterminateValue(); + + setVelocityViaPopover(3); + hitObjectHasVelocity(0, 3); + hitObjectHasVelocity(1, 3); + } + private void clickDifficultyPiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => { var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); @@ -101,6 +141,14 @@ namespace osu.Game.Tests.Visual.Editing return slider?.Current.Value == velocity; }); + private void velocityPopoverHasIndeterminateValue() => AddUntilStep("velocity popover has indeterminate value", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider != null && slider.Current.Value == null; + }); + private void dismissPopover() { AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); From e55e2a1697aa7fe18050f61cfc3232f34855c0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 22:54:27 +0100 Subject: [PATCH 19/41] Allow to adjust slider velocity on multiple objects simultaneously --- ...ceneHitObjectDifficultyPointAdjustments.cs | 6 +- .../Timeline/DifficultyPointPiece.cs | 47 ++++--- .../IndeterminateSliderWithTextBoxInput.cs | 115 ++++++++++++++++++ 3 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index a678582dd6..4012a672ed 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Editing private void velocityPopoverHasSingleValue(double velocity) => AddUntilStep($"velocity popover has {velocity}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider?.Current.Value == velocity; }); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Editing private void velocityPopoverHasIndeterminateValue() => AddUntilStep("velocity popover has indeterminate value", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider != null && slider.Current.Value == null; }); @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Editing private void setVelocityViaPopover(double velocity) => AddStep($"set {velocity} via popover", () => { var popover = this.ChildrenOfType().Single(); - var slider = popover.ChildrenOfType>().Single(); + var slider = popover.ChildrenOfType>().Single(); slider.Current.Value = velocity; }); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index ae1884d295..76a8243e05 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -49,9 +51,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class DifficultyEditPopover : OsuPopover { private readonly HitObject hitObject; - private readonly DifficultyControlPoint point; - private SliderWithTextBoxInput sliderVelocitySlider; + private IndeterminateSliderWithTextBoxInput sliderVelocitySlider; [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } @@ -59,7 +60,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public DifficultyEditPopover(HitObject hitObject) { this.hitObject = hitObject; - point = hitObject.DifficultyControlPoint; } [BackgroundDependencyLoader] @@ -74,9 +74,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.Y, Children = new Drawable[] { - sliderVelocitySlider = new SliderWithTextBoxInput("Velocity") + sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new DifficultyControlPoint().SliderVelocityBindable) { - Current = new DifficultyControlPoint().SliderVelocityBindable, KeyboardStep = 0.1f }, new OsuTextFlowContainer @@ -89,17 +88,37 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; - var selectedPointBindable = point.SliderVelocityBindable; + // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. + // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. + var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); + var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray(); - // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). - // generally that level of precision could only be set by externally editing the .osu file, so at the point - // a user is looking to update this within the editor it should be safe to obliterate this additional precision. - double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision; - if (selectedPointBindable.Precision < expectedPrecision) - selectedPointBindable.Precision = expectedPrecision; + // even if there are multiple objects selected, we can still display a value if they all have the same value. + var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null; - sliderVelocitySlider.Current = selectedPointBindable; - sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject)); + if (selectedPointBindable != null) + { + // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). + // generally that level of precision could only be set by externally editing the .osu file, so at the point + // a user is looking to update this within the editor it should be safe to obliterate this additional precision. + sliderVelocitySlider.Current.Value = selectedPointBindable.Value; + } + + sliderVelocitySlider.Current.BindValueChanged(val => + { + if (val.NewValue == null) + return; + + beatmap.BeginChange(); + + foreach (var h in relevantObjects) + { + h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value; + beatmap.Update(h); + } + + beatmap.EndChange(); + }); } } } diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs new file mode 100644 index 0000000000..a5c682e56a --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -0,0 +1,115 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Screens.Edit.Timing +{ + /// + /// Analogous to , but supports scenarios + /// where multiple objects with multiple different property values are selected + /// by providing an "indeterminate state". + /// + public class IndeterminateSliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue + where T : struct, IEquatable, IComparable, IConvertible + { + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly SettingsSlider slider; + private readonly LabelledTextBox textbox; + + /// + /// Creates an . + /// + /// The label text for the slider and text box. + /// + /// Bindable to use for the slider until a non-null value is set for . + /// In particular, it can be used to control min/max bounds and precision in the case of s. + /// + public IndeterminateSliderWithTextBoxInput(LocalisableString labelText, Bindable indeterminateValue) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + textbox = new LabelledTextBox + { + Label = labelText, + PlaceholderText = "(multiple)" + }, + slider = new SettingsSlider + { + TransferValueOnCommit = true, + RelativeSizeAxes = Axes.X, + Current = indeterminateValue + } + } + }, + }; + + textbox.OnCommit += (t, isNew) => + { + if (!isNew) return; + + try + { + slider.Current.Parse(t.Text); + } + catch + { + // TriggerChange below will restore the previous text value on failure. + } + + // This is run regardless of parsing success as the parsed number may not actually trigger a change + // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. + Current.TriggerChange(); + }; + slider.Current.BindValueChanged(val => Current.Value = val.NewValue); + + Current.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + if (Current.Value is T nonNullValue) + { + slider.Current.Value = nonNullValue; + textbox.Text = slider.Current.ToString(); + } + else + { + textbox.Text = null; + } + } + } +} From d567d2be973cd860122454bfbb64b7150ef0a8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 23:07:24 +0100 Subject: [PATCH 20/41] Fix multiple issues with textbox content display - Sometimes would display too many decimal digits due to floating point representation errors. - Placeholder would also look wrong if text was removed during a multiple (but determinate) selection. --- .../Graphics/UserInterface/OsuSliderBar.cs | 21 ++----------------- .../IndeterminateSliderWithTextBoxInput.cs | 10 +++++++-- osu.Game/Utils/FormatUtils.cs | 18 ++++++++++++++++ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index d4310dc901..333ae4f832 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -19,6 +19,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Overlays; +using osu.Game.Utils; namespace osu.Game.Graphics.UserInterface { @@ -219,7 +220,7 @@ namespace osu.Game.Graphics.UserInterface decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); // Find the number of significant digits (we could have less than 5 after normalize()) - int significantDigits = findPrecision(decimalPrecision); + int significantDigits = FormatUtils.FindPrecision(decimalPrecision); TooltipText = floatValue.ToString($"N{significantDigits}"); } @@ -248,23 +249,5 @@ namespace osu.Game.Graphics.UserInterface /// The normalised decimal. private decimal normalise(decimal d, int sd) => decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); - - /// - /// Finds the number of digits after the decimal. - /// - /// The value to find the number of decimal digits for. - /// The number decimal digits. - private int findPrecision(decimal d) - { - int precision = 0; - - while (d != Math.Round(d)) - { - d *= 10; - precision++; - } - - return precision; - } } } diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index a5c682e56a..17f82f4978 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Timing { @@ -65,7 +67,6 @@ namespace osu.Game.Screens.Edit.Timing textbox = new LabelledTextBox { Label = labelText, - PlaceholderText = "(multiple)" }, slider = new SettingsSlider { @@ -104,11 +105,16 @@ namespace osu.Game.Screens.Edit.Timing if (Current.Value is T nonNullValue) { slider.Current.Value = nonNullValue; - textbox.Text = slider.Current.ToString(); + + // use the value from the slider to ensure that any precision/min/max set on it via the initial indeterminate value have been applied correctly. + decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); + textbox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); + textbox.PlaceholderText = string.Empty; } else { textbox.Text = null; + textbox.PlaceholderText = "(multiple)"; } } } diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index d14dbb49f3..799dc75ca9 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -31,5 +31,23 @@ namespace osu.Game.Utils /// /// The rank/position to be formatted. public static string FormatRank(this int rank) => rank.ToMetric(decimals: rank < 100_000 ? 1 : 0); + + /// + /// Finds the number of digits after the decimal. + /// + /// The value to find the number of decimal digits for. + /// The number decimal digits. + public static int FindPrecision(decimal d) + { + int precision = 0; + + while (d != Math.Round(d)) + { + d *= 10; + precision++; + } + + return precision; + } } } From e1c28ddd7617d15f466b6c7546b1e1ca353c85d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 23:16:45 +0100 Subject: [PATCH 21/41] Adjust difficulty point popover content spacing --- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 76a8243e05..b230bab0c2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -72,6 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Width = 200, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 15), Children = new Drawable[] { sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new DifficultyControlPoint().SliderVelocityBindable) From a5ba3bd012c95a6ab39ffcf3274a0ac74590efa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:07:38 +0100 Subject: [PATCH 22/41] Move gameplay test pieces to own namespace --- osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/{ => GameplayTest}/EditorPlayer.cs | 2 +- .../Edit/{ => GameplayTest}/SaveBeforeGameplayTestDialog.cs | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) rename osu.Game/Screens/Edit/{ => GameplayTest}/EditorPlayer.cs (96%) rename osu.Game/Screens/Edit/{ => GameplayTest}/SaveBeforeGameplayTestDialog.cs (95%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index db42667033..e311c7a7ec 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 738f607cc8..0c54e39a35 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,6 +31,7 @@ using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Design; +using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; diff --git a/osu.Game/Screens/Edit/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs similarity index 96% rename from osu.Game/Screens/Edit/EditorPlayer.cs rename to osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index b2fab3fefc..9856ad62bb 100644 --- a/osu.Game/Screens/Edit/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -6,7 +6,7 @@ using osu.Framework.Screens; using osu.Game.Overlays; using osu.Game.Screens.Play; -namespace osu.Game.Screens.Edit +namespace osu.Game.Screens.Edit.GameplayTest { public class EditorPlayer : Player { diff --git a/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs b/osu.Game/Screens/Edit/GameplayTest/SaveBeforeGameplayTestDialog.cs similarity index 95% rename from osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs rename to osu.Game/Screens/Edit/GameplayTest/SaveBeforeGameplayTestDialog.cs index 7c03664c66..9334c74706 100644 --- a/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs +++ b/osu.Game/Screens/Edit/GameplayTest/SaveBeforeGameplayTestDialog.cs @@ -5,7 +5,7 @@ using System; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; -namespace osu.Game.Screens.Edit +namespace osu.Game.Screens.Edit.GameplayTest { public class SaveBeforeGameplayTestDialog : PopupDialog { From b47c0b63f4305fe3de7bcaf0d142825ddf3b3ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:30:40 +0100 Subject: [PATCH 23/41] Tweak loader transition when testing gameplay in editor --- osu.Game/Screens/Edit/Editor.cs | 2 +- .../Edit/GameplayTest/EditorPlayerLoader.cs | 44 +++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 26 ++++++----- 3 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0c54e39a35..2c758eaf41 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -798,7 +798,7 @@ namespace osu.Game.Screens.Edit pushEditorPlayer(); } - void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer())); + void pushEditorPlayer() => this.Push(new EditorPlayerLoader()); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs new file mode 100644 index 0000000000..610fff70f2 --- /dev/null +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs @@ -0,0 +1,44 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.Edit.GameplayTest +{ + public class EditorPlayerLoader : PlayerLoader + { + [Resolved] + private OsuLogo osuLogo { get; set; } + + public EditorPlayerLoader() + : base(() => new EditorPlayer()) + { + } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + MetadataInfo.FinishTransforms(true); + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + // call base with resuming forcefully set to true to reduce logo movements. + base.LogoArriving(logo, true); + logo.FinishTransforms(true, nameof(Scale)); + } + + protected override void ContentOut() + { + base.ContentOut(); + osuLogo.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint); + } + + protected override double PlayerPushDelay => 0; + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ba34df0e59..57db411571 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -36,7 +36,9 @@ namespace osu.Game.Screens.Play { protected const float BACKGROUND_BLUR = 15; - private const double content_out_duration = 300; + protected const double CONTENT_OUT_DURATION = 300; + + protected virtual double PlayerPushDelay => 1800; public override bool HideOverlaysOnEnter => hideOverlays; @@ -213,7 +215,7 @@ namespace osu.Game.Screens.Play // after an initial delay, start the debounced load check. // this will continue to execute even after resuming back on restart. - Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0)); + Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + PlayerPushDelay, 0)); showMuteWarningIfNeeded(); showBatteryWarningIfNeeded(); @@ -248,13 +250,13 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { cancelLoad(); - contentOut(); + ContentOut(); // If the load sequence was interrupted, the epilepsy warning may already be displayed (or in the process of being displayed). epilepsyWarning?.Hide(); // Ensure the screen doesn't expire until all the outwards fade operations have completed. - this.Delay(content_out_duration).FadeOut(); + this.Delay(CONTENT_OUT_DURATION).FadeOut(); ApplyToBackground(b => b.IgnoreUserSettings.Value = true); @@ -361,15 +363,15 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } - private void contentOut() + protected virtual void ContentOut() { // Ensure the logo is no longer tracking before we scale the content content.StopTracking(); - content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint); - content.FadeOut(content_out_duration, Easing.OutQuint); - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration); - highPassFilter.CutoffTo(0, content_out_duration); + content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint); + content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint); + lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION); + highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION); } private void pushWhenLoaded() @@ -394,9 +396,9 @@ namespace osu.Game.Screens.Play // ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared). var consumedPlayer = consumePlayer(); - contentOut(); + ContentOut(); - TransformSequence pushSequence = this.Delay(content_out_duration); + TransformSequence pushSequence = this.Delay(CONTENT_OUT_DURATION); // only show if the warning was created (i.e. the beatmap needs it) // and this is not a restart of the map (the warning expires after first load). @@ -418,7 +420,7 @@ namespace osu.Game.Screens.Play else { // This goes hand-in-hand with the restoration of low pass filter in contentOut(). - this.TransformBindableTo(volumeAdjustment, 0, content_out_duration, Easing.OutCubic); + this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic); } pushSequence.Schedule(() => From eb8c5292d5db823f1f99947ca936d3e8d3e722f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 13:50:57 +0100 Subject: [PATCH 24/41] Ensure editor background is dimmed after return from gameplay test --- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 7 +++++++ osu.Game/Screens/Edit/Editor.cs | 13 +++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index e311c7a7ec..f52a764708 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -10,10 +10,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Tests.Beatmaps.IO; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -59,6 +61,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); AddStep("exit player", () => editorPlayer.Exit()); AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddUntilStep("background has correct params", () => + { + var background = this.ChildrenOfType().Single(); + return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; + }); } [Test] diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2c758eaf41..d97838e86d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -487,7 +487,18 @@ namespace osu.Game.Screens.Edit public override void OnEntering(IScreen last) { base.OnEntering(last); + dimBackground(); + resetTrack(true); + } + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + dimBackground(); + } + + private void dimBackground() + { ApplyToBackground(b => { // todo: temporary. we want to be applying dim using the UserDimContainer eventually. @@ -496,8 +507,6 @@ namespace osu.Game.Screens.Edit b.IgnoreUserSettings.Value = true; b.BlurAmount.Value = 0; }); - - resetTrack(true); } public override bool OnExiting(IScreen next) From 5f2a789a6d58b564d79b03aa48475a3c6b686450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 14:01:00 +0100 Subject: [PATCH 25/41] Ensure editor clock is stopped before testing gameplay --- .../Editing/TestSceneEditorTestGameplay.cs | 31 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 4 +-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index f52a764708..1d572772eb 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -68,6 +68,37 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestGameplayTestWhenTrackRunning() + { + AddStep("start track", () => EditorClock.Start()); + AddAssert("sample playback enabled", () => !Editor.SamplePlaybackDisabled.Value); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddAssert("editor track stopped", () => !EditorClock.IsRunning); + AddAssert("sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); + + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddUntilStep("background has correct params", () => + { + var background = this.ChildrenOfType().Single(); + return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; + }); + + AddStep("start track", () => EditorClock.Start()); + AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value); + } + [Test] public void TestCancelGameplayTestWithUnsavedChanges() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d97838e86d..ab24391bb4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -545,9 +545,9 @@ namespace osu.Game.Screens.Edit public override void OnSuspending(IScreen next) { - refetchBeatmap(); - base.OnSuspending(next); + clock.Stop(); + refetchBeatmap(); } private void refetchBeatmap() From 9800cd490384bf41bebb1fe0d105e3a7c7ae4f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 16:58:24 +0100 Subject: [PATCH 26/41] Add test coverage for sample control point piece operation --- ...estSceneHitObjectSamplePointAdjustments.cs | 139 ++++++++++++++++++ .../Components/Timeline/SamplePointPiece.cs | 6 +- 2 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs new file mode 100644 index 0000000000..24048e6052 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -0,0 +1,139 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Humanizer; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneHitObjectSamplePointAdjustments : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("add test objects", () => + { + EditorBeatmap.Add(new HitCircle + { + StartTime = 0, + Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2, + SampleControlPoint = new SampleControlPoint + { + SampleBank = "normal", + SampleVolume = 80 + } + }); + + EditorBeatmap.Add(new HitCircle() + { + StartTime = 500, + Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, + SampleControlPoint = new SampleControlPoint + { + SampleBank = "soft", + SampleVolume = 60 + } + }); + }); + } + + [Test] + public void TestSingleSelection() + { + clickSamplePiece(0); + samplePopoverHasSingleBank("normal"); + samplePopoverHasSingleVolume(80); + + dismissPopover(); + + // select first object to ensure that sample pieces for unselected objects + // work independently from selection state. + AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); + + clickSamplePiece(1); + samplePopoverHasSingleBank("soft"); + samplePopoverHasSingleVolume(60); + + setVolumeViaPopover(90); + hitObjectHasSampleVolume(1, 90); + + setBankViaPopover("drum"); + hitObjectHasSampleBank(1, "drum"); + } + + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => + { + var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); + + InputManager.MoveMouseTo(difficultyPiece); + InputManager.Click(MouseButton.Left); + }); + + private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider?.Current.Value == volume; + }); + + private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var textBox = popover?.ChildrenOfType().First(); + + return textBox?.Current.Value == bank; + }); + + private void dismissPopover() + { + AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); + AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); + } + + private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () => + { + var popover = this.ChildrenOfType().Single(); + var slider = popover.ChildrenOfType>().Single(); + slider.Current.Value = volume; + }); + + private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.SampleControlPoint.SampleVolume == volume; + }); + + private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => + { + var popover = this.ChildrenOfType().Single(); + var textBox = popover.ChildrenOfType().First(); + textBox.Current.Value = bank; + }); + + private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.SampleControlPoint.SampleBank == bank; + }); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6a26f69e41..6250a9ccb8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class SamplePointPiece : HitObjectPointPiece, IHasPopover { - private readonly HitObject hitObject; + public readonly HitObject HitObject; private readonly Bindable bank; private readonly BindableNumber volume; @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public SamplePointPiece(HitObject hitObject) : base(hitObject.SampleControlPoint) { - this.hitObject = hitObject; + HitObject = hitObject; volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy(); bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy(); } @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Label.Text = $"{bank.Value} {volume.Value}"; } - public Popover GetPopover() => new SampleEditPopover(hitObject); + public Popover GetPopover() => new SampleEditPopover(HitObject); public class SampleEditPopover : OsuPopover { From 6ba154129bdae1037e4a3ae305020b0193e949b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:02:55 +0100 Subject: [PATCH 27/41] Add failing test coverage for setting sample volume on multiple objects --- ...estSceneHitObjectSamplePointAdjustments.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 24048e6052..0c4056e90c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -80,6 +80,46 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasSampleBank(1, "drum"); } + [Test] + public void TestMultipleSelectionWithSameSampleVolume() + { + AddStep("unify sample volume", () => + { + foreach (var h in EditorBeatmap.HitObjects) + h.SampleControlPoint.SampleVolume = 50; + }); + + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasSingleVolume(50); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasSingleVolume(50); + + setVolumeViaPopover(75); + hitObjectHasSampleVolume(0, 75); + hitObjectHasSampleVolume(1, 75); + } + + [Test] + public void TestMultipleSelectionWithDifferentSampleVolume() + { + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasIndeterminateVolume(); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasIndeterminateVolume(); + + setVolumeViaPopover(30); + hitObjectHasSampleVolume(0, 30); + hitObjectHasSampleVolume(1, 30); + } + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => { var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); @@ -96,6 +136,14 @@ namespace osu.Game.Tests.Visual.Editing return slider?.Current.Value == volume; }); + private void samplePopoverHasIndeterminateVolume() => AddUntilStep($"sample popover has indeterminate volume", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider != null && slider.Current.Value == null; + }); + private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); From 9cf45e418ab78da6e12dd40665777a285cd88269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:07:19 +0100 Subject: [PATCH 28/41] Add failing test coverage for setting sample bank on multiple objects --- ...estSceneHitObjectSamplePointAdjustments.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 0c4056e90c..5b2b6fc780 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -120,6 +120,46 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasSampleVolume(1, 30); } + [Test] + public void TestMultipleSelectionWithSameSampleBank() + { + AddStep("unify sample bank", () => + { + foreach (var h in EditorBeatmap.HitObjects) + h.SampleControlPoint.SampleBank = "soft"; + }); + + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasSingleBank("soft"); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasSingleBank("soft"); + + setBankViaPopover("drum"); + hitObjectHasSampleBank(0, "drum"); + hitObjectHasSampleBank(1, "drum"); + } + + [Test] + public void TestMultipleSelectionWithDifferentSampleBank() + { + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasIndeterminateBank(); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasIndeterminateBank(); + + setBankViaPopover("normal"); + hitObjectHasSampleBank(0, "normal"); + hitObjectHasSampleBank(1, "normal"); + } + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => { var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); @@ -152,6 +192,14 @@ namespace osu.Game.Tests.Visual.Editing return textBox?.Current.Value == bank; }); + private void samplePopoverHasIndeterminateBank() => AddUntilStep($"sample popover has indeterminate bank", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var textBox = popover?.ChildrenOfType().First(); + + return textBox != null && textBox.Current.Value == null; + }); + private void dismissPopover() { AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); From 3fee6b0938261e62654a1b1e8ab677d68653e524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:21:48 +0100 Subject: [PATCH 29/41] Add support for setting sample bank & volume for multiple objects at once --- ...estSceneHitObjectSamplePointAdjustments.cs | 10 +-- .../Components/Timeline/SamplePointPiece.cs | 71 +++++++++++++++---- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 5b2b6fc780..f30c56d53c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -171,15 +171,15 @@ namespace osu.Game.Tests.Visual.Editing private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider?.Current.Value == volume; }); - private void samplePopoverHasIndeterminateVolume() => AddUntilStep($"sample popover has indeterminate volume", () => + private void samplePopoverHasIndeterminateVolume() => AddUntilStep("sample popover has indeterminate volume", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider != null && slider.Current.Value == null; }); @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.Editing var popover = this.ChildrenOfType().SingleOrDefault(); var textBox = popover?.ChildrenOfType().First(); - return textBox != null && textBox.Current.Value == null; + return textBox != null && string.IsNullOrEmpty(textBox.Current.Value); }); private void dismissPopover() @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Visual.Editing private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () => { var popover = this.ChildrenOfType().Single(); - var slider = popover.ChildrenOfType>().Single(); + var slider = popover.ChildrenOfType>().Single(); slider.Current.Value = volume; }); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6250a9ccb8..f0b11ce96e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -1,9 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -55,18 +60,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class SampleEditPopover : OsuPopover { private readonly HitObject hitObject; - private readonly SampleControlPoint point; - private LabelledTextBox bank; - private SliderWithTextBoxInput volume; + private LabelledTextBox bank = null!; + private IndeterminateSliderWithTextBoxInput volume = null!; [Resolved(canBeNull: true)] - private EditorBeatmap beatmap { get; set; } + private EditorBeatmap beatmap { get; set; } = null!; public SampleEditPopover(HitObject hitObject) { this.hitObject = hitObject; - point = hitObject.SampleControlPoint; } [BackgroundDependencyLoader] @@ -85,19 +88,61 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label = "Bank Name", }, - volume = new SliderWithTextBoxInput("Volume") - { - Current = new SampleControlPoint().SampleVolumeBindable, - } + volume = new IndeterminateSliderWithTextBoxInput("Volume", new SampleControlPoint().SampleVolumeBindable) } } }; - bank.Current = point.SampleBankBindable; - bank.Current.BindValueChanged(_ => beatmap.Update(hitObject)); + // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. + // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. + var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); + var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray(); - volume.Current = point.SampleVolumeBindable; - volume.Current.BindValueChanged(_ => beatmap.Update(hitObject)); + // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. + string? commonBank = relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; + + if (!string.IsNullOrEmpty(commonBank)) + bank.Current.Value = commonBank; + + int? commonVolume = relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; + + if (commonVolume != null) + volume.Current.Value = commonVolume.Value; + + bank.Current.BindValueChanged(val => updateBankFor(relevantObjects, val.NewValue)); + volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); + } + + private void updateBankFor(IEnumerable objects, string? newBank) + { + if (string.IsNullOrEmpty(newBank)) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + h.SampleControlPoint.SampleBank = newBank; + beatmap.Update(h); + } + + beatmap.EndChange(); + } + + private void updateVolumeFor(IEnumerable objects, int? newVolume) + { + if (newVolume == null) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + h.SampleControlPoint.SampleVolume = newVolume.Value; + beatmap.Update(h); + } + + beatmap.EndChange(); } } } From 76baf08140b4ae9971a44c9443f5b780a3d7ecef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:43:33 +0100 Subject: [PATCH 30/41] Expand test coverage with desired bank text box placeholder behaviour --- ...estSceneHitObjectSamplePointAdjustments.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index f30c56d53c..460b608166 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Editing } }); - EditorBeatmap.Add(new HitCircle() + EditorBeatmap.Add(new HitCircle { StartTime = 500, Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, @@ -138,9 +139,15 @@ namespace osu.Game.Tests.Visual.Editing clickSamplePiece(1); samplePopoverHasSingleBank("soft"); + setBankViaPopover(string.Empty); + hitObjectHasSampleBank(0, "soft"); + hitObjectHasSampleBank(1, "soft"); + samplePopoverHasSingleBank("soft"); + setBankViaPopover("drum"); hitObjectHasSampleBank(0, "drum"); hitObjectHasSampleBank(1, "drum"); + samplePopoverHasSingleBank("drum"); } [Test] @@ -155,9 +162,15 @@ namespace osu.Game.Tests.Visual.Editing clickSamplePiece(1); samplePopoverHasIndeterminateBank(); + setBankViaPopover(string.Empty); + hitObjectHasSampleBank(0, "normal"); + hitObjectHasSampleBank(1, "soft"); + samplePopoverHasIndeterminateBank(); + setBankViaPopover("normal"); hitObjectHasSampleBank(0, "normal"); hitObjectHasSampleBank(1, "normal"); + samplePopoverHasSingleBank("normal"); } private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => @@ -187,17 +200,17 @@ namespace osu.Game.Tests.Visual.Editing private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var textBox = popover?.ChildrenOfType().First(); + var textBox = popover?.ChildrenOfType().First(); - return textBox?.Current.Value == bank; + return textBox?.Current.Value == bank && string.IsNullOrEmpty(textBox?.PlaceholderText.ToString()); }); - private void samplePopoverHasIndeterminateBank() => AddUntilStep($"sample popover has indeterminate bank", () => + private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var textBox = popover?.ChildrenOfType().First(); + var textBox = popover?.ChildrenOfType().First(); - return textBox != null && string.IsNullOrEmpty(textBox.Current.Value); + return textBox != null && string.IsNullOrEmpty(textBox.Current.Value) && !string.IsNullOrEmpty(textBox.PlaceholderText.ToString()); }); private void dismissPopover() From 73ca1d39a2fbaeef88ad13ebc7904027eece8d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 18:06:32 +0100 Subject: [PATCH 31/41] Improve sample bank text box UX in case of multiple selection --- ...estSceneHitObjectSamplePointAdjustments.cs | 4 +++ .../Components/Timeline/SamplePointPiece.cs | 26 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 460b608166..dca30a6fc0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -237,6 +237,10 @@ namespace osu.Game.Tests.Visual.Editing var popover = this.ChildrenOfType().Single(); var textBox = popover.ChildrenOfType().First(); textBox.Current.Value = bank; + // force a commit via keyboard. + // this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit. + InputManager.ChangeFocus(textBox); + InputManager.Key(Key.Enter); }); private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () => diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index f0b11ce96e..fbbbb153b9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -99,20 +99,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. - string? commonBank = relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; - + string? commonBank = getCommonBank(relevantControlPoints); if (!string.IsNullOrEmpty(commonBank)) bank.Current.Value = commonBank; - int? commonVolume = relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; - + int? commonVolume = getCommonVolume(relevantControlPoints); if (commonVolume != null) volume.Current.Value = commonVolume.Value; - bank.Current.BindValueChanged(val => updateBankFor(relevantObjects, val.NewValue)); + updateBankPlaceholderText(relevantObjects); + bank.Current.BindValueChanged(val => + { + updateBankFor(relevantObjects, val.NewValue); + updateBankPlaceholderText(relevantObjects); + }); + // on commit, ensure that the value is correct by sourcing it from the objects' control points again. + // this ensures that committing empty text causes a revert to the previous value. + bank.OnCommit += (_, __) => bank.Current.Value = getCommonBank(relevantControlPoints); + volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); } + private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; + private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; + private void updateBankFor(IEnumerable objects, string? newBank) { if (string.IsNullOrEmpty(newBank)) @@ -129,6 +139,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.EndChange(); } + private void updateBankPlaceholderText(IEnumerable objects) + { + string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray()); + bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : null; + } + private void updateVolumeFor(IEnumerable objects, int? newVolume) { if (newVolume == null) From 9a19a516f99cd62b4b2dc5d5baa76e982ca96bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 18:09:40 +0100 Subject: [PATCH 32/41] Adjust spacings on sample point piece popover --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 ++ .../Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index fbbbb153b9..2cbfe88519 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -82,6 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Width = 200, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 10), Children = new Drawable[] { bank = new LabelledTextBox diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 17f82f4978..14b8c4c9de 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; using osu.Game.Utils; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -62,6 +63,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), Children = new Drawable[] { textbox = new LabelledTextBox From ca239ca40a2913103958552d911342bb2abef98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:41:16 +0100 Subject: [PATCH 33/41] Add failing test case for sharing clock state with gameplay test --- .../Editing/TestSceneEditorTestGameplay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 1d572772eb..160af47a6d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -14,6 +14,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osuTK.Graphics; using osuTK.Input; @@ -150,6 +151,35 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); } + [Test] + public void TestSharedClockState() + { + AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000)); + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + + GameplayClockContainer gameplayClockContainer = null; + AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType().First()); + AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning); + AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000); + + double timeAtPlayerExit = 0; + AddWaitStep("wait some", 5); + AddStep("store time before exit", () => timeAtPlayerExit = gameplayClockContainer.CurrentTime); + + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddAssert("time is past player exit", () => EditorClock.CurrentTime >= timeAtPlayerExit); + } + public override void TearDownSteps() { base.TearDownSteps(); From d2ddc25ab3a7457bf2496e5a5216f29ba31b4c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:50:38 +0100 Subject: [PATCH 34/41] Propagate clock state from editor to gameplay test --- osu.Game/Screens/Edit/Editor.cs | 21 +++++++++++++------ .../Screens/Edit/GameplayTest/EditorPlayer.cs | 15 +++++++++---- .../Edit/GameplayTest/EditorPlayerLoader.cs | 4 ++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ab24391bb4..ff9a19434b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -325,6 +325,19 @@ namespace osu.Game.Screens.Edit /// public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); + /// + /// Creates an instance representing the current state of the editor. + /// + /// + /// The next beatmap to be shown, in the case of difficulty switch. + /// indicates that the beatmap will not be changing. + /// + private EditorState getState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState + { + Time = clock.CurrentTimeAccurate, + ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty + }; + /// /// Restore the editor to a provided state. /// @@ -780,11 +793,7 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState - { - Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty - }); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, getState(nextBeatmap)); private void cancelExit() { @@ -807,7 +816,7 @@ namespace osu.Game.Screens.Edit pushEditorPlayer(); } - void pushEditorPlayer() => this.Push(new EditorPlayerLoader()); + void pushEditorPlayer() => this.Push(new EditorPlayerLoader(getState())); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 9856ad62bb..266c5e81d4 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Screens.Play; @@ -10,14 +11,20 @@ namespace osu.Game.Screens.Edit.GameplayTest { public class EditorPlayer : Player { - public EditorPlayer() - : base(new PlayerConfiguration { ShowResults = false }) - { - } + private readonly EditorState editorState; [Resolved] private MusicController musicController { get; set; } + public EditorPlayer(EditorState editorState) + : base(new PlayerConfiguration { ShowResults = false }) + { + this.editorState = editorState; + } + + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) + => new MasterGameplayClockContainer(beatmap, editorState.Time, true); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs index 610fff70f2..5c2ab04fd7 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Edit.GameplayTest [Resolved] private OsuLogo osuLogo { get; set; } - public EditorPlayerLoader() - : base(() => new EditorPlayer()) + public EditorPlayerLoader(EditorState editorState) + : base(() => new EditorPlayer(editorState)) { } From 2562412125e974efc2c89a4b4b31daa2070aa589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 13:12:25 +0100 Subject: [PATCH 35/41] Propagate clock state from gameplay test back to editor --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 9 +++++++-- osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ff9a19434b..2a7e2c9cef 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -332,7 +332,7 @@ namespace osu.Game.Screens.Edit /// The next beatmap to be shown, in the case of difficulty switch. /// indicates that the beatmap will not be changing. /// - private EditorState getState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState + public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState { Time = clock.CurrentTimeAccurate, ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty @@ -793,7 +793,7 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, getState(nextBeatmap)); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); private void cancelExit() { @@ -816,7 +816,7 @@ namespace osu.Game.Screens.Edit pushEditorPlayer(); } - void pushEditorPlayer() => this.Push(new EditorPlayerLoader(getState())); + void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this)); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 266c5e81d4..479dc2e6b3 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -11,15 +11,17 @@ namespace osu.Game.Screens.Edit.GameplayTest { public class EditorPlayer : Player { + private readonly Editor editor; private readonly EditorState editorState; [Resolved] private MusicController musicController { get; set; } - public EditorPlayer(EditorState editorState) + public EditorPlayer(Editor editor) : base(new PlayerConfiguration { ShowResults = false }) { - this.editorState = editorState; + this.editor = editor; + editorState = editor.GetState(); } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) @@ -45,6 +47,9 @@ namespace osu.Game.Screens.Edit.GameplayTest public override bool OnExiting(IScreen next) { musicController.Stop(); + + editorState.Time = GameplayClockContainer.CurrentTime; + editor.RestoreState(editorState); return base.OnExiting(next); } } diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs index 5c2ab04fd7..addc79ba61 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Edit.GameplayTest [Resolved] private OsuLogo osuLogo { get; set; } - public EditorPlayerLoader(EditorState editorState) - : base(() => new EditorPlayer(editorState)) + public EditorPlayerLoader(Editor editor) + : base(() => new EditorPlayer(editor)) { } From 6b4b6de5545f16762a8a63fdeb011ed6e95ee093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 13:51:02 +0100 Subject: [PATCH 36/41] Fix test gameplay starting in a hidden state --- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 479dc2e6b3..f49603c754 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -44,6 +44,16 @@ namespace osu.Game.Screens.Edit.GameplayTest protected override bool CheckModsAllowFailure() => false; // never fail. + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + // finish alpha transforms on entering to avoid gameplay starting in a half-hidden state. + // the finish calls are purposefully not propagated to children to avoid messing up their state. + FinishTransforms(); + GameplayClockContainer.FinishTransforms(false, nameof(Alpha)); + } + public override bool OnExiting(IScreen next) { musicController.Stop(); From 5be22916bb80ca652099369e6a69b2cee955fca5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Nov 2021 14:26:18 +0900 Subject: [PATCH 37/41] Add test coverage against frequency ramp increasing between fail animation and fail screen --- .../Visual/Gameplay/TestSceneFailAnimation.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 36fc6812bd..7167d3120a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -31,12 +31,18 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); + + // The pause screen and fail animation both ramp frequency. + // This tests to ensure that it doesn't reset during that handoff. + AddAssert("frequency only ever decreased", () => !((FailPlayer)Player).FrequencyIncreased); } private class FailPlayer : TestPlayer { public new FailOverlay FailOverlay => base.FailOverlay; + public bool FrequencyIncreased { get; private set; } + public FailPlayer() : base(false, false) { @@ -47,6 +53,19 @@ namespace osu.Game.Tests.Visual.Gameplay base.LoadComplete(); HealthProcessor.FailConditions += (_, __) => true; } + + private double lastFrequency = double.MaxValue; + + protected override void Update() + { + base.Update(); + + double freq = Beatmap.Value.Track.AggregateFrequency.Value; + + FrequencyIncreased |= freq > lastFrequency; + + lastFrequency = freq; + } } } } From a4aa0087934c54b7dec27a833a83da6c7f692233 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Nov 2021 14:13:12 +0900 Subject: [PATCH 38/41] Fix frequency ramping a second time incorrectly after fail --- osu.Game/Screens/Play/FailAnimation.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index f3676baf80..193e1e4129 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -107,7 +107,8 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ => { - RemoveFilters(); + // Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep. + RemoveFilters(false); OnComplete?.Invoke(); }); @@ -137,15 +138,16 @@ namespace osu.Game.Screens.Play Content.FadeColour(Color4.Gray, duration); } - public void RemoveFilters() + public void RemoveFilters(bool resetTrackFrequency = true) { + if (resetTrackFrequency) + track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + if (filters.Parent == null) return; RemoveInternal(filters); filters.Dispose(); - - track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); } protected override void Update() From a0e25d18cd5f809bfade52754a652d318d47a805 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Nov 2021 18:24:00 +0900 Subject: [PATCH 39/41] Expose more of `WorkingBeatmap` via interface --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 65 +++++++++++++++++++++++++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 21 ++++----- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index a916b37b85..22a922db59 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -17,33 +17,69 @@ namespace osu.Game.Beatmaps { public interface IWorkingBeatmap { + IBeatmapInfo BeatmapInfo { get; } + + IBeatmapSetInfo BeatmapSetInfo => BeatmapInfo.BeatmapSet; + + IBeatmapMetadataInfo Metadata => BeatmapInfo.Metadata; + /// - /// Retrieves the which this represents. + /// Whether the Beatmap has finished loading. + /// + public bool BeatmapLoaded { get; } + + /// + /// Whether the Background has finished loading. + /// + public bool BackgroundLoaded { get; } + + /// + /// Whether the Waveform has finished loading. + /// + public bool WaveformLoaded { get; } + + /// + /// Whether the Storyboard has finished loading. + /// + public bool StoryboardLoaded { get; } + + /// + /// Whether the Skin has finished loading. + /// + public bool SkinLoaded { get; } + + /// + /// Whether the Track has finished loading. + /// + public bool TrackLoaded { get; } + + /// + /// Retrieves the which this represents. /// IBeatmap Beatmap { get; } /// - /// Retrieves the background for this . + /// Retrieves the background for this . /// Texture Background { get; } /// - /// Retrieves the for the of this . + /// Retrieves the for the of this . /// Waveform Waveform { get; } /// - /// Retrieves the which this provides. + /// Retrieves the which this provides. /// Storyboard Storyboard { get; } /// - /// Retrieves the which this provides. + /// Retrieves the which this provides. /// ISkin Skin { get; } /// - /// Retrieves the which this has loaded. + /// Retrieves the which this has loaded. /// Track Track { get; } @@ -67,7 +103,7 @@ namespace osu.Game.Beatmaps /// /// /// In a standard game context, the loading of the track is managed solely by MusicController, which will - /// automatically load the track of the current global IBindable WorkingBeatmap. + /// automatically load the track of the current global IBindable IWorkingBeatmap. /// As such, this method should only be called in very special scenarios, such as external tests or apps which are /// outside of the game context. /// @@ -79,5 +115,20 @@ namespace osu.Game.Beatmaps /// /// The storage path to the file. Stream GetStream(string storagePath); + + /// + /// Beings loading the contents of this asynchronously. + /// + public void BeginAsyncLoad(); + + /// + /// Cancels the asynchronous loading of the contents of this . + /// + public void CancelAsyncLoad(); + + /// + /// Reads the correct track restart point from beatmap metadata and sets looping to enabled. + /// + void PrepareTrackForPreviewLooping(); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d2c0f7de0f..51eea94d3a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -28,8 +28,10 @@ namespace osu.Game.Beatmaps { public readonly BeatmapInfo BeatmapInfo; + // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapSetInfo BeatmapSetInfo; + // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapMetadata Metadata; protected AudioManager AudioManager { get; } @@ -89,6 +91,9 @@ namespace osu.Game.Beatmaps var rulesetInstance = ruleset.CreateInstance(); + if (rulesetInstance == null) + throw new RulesetLoadException("Creating ruleset instance failed when attempting to create playable beatmap."); + IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); // Check if the beatmap can be converted @@ -176,17 +181,8 @@ namespace osu.Game.Beatmaps private CancellationTokenSource loadCancellation = new CancellationTokenSource(); - /// - /// Beings loading the contents of this asynchronously. - /// - public void BeginAsyncLoad() - { - loadBeatmapAsync(); - } + public void BeginAsyncLoad() => loadBeatmapAsync(); - /// - /// Cancels the asynchronous loading of the contents of this . - /// public void CancelAsyncLoad() { lock (beatmapFetchLock) @@ -234,6 +230,8 @@ namespace osu.Game.Beatmaps public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; + IBeatmapInfo IWorkingBeatmap.BeatmapInfo => BeatmapInfo; + public IBeatmap Beatmap { get @@ -273,9 +271,6 @@ namespace osu.Game.Beatmaps [NotNull] public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); - /// - /// Reads the correct track restart point from beatmap metadata and sets looping to enabled. - /// public void PrepareTrackForPreviewLooping() { Track.Looping = true; From a90cfb4a2f9b4cf839e3ad0daac1219c984b96e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Nov 2021 19:30:46 +0900 Subject: [PATCH 40/41] Implement remaining properties via implicit implementation rather than interface methods --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 22a922db59..ba887edf62 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -19,9 +19,9 @@ namespace osu.Game.Beatmaps { IBeatmapInfo BeatmapInfo { get; } - IBeatmapSetInfo BeatmapSetInfo => BeatmapInfo.BeatmapSet; + IBeatmapSetInfo BeatmapSetInfo { get; } - IBeatmapMetadataInfo Metadata => BeatmapInfo.Metadata; + IBeatmapMetadataInfo Metadata { get; } /// /// Whether the Beatmap has finished loading. diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 51eea94d3a..bba836484f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -231,6 +231,8 @@ namespace osu.Game.Beatmaps public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; IBeatmapInfo IWorkingBeatmap.BeatmapInfo => BeatmapInfo; + IBeatmapMetadataInfo IWorkingBeatmap.Metadata => Metadata; + IBeatmapSetInfo IWorkingBeatmap.BeatmapSetInfo => BeatmapSetInfo; public IBeatmap Beatmap { From 1d87b47fecb9ed281d2c641a7e15262de827e55d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 15 Nov 2021 19:40:31 +0900 Subject: [PATCH 41/41] Remove now-unnecessary R# disables --- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bba836484f..f68f34f673 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -27,11 +27,7 @@ namespace osu.Game.Beatmaps public abstract class WorkingBeatmap : IWorkingBeatmap { public readonly BeatmapInfo BeatmapInfo; - - // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapSetInfo BeatmapSetInfo; - - // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapMetadata Metadata; protected AudioManager AudioManager { get; }