From cc800a18b203e474fc9fe6d43f50c2bc4e63ea03 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 18 Dec 2023 21:11:00 +0100 Subject: [PATCH 01/14] Fix opening log files from notification not presenting the correct file --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8d9e029c9d1b..e7ff99ef01ea 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1190,7 +1190,7 @@ private void forwardGeneralLogsToNotifications() } else if (recentLogCount == short_term_display_limit) { - string logFile = $@"{entry.Target.Value.ToString().ToLowerInvariant()}.log"; + string logFile = Logger.GetLogger(entry.Target.Value).Filename; Schedule(() => Notifications.Post(new SimpleNotification { @@ -1198,7 +1198,7 @@ private void forwardGeneralLogsToNotifications() Text = NotificationsStrings.SubsequentMessagesLogged, Activated = () => { - Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile); + Logger.Storage.PresentFileExternally(logFile); return true; } })); From ee8a5d5a3077c5a1045ef74fc8b037296a26a005 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 13:33:45 +0900 Subject: [PATCH 02/14] Update bug-issue.yml with new workflow for exporting logs --- .github/ISSUE_TEMPLATE/bug-issue.yml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml index ff6d869e7200..17a3e1df4137 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue.yml +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -46,22 +46,16 @@ body: value: | ## Logs - Attaching log files is required for every reported bug. See instructions below on how to find them. - - **Logs are reset when you reopen the game.** If the game crashed or has been closed since you found the bug, retrieve the logs using the file explorer instead. + Attaching log files is required for **every** issue, regardless of whether you deem them required or not. See instructions below on how to find them. ### Desktop platforms If the game has not yet been closed since you found the bug: - 1. Head on to game settings and click on "Open osu! folder" - 2. Then open the `logs` folder located there - - The default places to find the logs on desktop platforms are as follows: - - `%AppData%/osu/logs` *on Windows* - - `~/.local/share/osu/logs` *on Linux* - - `~/Library/Application Support/osu/logs` *on macOS* + 1. Head on to game settings and click on "Export logs" + 2. Click the notification to locate the file + 3. Drag the generated `.zip` files into the github issue window - If you have selected a custom location for the game files, you can find the `logs` folder there. + ![export logs button](https://github.com/ppy/osu/assets/191335/0866443f-0728-47bc-9dbd-f2b79ac802d5) ### Mobile platforms @@ -69,10 +63,6 @@ body: - *On Android*, navigate to `Android/data/sh.ppy.osulazer/files/logs` using a file browser app. - *On iOS*, connect your device to a PC and copy the `logs` directory from the app's document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer) - --- - - After locating the `logs` folder, select all log files inside and drag them into the "Logs" box below. - - type: textarea attributes: label: Logs From 469a659938dfb3c696c9a9fb51c7252214014a31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 13:35:02 +0900 Subject: [PATCH 03/14] Fix arrow pointing to wrong place --- .github/ISSUE_TEMPLATE/bug-issue.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml index 17a3e1df4137..00a873f9c8b6 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue.yml +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -53,9 +53,9 @@ body: If the game has not yet been closed since you found the bug: 1. Head on to game settings and click on "Export logs" 2. Click the notification to locate the file - 3. Drag the generated `.zip` files into the github issue window + 3. Drag the generated `.zip` files into the github issue window - ![export logs button](https://github.com/ppy/osu/assets/191335/0866443f-0728-47bc-9dbd-f2b79ac802d5) + ![export logs button](https://github.com/ppy/osu/assets/191335/cbfa5550-b7ed-4c5c-8dd0-8b87cc90ad9b) ### Mobile platforms From 44efa2c540c392c998af7cd052a88d35d053aa5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 15:09:03 +0900 Subject: [PATCH 04/14] Fix incorrect ordering of items at song select when certain sort modes are used --- .../Screens/Select/Carousel/CarouselGroup.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index c353ee98ae3f..be841465bfdb 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -87,7 +87,7 @@ public override void Filter(FilterCriteria criteria) items.ForEach(c => c.Filter(criteria)); // Sorting is expensive, so only perform if it's actually changed. - if (lastCriteria?.Sort != criteria.Sort) + if (lastCriteria?.RequiresSorting(criteria) != false) { criteriaComparer = Comparer.Create((x, y) => { diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 811f623ee54c..0bea2247cead 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -219,6 +219,44 @@ public string SearchTerm public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; } + /// + /// Given a new filter criteria, decide whether a full sort needs to be performed. + /// + /// + /// + public bool RequiresSorting(FilterCriteria newCriteria) + { + if (Sort != newCriteria.Sort) + return true; + + switch (Sort) + { + // Some sorts are stable across all other changes. + // Running these sorts will sort all items, including currently hidden items. + case SortMode.Artist: + case SortMode.Author: + case SortMode.DateSubmitted: + case SortMode.DateAdded: + case SortMode.DateRanked: + case SortMode.Source: + case SortMode.Title: + return false; + + // Some sorts use aggregate max comparisons, which will change based on filtered items. + // These sorts generally ignore items hidden by filtered state, so we must force a sort under all circumstances here. + // + // This makes things very slow when typing a text search, and we probably want to consider a way to optimise things going forward. + case SortMode.LastPlayed: + case SortMode.BPM: + case SortMode.Length: + case SortMode.Difficulty: + return true; + + default: + throw new ArgumentOutOfRangeException(); + } + } + public enum MatchMode { /// From 011bd61e7d3f7301fd00bd5acd0d89120a92b61a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 16:47:49 +0900 Subject: [PATCH 05/14] Give sliders in test scene a sample --- .../TestSceneSliderEarlyHitJudgement.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs index 9caee86a3250..4ea21e51f66a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderEarlyHitJudgement.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; @@ -160,6 +161,10 @@ private void performTest(List frames, Action? adjustSliderF Position = new Vector2(256 - slider_path_length / 2, 192), TickDistanceMultiplier = 3, ClassicSliderBehaviour = classic, + Samples = new[] + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, From 7c05d66bd7913c3f7aa1a569af5fa1dd901c58dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Dec 2023 08:57:18 +0100 Subject: [PATCH 06/14] Add more detail to exception --- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 0bea2247cead..a7c8e7d09372 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -253,7 +253,7 @@ public bool RequiresSorting(FilterCriteria newCriteria) return true; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(Sort), Sort, "Unknown sort mode"); } } From fe5e071e70cbd0a24f555fb9255dfa957246eb36 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Dec 2023 17:01:52 +0900 Subject: [PATCH 07/14] Fix sliding sample playing before Slider's start time --- .../Objects/Drawables/DrawableSlider.cs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 1f9a02804598..b306fd38c19e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -128,8 +128,6 @@ private void load() foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; }, true); - - Tracking.BindValueChanged(updateSlidingSample); } protected override void OnApply() @@ -166,14 +164,6 @@ public override void StopAllSamples() slidingSample?.Stop(); } - private void updateSlidingSample(ValueChangedEvent tracking) - { - if (tracking.NewValue) - slidingSample?.Play(); - else - slidingSample?.Stop(); - } - protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); @@ -238,9 +228,18 @@ protected override void Update() Tracking.Value = SliderInputManager.Tracking; - if (Tracking.Value && slidingSample != null) - // keep the sliding sample playing at the current tracking position - slidingSample.Balance.Value = CalculateSamplePlaybackBalance(CalculateDrawableRelativePosition(Ball)); + if (slidingSample != null) + { + if (Tracking.Value && Time.Current >= HitObject.StartTime) + { + // keep the sliding sample playing at the current tracking position + if (!slidingSample.IsPlaying) + slidingSample.Play(); + slidingSample.Balance.Value = CalculateSamplePlaybackBalance(CalculateDrawableRelativePosition(Ball)); + } + else if (slidingSample.IsPlaying) + slidingSample.Stop(); + } double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1); From 7e9c1b2acbaabb19564821d748deb2c97a964554 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 17:27:52 +0900 Subject: [PATCH 08/14] Use `sender`'s realm (because we can) --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 607b891beb0a..4c6c67c34847 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -320,7 +320,7 @@ private void beatmapSetsChanged(IRealmCollection sender, ChangeS // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. // When an update occurs, the previous beatmap set is either soft or hard deleted. // Check if the current selection was potentially deleted by re-querying its validity. - bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID))?.DeletePending != false; + bool selectedSetMarkedDeleted = sender.Realm.Find(SelectedBeatmapSet.ID)?.DeletePending != false; int[] modifiedAndInserted = changes.NewModifiedIndices.Concat(changes.InsertedIndices).ToArray(); From 8f5d21dc703e3c1f3930e4b4e1c58c90acaba87a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 18:10:55 +0900 Subject: [PATCH 09/14] Actually fix realm selection retention regression --- .../SongSelect/TestScenePlaySongSelect.cs | 4 ---- osu.Game/Screens/Select/BeatmapCarousel.cs | 24 +++++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6af53df725d3..518035fb822a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -464,8 +464,6 @@ public void TestSelectionRetainedOnBeatmapUpdate() manager.Import(testBeatmapSetInfo); }, 10); - AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); - AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); Task?> updateTask = null!; @@ -478,8 +476,6 @@ public void TestSelectionRetainedOnBeatmapUpdate() }); AddUntilStep("wait for update completion", () => updateTask.IsCompleted); - AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh())); - AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID, () => Is.EqualTo(originalOnlineSetID)); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 4c6c67c34847..47cdbe34f499 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -269,8 +269,28 @@ private void deletedBeatmapSetsChanged(IRealmCollection sender, if (changes == null) return; - foreach (int i in changes.InsertedIndices) - removeBeatmapSet(sender[i].ID); + var removeableSets = changes.InsertedIndices.Select(i => sender[i].ID).ToHashSet(); + + // This schedule is required to retain selection of beatmaps over an ImportAsUpdate operation. + // This is covered by TestPlaySongSelect.TestSelectionRetainedOnBeatmapUpdate. + // + // In short, we have specialised logic in `beatmapSetsChanged` (directly below) to infer that an + // update operation has occurred. For this to work, we need to confirm the `DeletePending` flag + // of the current selection. + // + // If we don't schedule the following code, it is possible for the `deleteBeatmapSetsChanged` handler + // to be invoked before the `beatmapSetsChanged` handler (realm call order seems non-deterministic) + // which will lead to the currently selected beatmap changing via `CarouselGroupEagerSelect`. + // + // We need a better path forward here. A few ideas: + // - Avoid the necessity of having realm subscriptions on deleted/hidden items, maybe by storing all guids in realm + // to a local list so we can better look them up on receiving `DeletedIndices`. + // - Add a new property on `BeatmapSetInfo` to link to the pre-update set, and use that to handle the update case. + Schedule(() => + { + foreach (var set in removeableSets) + removeBeatmapSet(set); + }); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) From f09c6b8c1ba778d2a5a6f6f3c29de875bc3dc7a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 18:20:02 +0900 Subject: [PATCH 10/14] Change default values of new object counts to `-1` to identify non-processed values --- osu.Game/BackgroundDataStoreProcessor.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- osu.Game/Beatmaps/IBeatmapInfo.cs | 3 +++ osu.Game/Database/RealmAccess.cs | 17 ++++++++++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 33b66ecfc7f5..d4cd1ff6ea99 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -196,7 +196,7 @@ private void processBeatmapsWithMissingObjectCounts() realmAccess.Run(r => { - foreach (var b in r.All().Where(b => b.TotalObjectCount == 0)) + foreach (var b in r.All().Where(b => b.TotalObjectCount < 0 || b.EndTimeObjectCount < 0)) beatmapIds.Add(b.ID); }); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 2d04732f911d..425fd98d27e9 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -120,9 +120,9 @@ public BeatmapOnlineStatus Status [JsonIgnore] public bool Hidden { get; set; } - public int EndTimeObjectCount { get; set; } + public int EndTimeObjectCount { get; set; } = -1; - public int TotalObjectCount { get; set; } + public int TotalObjectCount { get; set; } = -1; /// /// Reset any fetched online linking information (and history). diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 9dcff5ce5eda..04c2017dedb2 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -59,11 +59,13 @@ public interface IBeatmapInfo : IHasOnlineID, IEquatable /// /// The basic star rating for this beatmap (with no mods applied). + /// Defaults to -1 (meaning not-yet-calculated). /// double StarRating { get; } /// /// The number of hitobjects in the beatmap with a distinct end time. + /// Defaults to -1 (meaning not-yet-calculated). /// /// /// Canonically, these are hitobjects are either sliders or spinners. @@ -72,6 +74,7 @@ public interface IBeatmapInfo : IHasOnlineID, IEquatable /// /// The total number of hitobjects in the beatmap. + /// Defaults to -1 (meaning not-yet-calculated). /// int TotalObjectCount { get; } } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 191bb49b0c9c..ad61292c2e66 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -89,8 +89,9 @@ public class RealmAccess : IDisposable /// 35 2023-10-16 Clear key combinations of keybindings that are assigned to more than one action in a given settings section. /// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs. /// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo. + /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// - private const int schema_version = 38; + private const int schema_version = 39; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1095,6 +1096,20 @@ void convertOnlineIDs() where T : RealmObject break; } + + case 39: + foreach (var b in migration.NewRealm.All()) + { + // Either actually no objects, or processing ran and failed. + // Reset to -1 so the next time they become zero we know that processing was attempted. + if (b.TotalObjectCount == 0 && b.EndTimeObjectCount == 0) + { + b.TotalObjectCount = -1; + b.EndTimeObjectCount = -1; + } + } + + break; } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From 372f930f8bd17b6ab4b928805fa4ee78a2100a48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 18:27:44 +0900 Subject: [PATCH 11/14] Refactor usage of object counts for mania key count lookup to be a bit safer Protects against non-initialised values and also div-by-zero. --- .../Beatmaps/ManiaBeatmapConverter.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index a49604972957..def22608d617 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -59,23 +59,26 @@ public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficult { double roundedCircleSize = Math.Round(difficulty.CircleSize); - if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset)) + if (difficulty.SourceRuleset.ShortName == ManiaRuleset.SHORT_NAME) return (int)Math.Max(1, roundedCircleSize); double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty); - int countSliderOrSpinner = difficulty.EndTimeObjectCount; - - // In osu!stable, this division appears as if it happens on floats, but due to release-mode - // optimisations, it actually ends up happening on doubles. - double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount; - - if (percentSpecialObjects < 0.2) - return 7; - if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5) - return roundedOverallDifficulty > 5 ? 7 : 6; - if (percentSpecialObjects > 0.6) - return roundedOverallDifficulty > 4 ? 5 : 4; + if (difficulty.TotalObjectCount > 0 && difficulty.EndTimeObjectCount >= 0) + { + int countSliderOrSpinner = difficulty.EndTimeObjectCount; + + // In osu!stable, this division appears as if it happens on floats, but due to release-mode + // optimisations, it actually ends up happening on doubles. + double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount; + + if (percentSpecialObjects < 0.2) + return 7; + if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5) + return roundedOverallDifficulty > 5 ? 7 : 6; + if (percentSpecialObjects > 0.6) + return roundedOverallDifficulty > 4 ? 5 : 4; + } return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); } From 502e3edac3ea2c5082718ca49d87064fdea4e92e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:39:48 +0900 Subject: [PATCH 12/14] Add missing invalidation call --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 47cdbe34f499..fe7eee701f5f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -290,6 +290,8 @@ private void deletedBeatmapSetsChanged(IRealmCollection sender, { foreach (var set in removeableSets) removeBeatmapSet(set); + + invalidateAfterChange(); }); } From c556475c2c3522729cc1cf17b02b1497730d6e66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Dec 2023 19:46:30 +0900 Subject: [PATCH 13/14] Revert to using a more manual approach to holding focus --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 30d7b6191e6d..baa7e594c18e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -132,6 +132,8 @@ protected virtual IEnumerable CreateFooterButtons() protected ShearedToggleButton? CustomisationButton { get; private set; } protected SelectAllModsButton? SelectAllModsButton { get; set; } + private bool textBoxShouldFocus; + private Sample? columnAppearSample; private WorkingBeatmap? beatmap; @@ -510,9 +512,9 @@ private void updateCustomisationVisualState() TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic); if (customisationVisible.Value) - GetContainingInputManager().ChangeFocus(modSettingsArea); + SearchTextBox.KillFocus(); else - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); + setTextBoxFocus(textBoxShouldFocus); } /// @@ -626,8 +628,7 @@ protected override void PopIn() nonFilteredColumnCount += 1; } - if (textSearchStartsActive.Value) - SearchTextBox.HoldFocus = true; + setTextBoxFocus(textSearchStartsActive.Value); } protected override void PopOut() @@ -766,12 +767,20 @@ protected override bool OnKeyDown(KeyDownEvent e) return false; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) - SearchTextBox.HoldFocus = !SearchTextBox.HoldFocus; - if (SearchTextBox.HoldFocus) - SearchTextBox.TakeFocus(); + setTextBoxFocus(!textBoxShouldFocus); return true; } + private void setTextBoxFocus(bool keepFocus) + { + textBoxShouldFocus = keepFocus; + + if (textBoxShouldFocus) + SearchTextBox.TakeFocus(); + else + SearchTextBox.KillFocus(); + } + #endregion #region Sample playback control From d793d1cea16de4098c58fb5c50a3e20142a7a996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 19 Dec 2023 14:52:16 +0100 Subject: [PATCH 14/14] Add test coverage of desired input handling behaviour --- .../TestSceneModSelectOverlay.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 80be4412b39e..4b101a52f42a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -572,7 +572,7 @@ void navigateAndClick() where T : Drawable [Test] public void TestTextSearchActiveByDefault() { - configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true); + AddStep("text search starts active", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true)); createScreen(); AddUntilStep("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); @@ -587,7 +587,7 @@ public void TestTextSearchActiveByDefault() [Test] public void TestTextSearchNotActiveByDefault() { - configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false); + AddStep("text search does not start active", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false)); createScreen(); AddUntilStep("search text box not focused", () => !modSelectOverlay.SearchTextBox.HasFocus); @@ -599,6 +599,31 @@ public void TestTextSearchNotActiveByDefault() AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus); } + [Test] + public void TestTextSearchDoesNotBlockCustomisationPanelKeyboardInteractions() + { + AddStep("text search starts active", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true)); + createScreen(); + + AddUntilStep("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); + AddAssert("DT selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value), () => Is.EqualTo(1)); + + AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick()); + assertCustomisationToggleState(false, true); + AddStep("hover over mod settings slider", () => + { + var slider = modSelectOverlay.ChildrenOfType().Single().ChildrenOfType>().First(); + InputManager.MoveMouseTo(slider); + }); + AddStep("press right arrow", () => InputManager.PressKey(Key.Right)); + AddAssert("DT speed changed", () => !SelectedMods.Value.OfType().Single().SpeedChange.IsDefault); + + AddStep("close customisation area", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("search text box reacquired focus", () => modSelectOverlay.SearchTextBox.HasFocus); + } + [Test] public void TestDeselectAllViaKey() {